<?php

# PLUGIN PREVIEW BY TEXTPATTERN.INFO

/**
 * smd_calendar
 *
 * A Textpattern CMS plugin for complete monthly event and calendar management
 *  -> Originally based on mdp_calendar - thanks Marshall!
 *  -> Full-size / mini calendar by month, with ISO week support
 *  -> Txp articles are events. Future, past, and today's events are supported. Multi-day (spanned) events are based on article expiry
 *  -> Next/prev month/year select list with customisable start/end years
 *  -> Filter events by cat / section / author / status / time / expiry
 *  -> Specify event frequency in custom field (1 week / 10 days / 3 months / etc)
 *  -> Optionally (re)schedule / cancel / omit event dates
 *  -> Customisable output for events and cells using multiple forms/container and classes
 *  -> Conditional tags for building custom logic
 *
 * @author Stef Dawson
 * @link   http://stefdawson.com/
 */

// TODO:
//  * allow table header to be removed / restyled completely (the month/week dropdown & nav icons)
//     -- a form (navform?) for the header row with access to all vars such as which month is being displayed?
//     -- tools to allow the header to be generated from components and laid out in any manner?
//  * allow URL vars to be passed as POST (to bypass gbp_permlinks)
//  * add custom rows to the table (header, footer) -- header could be used to replace the current nav/dropdowns
//  * div-based calendar layout?
// TOFIX:
//  * Expiry dates on extra+ allspanned dates in smd_article_event (and calendar?). They currently 'creep' a day for every day of a spanned event
//  * Ranges in stepfields (http://forum.textpattern.com/viewtopic.php?pid=254395#p254395 and http://forum.textpattern.com/viewtopic.php?pid=255617#p255617)

if ($date gps('date')) {
    
$_GET['month'] = $date;
}

if (
class_exists('\Textpattern\Tag\Registry')) {
    
Txp::get('\Textpattern\Tag\Registry')
        ->
register('smd_calendar')
        ->
register('smd_cal_info')
        ->
register('smd_cal_now')
        ->
register('smd_cal_class')
        ->
register('smd_if_cal')
        ->
register('smd_article_event')
        ->
register('smd_event_info')
        ->
register('smd_event_duration');
}

function 
smd_calendar($atts$thing null)
{
    global 
$pretext$thisarticle$variable$prefs$smd_cal_flag$smd_date$smd_calinfo$smd_cal_ucls;

    
extract(lAtts(array(
        
'time'          => 'any',
        
'size'          => 'large',
        
'expired'       => '',
        
'category'      => null,
        
'subcats'       => '',
        
'section'       => '',
        
'author'        => '',
        
'realname'      => '',
        
'status'        => 'live',
        
'showall'       => '0',
        
'static'        => '',
        
'form'          => '',
        
'spanform'      => 'SMD_SAME',
        
'recurform'     => 'SMD_SAME',
        
'cellform'      => '',
        
'headerform'    => '',
        
'stepfield'     => '',
        
'skipfield'     => '',
        
'omitfield'     => '',
        
'extrafield'    => '',
        
'extrastrict'   => '0',
        
'datefields'    => '',
        
'showskipped'   => '0',
        
'showspanned'   => '1',
        
'holidays'      => '',
        
'holidayflags'  => 'standard',
        
'classlevels'   => 'cell, event',
        
'linkposted'    => 'recur, multi, multiprev, multilast',
        
'classprefixes' => 'smd_cal_, smd_cal_ev_',
        
'class'         => '',
        
'rowclass'      => '',
        
'cellclass'     => '',
        
'emptyclass'    => 'empty',
        
'isoweekclass'  => 'week',
        
'navclass'      => 'navprev, navnext',
        
'navarrow'      => '&#60;, &#62;',
        
'navid'         => '',
        
'eventclasses'  => 'category',
        
'eventwraptag'  => 'span',
        
'select'        => '',
        
'selectbtn'     => '',
        
'myclass'       => '',
        
'mywraptag'     => '',
        
'caption'       => '',
        
'summary'       => '',
        
'id'            => '',
        
'week'          => '',
        
'month'         => '',
        
'year'          => '',
        
'remap'         => '',
        
'yearwidth'     => '0',
        
'isoweeks'      => '',
        
'dayformat'     => 'ABBR',
        
'monthformat'   => 'FULL',
        
'firstday'      => 0,
        
'maintain'      => 'calid',
        
'nameval'       => '',
        
'event_delim'   => ',',
        
'gmt'           => 0,
        
'lang'          => '',
        
'debug'         => 0,
    ), 
$atts));

    
$size = (in_array($size, array('small''large'))) ? $size 'large';
    
$status = ($status) ? $status 'live'// in case status is empty
    
$firstday = ($isoweeks == '') ? $firstday 1;
    
$spanform = ($spanform == 'SMD_SAME') ? $form $spanform;
    
$recurform = ($recurform == 'SMD_SAME') ? $form $recurform;
    
$cellform = (empty($cellform)) ? '' fetch_form($cellform);
    
$headerform = (empty($headerform)) ? '' fetch_form($headerform);
    
$frontpage = ($section=='' && $pretext['s']=='default') ? true false;

    
// Set up the class prefixes
    
$clevs do_list($classlevels);
    
$cls do_list($classprefixes);
    
$cls_pfx $evc_pfx $cls[0];
    if (
count($cls) > 1){
        
$evc_pfx $cls[1];
    }

    
// Set up the nav class(es)
    
$maintain do_list($maintain);
    
$navarrow do_list($navarrow);
    
$navparr $navarrow[0];
    
$navnarr = (count($navarrow) > 1) ? $navarrow[1] : $navarrow[0];
    
$navclass do_list($navclass);
    
$navpclass $navclass[0];
    
$navnclass = (count($navclass) > 1) ? $navclass[1] : $navclass[0];

    
// Filters
    
$fopts = array();
    
$catSQL $secSQL $authSQL $fpSQL '';
    if(
$category !== null) {
        
$uncats false;
        
$allcats do_list($category);
        if ((
$pos array_search('SMD_UNCAT'$allcats)) !== false) {
            
$uncats true;
            unset(
$allcats[$pos]);
            
$category join(','$allcats);
        }
        
$fopts['c'] = $category// TODO: Can fopts take a list? Should it include subcats?
        
$subcats = (empty($subcats)) ? : ((strtolower($subcats)=="all") ? 99999 intval($subcats));
        if (
$subcats) {
            
$outcats = array();
            foreach (
$allcats as $cat) {
                
$cats getTree(doslash($cat), 'article');
                foreach (
$cats as $jdx => $val) {
                    if (
$cats[$jdx]['level'] <= $subcats) {
                        
$outcats[] = $cats[$jdx]['name'];
                    }
                }
            }
            
$allcats $outcats;
        }
        
$catSQL doQuote(join("','"doSlash($allcats)));
        
$catSQL = ($uncats " AND (Category1 = '' AND Category2 = '')" '') .
            (
$uncats && $allcats " OR " : ($allcats " AND " '')) .
            (
$allcats "( Category1 IN (".$catSQL.") OR Category2 IN (".$catSQL.") ) " '');
    }
    if(
$section) {
        
$secs do_list($section);
        
$smd_calinfo['s'] = $secs[0];
        
$secSQL doQuote(join("','"doSlash($secs)));
        
$secSQL " AND Section IN (".$secSQL.") ";
    }
    if(
$realname) {
        
$authors safe_column('name''txp_users''RealName IN ('doQuote(join("','"doArray(do_list($realname), 'urldecode'))) .')' );
        
$author join(','$authors);
    }
    if(
$author) {
        
$fopts['author'] = htmlentities(gps('author'));
        
$authSQL doQuote(join("','"doSlash(do_list($author))));
        
$authSQL " AND AuthorID IN (".$authSQL.") ";
    }
    if (
$frontpage && !$showall) {
        
$fpSQL filterFrontPage();
    }
    
$smd_calinfo['artid'] = $thisarticle['thisid'];
    
$smd_calinfo['artitle'] = $thisarticle['url_title'];
    
$nameval do_list($nameval);
    foreach (
$nameval as $nv) {
        
$nv explode("="$nv);
        if (
$nv[0]) {
            
$fopts[$nv[0]] = ((isset($nv[1])) ? $nv[1] : '');
        }
    }
    
$status do_list($status);
    
$stati = array();
    foreach (
$status as $stat) {
        if (empty(
$stat)) {
            continue;
        } else if (
is_numeric($stat)) {
            
$stati[] = $stat;
        } else {
            
$stati[] = getStatusNum($stat);
        }
    }
    
$stati " Status IN (".join(','$stati).")";

    
$expired = ($expired) ? $expired $prefs['publish_expired_articles'];
    
$expired = (($expired) ? '' ' AND (now() <= Expires OR Expires IS NULL)');
    
$eventclasses do_list($eventclasses);
    
$holidayflags do_list($holidayflags);
    
$linkposted do_list($linkposted);
    
$datefields do_list($datefields);

    
// Work out the first and last posts to determine the year range - probably a better way of doing this than 3 queries
    
$filt $stati . (($category !== null) ? $catSQL '') . (($section) ? $secSQL '') . (($author) ? $authSQL '') . $fpSQL;
    
$earliest safe_field('unix_timestamp(Posted) AS uPosted''textpattern'$filt .' ORDER BY Posted ASC LIMIT 0, 1'$debug);
    
$lp safe_field('unix_timestamp(Posted) AS uPosted''textpattern'$filt .' ORDER BY Posted DESC LIMIT 0, 1'$debug);
    
$lm safe_field('unix_timestamp(LastMod) AS uLastMod''textpattern'$filt .' ORDER BY LastMod DESC LIMIT 0, 1'$debug);
    
$latest = ($time=="past") ? time() : (($lp $lm) ? $lp $lm);

    
$yearwidth do_list($yearwidth);
    
$yearwidth[0] = (empty($yearwidth[0])) ? $yearwidth[0];
    if (
count($yearwidth) == 1) {
        
$yearwidth[1] = $yearwidth[0];
    }
    
$usenow = array(false,false);
    foreach (
$yearwidth as $yridx => $yritem) {
        if (
strpos($yritem,"c") !== false) {
            
$yearwidth[$yridx] = intval($yritem);
            
$usenow[$yridx] = true;
        }
    }

    
// Remap w/m/y to other vars if required
    
$remap do_list($remap);
    
$dmap = array("y" => "y""m" => "m""w" => "w");
    foreach (
$remap as $dpair) {
        
$dpair do_list($dpair':');
        
$dmap[$dpair[0]] = (isset($dpair[1])) ? $dpair[1] : $dpair[0];
    }
    
$earliest date("Y"strtotime("-".$yearwidth[0]." year", ( (empty($earliest) || $usenow[0]==true) ? time() : $earliest) ) );
    
$latest date("Y"strtotime("+".$yearwidth[1]." year", ( (empty($latest) || $usenow[1]==true) ? time() : $latest) ) );

    
// Check the URL for current date and calendar target info
    
$in_calid gps('calid');
    
$in_year = (gps($dmap["y"]) and is_numeric(gps($dmap["y"]))) ? (int)gps($dmap["y"]) : '';
    
$in_month = (gps($dmap["m"]) and is_numeric(gps($dmap["m"]))) ? (int)gps($dmap["m"]) : '';
    
$in_week = (gps($dmap["w"]) and is_numeric(gps($dmap["w"]))) ? (int)gps($dmap["w"]) : '';

    if(
$static) { // if we're static w/o any supplied vars, use the current date
        
if(!$year) { $year safe_strftime('%Y'); }
        if(!
$month) { $month safe_strftime('%m'); }
    } else { 
// otherwise use current date only if there's nothing else
        
if( $id == $in_calid ) { // use incoming
            
$year = ($in_year) ? $in_year : (($year) ? $year safe_strftime('%Y'));
            
$month = ($in_month) ? $in_month : (($month) ? $month safe_strftime('%m'));
            
// If week is used, adjust month so it encompasses the given week
            
$week $in_week;
            if (
$week) {
                
$month safe_strftime("%m"strtotime($year."W".str_pad($week2'0'STR_PAD_LEFT))); // Get the month from the week
            
}
        } else { 
// use current
            
if(!$year) { $year safe_strftime('%Y'); }
            if(!
$month) { $month safe_strftime('%m'); }
            if(
$week) { $month safe_strftime("%m"strtotime($year."W".str_pad($week2'0'STR_PAD_LEFT))); }
        }
    }
    
$smd_calinfo['id'] = ($in_calid) ? $in_calid $id;
    
$smd_date['y'] = $year$smd_date['m'] = $month// $week/day/isoyear are set per event later

    
$ts_first mktime(000$month1$year);
    
$ts_last mktime(235959$monthdate('t',$ts_first), $year);
    
$ts_lastoff $ts_last tz_offset($ts_last);
    if (
$debug) {
        echo 
"++ THIS MONTH'S CALENDAR [ start stamp // end date // end stamp // end date // tz offset (end) ] ++";
        
dmp($ts_firstdate('Y-m-d H:i:s'$ts_first), $ts_lastdate('Y-m-d H:i:s'$ts_last), $ts_lastoff);
    }
    
$extrasql $catSQL $secSQL $authSQL $fpSQL;

    switch(
$time) {
        case 
"any" : break;
        case 
"future" $extrasql .= " AND Posted > now()"; break;
        default : 
$extrasql .= " AND Posted < now()"; break; // The past
    
}

    
// Holidays are global 'exclusions', either defined directly or in a txp:variable
    
$holidays do_list($holidays);
    
$txphols do_list($holidays[0], ":");
    if (
$txphols[0] == "txpvar") {
        
$holidays do_list($variable[$txphols[1]]);
    }
    
// Force each holiday to a known format. Holidays without years use current year
    
foreach ($holidays as $key => $val) {
        if (empty(
$val)) continue;
        
$numparts preg_match('/^([\w]+).?([\w]+).?([\w]+)?$/'$val$parts);

        if (
$numparts) {
            if (
count($parts) == 3) {
                
$parts[3] = $year;
         }
            
$val str_pad($parts[1], 2'0'STR_PAD_LEFT).'-'.str_pad($parts[2], 2'0'STR_PAD_LEFT).'-'.$parts[3];
        }
        
$holidays[$key] = date("d-M-Y"safe_strtotime($val));
    }

    if (
$debug && !empty($holidays) && $holidays[0] != '') {
        echo 
"++ HOLIDAYS ++ ";
        
dmp($holidays);
    }

    
// Get all matching articles in (and before) this month
    
$events = array();
    
$uposted_field = (empty($datefields[0])) ? 'uPosted' "UNIX_TIMESTAMP($datefields[0])";
    
$sql2 $stati " HAVING $uposted_field <= ".$ts_lastoff$expired $extrasql ." ORDER BY Posted ASC";
    
$grabCols '*, unix_timestamp(Posted) as uPosted, unix_timestamp(LastMod) as uLastMod, unix_timestamp(Expires) as uExpires';
    
$evlist safe_rows($grabCols'textpattern'$sql2$debug);
    
article_push();

    
// If any events recur and fall within the current month, add those as well
    // If any dates are to be excluded, the entry is skipped UNLESS showskipped indicates otherwise
    
foreach ($evlist as $row) {
        
$idx 0// In case the 1st day of the month is a continuation of an event from the end of the previous month
        
$start = (!empty($datefields[0]) && !empty($row[$datefields[0]]) && ($stdt strtotime($row[$datefields[0]])) !== false) ? $stdt $row['uPosted'] + tz_offset($row['uPosted']);
        
$start_date date("Y-m-d"$start); // For recurring/spanned events on a minical, this is the event the cell links to
        
$real_end = (isset($datefields[1]) && !empty($row[$datefields[1]]) && ($endt strtotime($row[$datefields[1]])) !== false) ? $endt : (($row['uExpires']==0) ? $row['uExpires'] + tz_offset($row['uExpires']));

        
// If end < start the user-specified dates cannot be trusted
        
if ($real_end != && $real_end <= $start) {
            
$start $row['uPosted'] + tz_offset($row['uPosted']);
            
$real_end $row['uExpires'] + tz_offset($row['uExpires']);
            
trigger_error('Expiry cannot be before start date in "'.$row['Title'].'": ignored'E_USER_WARNING);
        }

        
$end = ($real_end != && $real_end $ts_last) ? $real_end $ts_last;
        
$real_diff = ($real_end==0) ? $real_end $start;
        
$real_end_month = ($real_end==0) ? date('m'$real_end);
        
$real_end_year = ($real_end==0) ? date('Y'$real_end);
        
$fake_diff strtotime(date("Y-M-d"$real_end) . " 23:59:59");
        
$diff = ($real_end==0) ? $fake_diff $start;
        
$smd_cal_flag = array();
        
$smd_cal_ucls = array();

        
$ev_month date('m'$start);
        
$ev_year date('Y'$start);
        
$ev_hr date('H'$start);
        
$ev_mn date('i'$start);
        
$ev_sc date('s'$start);

        if (
$debug 1) {
            echo 
'++ EVENT START // END // (if non-zero) REAL END ++';
            
dmp(date('d-M-Y H:i:s'$start) .' // 'date('d-M-Y H:i:s'$end) .' // '. ( ($real_end == 0) ? '' date('d-M-Y H:i:s'$real_end) ));
            
dmp($row['Title']);
            if (
$debug 2) {
                
dmp($row);
            }
        }

        
$multi = (($end $start) && ($real_end $start) && ($real_end $ts_first) && (date("d-m-Y"$real_end) != date("d-m-Y"$start))) ? true false;
        
$recur = (empty($row[$stepfield])) ? false true;
        
$hol_hit in_array(date("d-M-Y"$start), $holidays);
        
$evclasses = array();
        foreach (
$eventclasses as $evcls) {
            switch (
$evcls) {
                case 
"":
                    break;
                case 
"gcat":
                    if (isset(
$pretext['c']) && !empty($pretext['c'])) {
                        
$evclasses[] = $evc_pfx.$pretext['c'];
                    }
                    break;
                case 
"category":
                    if (isset(
$row['Category1']) && !empty($row['Category1'])) {
                        
$evclasses[] = $evc_pfx.$row['Category1'];
                    }
                    if (isset(
$row['Category2']) && !empty($row['Category2'])) {
                        
$evclasses[] = $evc_pfx.$row['Category2'];
                    }
                    break;
                case 
"section":
                    if (isset(
$pretext['s']) && !empty($pretext['s'])) {
                        
$evclasses[] = $evc_pfx.$pretext['s'];
                    }
                    break;
                case 
"author":
                    if (isset(
$pretext['author']) && !empty($pretext['author'])) {
                        
$evclasses[] = $evc_pfx.$pretext['author'];
                    }
                    break;
                default:
                    if (isset(
$row[$evcls]) && !empty($row[$evcls])) {
                        
$evclasses[] = $evc_pfx.$row[$evcls];
                    }
                    break;
            }
        }
        
$ignore = array();
        
$omit = array();
        
$cflag = array();

        if (
$debug && $evclasses) {
            echo 
'++ EVENT CLASSES ++';
            
dmp($evclasses);
        }

        
// Events that start or are added this month
        
if (($start $end) && ($start $ts_first)) {
            
populateArticleData($row);

            
// a standard event or start of a multi
            
if ($showspanned && $multi && !$recur) {
                
$smd_cal_flag[] = 'multifirst';
            }
            if (
$recur) {
                
$smd_cal_flag[] = 'recurfirst';
            }
            if (!
$smd_cal_flag) {
                
$smd_cal_flag[] = 'standard';
            }
            if ( ( 
$hol_hit && !in_array('multi',$holidayflags) && in_array('multifirst',$smd_cal_flag) ) || ( $hol_hit && !in_array('standard',$holidayflags) && in_array('standard',$smd_cal_flag) ) ) {
                
$smd_cal_flag[] = 'cancel';
            }
            foreach (
$smd_cal_flag as $item) {
                
$cflag[] = $cls_pfx.$item;
            }

            
$idx $smd_date['d'] = (int)strftime('%d'$start);
            
$smd_date['w'] = strftime(smd_cal_reformat_win('%V'$start), $start);
            
$smd_date['iy'] = strftime(smd_cal_reformat_win('%G'$start), $start);
            
$use_posted in_array('standard'$linkposted);

            
$op = ($thing) ? parse($thing) : (($form) ? parse_form($form) : (($size=="small") ? smd_cal_minilink($row$idx$month$year$use_posted) : href($row['Title'], permlinkurl($row), ' title="'.$row['Title'].'"')) );
            
$events[$idx][] = array('ev' => $op'evid' => $row['ID'], 'flag' => $smd_cal_flag'classes' => array_merge($cflag$smd_cal_ucls$evclasses), 'posted' => $start_date);
            
$smd_cal_flag = array();
            
$cflag = array();
            
$smd_cal_ucls = array();
            
$use_posted '';
        }

        
// Generate a skip array for this event
        
if ($skipfield && $row[$skipfield] != '') {
            
$ignores do_list($row[$skipfield]);
            foreach (
$ignores as $val) {
                
$igrng smd_expand_daterange($val$start$end);
                foreach (
$igrng as $theval) {
                    
$ignore[] = date("d-M-Y"$theval); // Force each date to a known format
                
}
            }
        }
        
// Generate an omit array for this event
        
if ($omitfield && $row[$omitfield] != '') {
            
$omits do_list($row[$omitfield]);
            foreach (
$omits as $val) {
                
$omrng smd_expand_daterange($val$start$end);
                foreach (
$omrng as $theval) {
                    
$omit[] = date("d-M-Y"$theval);
                }
            }
        }
        if (
$debug && ($ignore || $omit)) {
            echo 
'++ OMITTED DATES ++';
            
dmp($omit);
            echo 
'++ CANCELLED DATES ++';
            
dmp($ignore);
        }
        
// Calculate the date offsets and check recurring events that fall within the month of interest
        
if ($stepfield && $row[$stepfield] != '') {
            
$freq do_list($row[$stepfield]);
            
$stampoff = (int)(3600*$ev_hr) + (int)(60*$ev_mn) + (int)$ev_sc;
            foreach (
$freq as $interval) {
                
$max_loop 99999// Yuk, but practically limitless
                
$origerval $interval;
                
$interval str_replace("?month"date('F'mktime(0,0,0,$month,1)), $interval);
                
$interval str_replace("?year"$year$interval);
                if (
strpos($interval"last") === 0) {
                    
$interval date("l, F jS Y"strtotime$intervalmktime(1200date("n"mktime(0,0,0,$month,1,$year))+11$year) ));
                    
$max_loop 1;
                } else if (
strpos($interval"first") === 0) {
                    
$interval date("l, F jS Y"strtotime$intervalmktime(1200, (($month>1) ? $month-12), date("t"mktime(0,0,0,$month-1,1,(($month==1) ? $year-1$year))), (($month==1) ? $year-1$year)) ));
                    
$max_loop 1;
                } else if (
strpos($interval"this") === 0) {
                    
$max_loop 1;
                }
                
$ts_loop 0;
                
$ts_curr $start;
                if (
strpos($origerval"?month") || strpos($origerval"?year")) {
                    
$max_loop 1;
            }

//              $rng = smd_expand_daterange($interval);
//dmp($interval, $rng);
                
while ($ts_curr $end && $ts_loop $max_loop) {
                    if (
$max_loop == 1) {
                        
$ts_curr strtotime($interval);
                        
$ts_curr = ($ts_curr $start || $ts_curr $end) ? $start $ts_curr;
                    } else {
                        
$ts_curr strtotime($interval$ts_curr);
                    }
                    if (
$ts_curr === false) {
                        
$ts_loop++;
                        break;
                    } else {
                        if (
$debug 1) {
                            
dmp("INTERVAL: "date('d-M-Y H:i:s'$ts_curr+$stampoff));
                        }
                        if (
$ts_curr $end && $ts_curr >= $ts_first && $ts_curr != $start) {
                            
// A recurring event. Check it isn't a holiday or to be ignored
                            
populateArticleData($row);
                            
$op '';
                            
$idx = (int)strftime('%d'$ts_curr);
                            
$smd_cal_flag[] = 'recur';
                            
$thisdate date("d-M-Y"$ts_curr);
                            
$omit_me in_array($thisdate$omit);
                            
$show_me = !in_array($thisdate$ignore);
                            
$hol_hit in_array($thisdate$holidays);
                            
$show_hol = ($hol_hit && !in_array('recur',$holidayflags) ) ? false true;
                            
$use_posted smd_cal_in_array(array('recur''recurfirst'), $linkposted);

                            if ( 
$omit_me ) {
                                
$smd_cal_flag[] = 'omit';
                            }
                            if ( (!
$show_me || !$show_hol) && !$omit_me ) {
                                
$smd_cal_flag[] = 'cancel';
                            }
                            foreach (
$smd_cal_flag as $item) {
                                
$cflag[] = $cls_pfx.$item;
                            }

                            
// Create the events that appear in the cell but only if they've not appeared before, or are to be ignored/omitted
                            
if (!$omit_me) {
                                if ((
$show_me && $show_hol) || $showskipped) {
                                    
$smd_date['d'] = $idx;
                                    
$smd_date['w'] = strftime(smd_cal_reformat_win('%V'$ts_curr), $ts_curr);
                                    
$smd_date['iy'] = strftime(smd_cal_reformat_win('%G'$ts_curr), $ts_curr);
                                    
$op = ($recurform) ? parse_form($recurform) : (($thing) ? parse($thing) : (($size=="small") ? smd_cal_minilink($row$idx$month$year$use_posted) : href($row['Title'], permlinkurl($row), ' title="'.$row['Title'].'"')) );
                                }
                            }
                            
$used = array();
                            if (isset(
$events[$idx]) && $events[$idx] != NULL) {
                                foreach (
$events[$idx] as $ev) {
                                    
$used[] = $ev['ev'];
                                }
                            }
                            if (isset(
$events[$idx]) && $events[$idx] == NULL || !in_array($op$used)) {
                                
$events[$idx][] = array('ev' => $op'evid' => $row['ID'], 'flag' => $smd_cal_flag'classes' => array_merge($cflag$smd_cal_ucls$evclasses), 'posted' => $start_date);
                            }
                            
$smd_cal_flag = array();
                            
$cflag = array();
                            
$smd_cal_ucls = array();
                            
$use_posted '';
                        }
                        
$ts_loop++;
                    }
                }
            }
        } else if (
$showspanned && $multi) {
            
// Non-recurring events may span more than one date but they must still respect ignored dates and holidays
            
populateArticleData($row);
            
$lastday = (int)strftime('%d'$end);
            
$real_lastday = (int)strftime('%d'$real_end);
            while (++
$idx <= $lastday) {
                
$op '';
                
$multiflag = (($year==$real_end_year) && ($month==$real_end_month) && ($idx==$real_lastday)) ? 'multilast' : (($idx==1) ? 'multiprev' 'multi');
                
$smd_cal_flag[] = $multiflag;
                
$thistime mktime(000$month$idx$year);
                
$thisdate date("d-M-Y"$thistime);
                
$omit_me in_array($thisdate$omit);
                
$show_me = !in_array($thisdate$ignore);
                
$hol_hit in_array($thisdate$holidays);
                
$show_hol = ($hol_hit && !in_array('multi',$holidayflags) ) ? false true;
                
$use_posted smd_cal_in_array(array('multi''multifirst''multilast''multiprev'), $linkposted);
                if ( 
$omit_me ) {
                    
$smd_cal_flag[] = 'omit';
                }
                if ( (!
$show_me || !$show_hol) && !$omit_me ) {
                    
$smd_cal_flag[] = 'cancel';
                }
                foreach (
$smd_cal_flag as $item) {
                    
$cflag[] = $cls_pfx.$item;
                }
                
// Create the spanned event that appears in the cell
                
if (!$omit_me) {
                    if ( (
$show_me && $show_hol) || $showskipped) {
                        
$smd_date['d'] = $idx;
                        
$smd_date['w'] = strftime(smd_cal_reformat_win('%V'$thistime), $thistime);
                        
$smd_date['iy'] = strftime(smd_cal_reformat_win('%G'$thistime), $thistime);
                        
$op = ($spanform) ? parse_form($spanform) : (($thing) ? parse($thing) : (($size=="small") ? smd_cal_minilink($row$idx$month$year$use_posted) : href('&rarr;'permlinkurl($row), ' title="'.$row['Title'].'"')) );
                    }
                }
                
$events[$idx][] = array('ev' => $op'evid' => $row['ID'], 'flag' => $smd_cal_flag'classes' => array_merge($cflag$smd_cal_ucls$evclasses), 'posted' => $start_date);
                
$smd_cal_flag = array();
                
$cflag = array();
                
$smd_cal_ucls = array();
                
$use_posted '';
            }
        }
        
// Add any extra dates for this event that are within the current month
        
if ($extrafield && $row[$extrafield] != '') {
            
$xtra do_list($row[$extrafield]);
            
$ev_hr date('H'$start);
            
$ev_mn date('i'$start);
            
$ev_sc date('s'$start);
            
$stampoff = (int)(3600*$ev_hr) + (int)(60*$ev_mn) + (int)$ev_sc;
            foreach (
$xtra as $val) {
                if (
strpos($val"+") === false) {
                    
$exrng smd_expand_daterange($val);
                    
$val date("Y-m-d"$exrng[0]);
                    
$spidth count($exrng);
                    
$spex 0;
                } else {
                    
$chk $showspanned && !$recur;
                    
$spidth $chk ceil($diff / (60*60*24)) : 1// days between dates
                    
$val rtrim($val'+');
                    
$spex $chk 0;
                }

                for (
$jdx 1$jdx <= $spidth$jdx++) {
                    
$tm safe_strtotime($val . (($jdx==1) ? '' '+'.($jdx-1).' days'));
                    if (
$diff && $jdx == 1) {
                        
$expstamp $tm+$stampoff+$real_diff;
                    }
                    
$idx $smd_date['d'] = (int)strftime('%d'$tm);
                    
$dt date("Y-m-d"$tm);
                    
$lst = ($extrastrict) ? $end $ts_last;
                    if (
$tm $lst && $tm >= $ts_first) {
                        
$fakerow $row;
                        
$fakerow['Posted'] = date("Y-m-d H:i:s"$tm+$stampoff);
                        
$fakerow['uPosted'] = $tm+$stampoff;
                        if (
$diff>0) {
                            
$fakerow['Expires'] = date("Y-m-d H:i:s"$expstamp);
                            
$fakerow['uExpires'] = $expstamp;
                        }

                        
populateArticleData($fakerow);
                        
$smd_cal_flag[] = 'extra';
                        
$cflag[] = $cls_pfx.'extra';
                        
$omit_me false;
                        
$show_me $show_hol true;
                        if (
$spex) {
                            
$multiflag = ($jdx==1) ? 'multifirst' : (($jdx==$spidth) ? 'multilast' : (($idx==1) ? 'multiprev' 'multi'));
                            
$thisdate date("d-M-Y"$tm);
                            
$omit_me in_array($thisdate$omit);
                            
$show_me = !in_array($thisdate$ignore);
                            
$hol_hit in_array($thisdate$holidays);
                            
$show_hol = ($hol_hit && !in_array('multi',$holidayflags) ) ? false true;
                            
$use_posted in_array('extra'$linkposted);
                            if (
$omit_me) {
                                
$smd_cal_flag[] = 'omit';
                            }
                            if ( (!
$show_me || !$show_hol) && !$omit_me ) {
                                
$smd_cal_flag[] = 'cancel';
                            }
                            
$smd_cal_flag[] = $multiflag;
                            
$cflag[] = $cls_pfx.$multiflag;
                        }
                        if (!
$omit_me) {
                            if ( (
$show_me && $show_hol) || $showskipped) {
                                
$smd_date['w'] = strftime(smd_cal_reformat_win('%V'$tm), $tm);
                                
$smd_date['iy'] = strftime(smd_cal_reformat_win('%G'$tm), $tm);
                                
$op = ($spex && $spanform) ? parse_form($spanform) : (($thing) ? parse($thing) : (($form) ? parse_form($form) : (($size=="small") ? smd_cal_minilink($row$idx$month$year$use_posted) : href((($spex && $jdx>1) ? '&rarr;' $row['Title']), permlinkurl($row), ' title="'.$row['Title'].'"')) ));
                                
$events[$idx][] = array('ev' => $op'evid' => $row['ID'], 'flag' => $smd_cal_flag'classes' => array_merge($cflag$smd_cal_ucls$evclasses), 'posted' => $dt);
                                
$smd_cal_flag = array();
                                
$cflag = array();
                                
$smd_cal_ucls = array();
                                
$use_posted '';
                            }
                        }
                    }
                }
            }
        }
    }
    
article_pop();

    if (
$debug && $events) {
        echo 
'++ ALL EVENTS ++';
        
dmp($events);
    }

    
// Generate the calendar
    
$calendar = new SMD_Calendar($size$year$month$events$section$category$debug);
    
$calendar->setWeek($week);
    
$calendar->setFirstDayOfWeek($firstday);
    
$calendar->setGMT($gmt);
    
$calendar->setLang($lang);
    
$calendar->setClassLevels($clevs);
    
$calendar->setClassPrefix($cls_pfx);
    
$calendar->setEventWraptag($eventwraptag);
    
$calendar->setCellForm($cellform);
    
$calendar->setHdrForm($headerform);
    
$calendar->setMYWraptag($mywraptag);
    
$calendar->setSummary($summary);
    
$calendar->setCaption($caption);
    
$calendar->setTableID($id);
    
$calendar->setTableClass($class);
    
$calendar->setRowClass($rowclass);
    
$calendar->setCellClass($cellclass);
    
$calendar->setEmptyClass($emptyclass);
    
$calendar->setISOWeekClass($isoweekclass);
    
$calendar->setNavInfo($navpclass,$navnclass,$navparr,$navnarr,$navid);
    
$calendar->setNavKeep($maintain);
    
$calendar->setMYClass($myclass);
    
$calendar->setNameFormat($dayformat"d");
    
$calendar->setNameFormat($monthformat"m");
    
$calendar->setRemap($dmap);
    
$calendar->setShowISOWeek($isoweeks);
    
$calendar->setEYear($earliest);
    
$calendar->setLYear($latest);
    
$calendar->setFilterOpts($fopts);
    
$calendar->setDelim($event_delim);
    
$calendar->setHolidays($holidays);
    
$calendar->setSelectors(do_list($select), $selectbtn);

    return 
$calendar->display($static);
}

class 
SMD_Calendar extends SMD_Raw_Calendar
{
    
// Override Constructor
    // Permits multiple events to show per day
    
var $section '';
    var 
$category '';
    var 
$size '';
    var 
$debug 0;
    var 
$events = array();

    public function 
SMD_Calendar($size$year$month$events$section$category$debug 0)
    {
        
$this->debug $debug;
        
$this->section $section;
        
$this->category $category;
        
$this->events $events;
        
$this->size $size;
        
$this->smd_Raw_Calendar($year,$month,$debug);
    }

    
// Override dspDayCell to display stuff right
    
function dspDayCell($theday)
    {
        global 
$smd_cal_flag$smd_calinfo$smd_cal_ucls$smd_date$permlink_mode;

        
$smd_cal_flag = array();
        
$smd_cal_ucls = array();
        
$tdclass = array();
        
$hasarticle = isset($this->events[$theday]);
        
$now time() + tz_offset();

        
$thedate mktime(000$this->month$theday$this->year);
        
$hol_hit in_array(date("d-M-Y"$thedate), $this->holidays);
        if (
$hasarticle) {
            
$smd_cal_flag[] = 'event';
        }
        if (
$hol_hit) {
            
$smd_cal_flag[] = 'hols';
        }
        
$cflag = array();
        foreach (
$smd_cal_flag as $item) {
            
$cflag[] = $this->cls_pfx.$item;
        }

        if (
$this->cellclass) {
            
$tdclass[] = $this->cellclass;
        }
        
$tdclass array_merge($tdclass$cflag);
        
$runningclass = (in_array("cell"$this->cls_lev) || in_array("cellplus"$this->cls_lev)) ? $tdclass : array();

        if(
$this->year == date('Y',$now) and $this->month == date('n',$now) and $theday == date('j',$now) ) {
            
$smd_cal_flag[] = 'today';
            
$runningclass[] = $this->cls_pfx.'today';
        }

        
$out = array();
        
$flags = array();
        
$evid = array();
        
$fout = array('standard'=>array(),'recur'=>array(),'recurfirst'=>array(),'multifirst'=>array(),'multi'=>array(),'multiprev'=>array(),'multilast'=>array(),'cancel'=>array(),'extra'=>array());
        if (empty(
$this->cellform) && $this->size == 'large') {
            
$out[] = hed($theday,4);
        }

        
$evcnt 0;
        if( isset(
$this->events[$theday]) ) {
            
$days_events $this->events[$theday];
            foreach(
$days_events as $ev) {
                
$evclass $ev['classes'];
                
$evid[] = $ev['evid'];
                
$flags array_merge($flags$ev['flag']);
                if (
in_array("cellplus"$this->cls_lev)) {
                    
$runningclass array_merge($runningclass$evclass);
                }
                
$cls = ($evclass && in_array("event"$this->cls_lev)) ? ' class="'.join(' '$evclass).'"' '';
                
$op = ($this->evwraptag) ? tag($ev['ev'], $this->evwraptag$cls) : $ev['ev'];
                foreach (
$ev['flag'] as $flev) {
                    
$fout[$flev][] = $op;
                }
                
$out[] = $op;
                
$evcnt++;
                if (
$this->size == 'small' && $evcnt == 1) {
                    break;
                }
            }
        } elseif (
$this->size == 'small') {
            
$out[] = hed($theday,4);
        }

        
// Amalgamate the event-level classes and cell-level classes if required
        
$runningclass array_unique($runningclass);
        if (
in_array("cellplus"$this->cls_lev)) {
            
$smd_cal_flag array_merge($smd_cal_flag$flags);
        }

        if (
$this->cellform) {
            
$thistime mktime(000$this->month$theday$this->year);
            
$smd_calinfo['id'] = $this->tableID;
            
$smd_date['y'] = $this->year;
            
$smd_date['m'] = $this->month;
            
$smd_date['w'] = strftime(smd_cal_reformat_win('%V'$thistime), $thistime);
            
$smd_date['iy'] = strftime(smd_cal_reformat_win('%G'$thistime), $thistime);
            
$smd_date['d'] = $theday;
            
$reps = array(
                
'{evid}' => join($this->event_delim$evid),
                
'{standard}' => join('',$fout['standard']),
                
'{recur}' => join('',$fout['recur']),
                
'{recurfirst}' => join('',$fout['recurfirst']),
                
'{allrecur}' => join('',array_merge($fout['recur'], $fout['recurfirst'])),
                
'{multifirst}' => join('',$fout['multifirst']),
                
'{multiprev}' => join('',$fout['multiprev']),
                
'{multi}' => join('',$fout['multilast']),
                
'{multilast}' => join('',$fout['multilast']),
                
'{allmulti}' => join('',array_merge($fout['multifirst'],$fout['multi'],$fout['multiprev'],$fout['multilast'])),
                
'{cancel}' => join('',$fout['cancel']),
                
'{extra}' => join('',$fout['extra']),
                
'{events}' => join('',$out),
                
'{numevents}' => $evcnt,
                
'{day}' => $theday,
                
'{dayzeros}' => str_pad($theday2'0'STR_PAD_LEFT),
                
'{weekday}' => ((is_array($this->dayNameFmt)) ? $this->dayNames[date('w',$thistime)] : strftime($this->dayNameFmt$thistime)),
                
'{weekdayabbr}' => strftime('%a'$thistime),
                
'{weekdayfull}' => strftime('%A'$thistime),
                
'{week}' => $smd_date['w'],
                
'{month}' => $this->month,
                
'{monthzeros}' => str_pad($this->month2'0'STR_PAD_LEFT),
                
'{monthname}' => ((is_array($this->mthNameFmt)) ? $this->mthNames[date('n',$thistime)] : strftime($this->mthNameFmt$thistime)),
                
'{monthnameabbr}' => strftime('%b'$thistime),
                
'{monthnamefull}' => strftime('%B'$thistime),
                
'{year}' => $this->year,
                
'{shortyear}' => strftime('%y'$thistime),
                
'{isoyear}' => $smd_date['iy'],
                
'{shortisoyear}' => strftime(smd_cal_reformat_win('%g'$thistime), $thistime),
            );
            
$cellout parse(strtr($this->cellform$reps));
            
$carray array_merge($runningclass$smd_cal_ucls);
            
$smd_cal_ucls = array();

            return 
doTag($cellout,'td',join(' ',$carray));
        } else {
            return 
doTag(join('',$out),'td',join(' ',$runningclass));
        }
    }

    function 
display($static false)
    {
        
$sum = ($this->tblSummary) ? ' summary="'.$this->tblSummary.'"' '';
        
$id = ($this->tableID) ? ' id="'.$this->tableID.'"' '';
        
$c[] = ($this->tblCaption) ? '<caption>'.$this->tblCaption.'</caption>' '';
        
$c[] = '<thead>';
        
$c[] = $this->dspHeader($static);
        
$c[] = $this->dspDayNames();
        
$c[] = '</thead>';
        
$c[] = $this->dspDayCells();

        return 
doTag(join('',$c),'table',$this->tableclass,$sum.$id);
    }

    function 
dspHeader($static)
    {
        global 
$pretext$smd_calinfo$permlink_mode;

        
$currmo $this->month;
        
$curryr $this->year;
        
$navpclass $this->getNavInfo("pc");
        
$navnclass $this->getNavInfo("nc");
        
$navparrow $this->getNavInfo("pa");
        
$navnarrow $this->getNavInfo("na");
        
$navid $this->getNavInfo("id");
        
$navpclass = ($navpclass) ? ' class="'.$navpclass.'"' '';
        
$navnclass = ($navnclass) ? ' class="'.$navnclass.'"' '';
        
$fopts $this->fopts;

        
$sec = (isset($smd_calinfo['s']) && !empty($smd_calinfo['s'])) ? $smd_calinfo['s'] : '';
        foreach (
$this->maintain as $col) {
            switch (
$col) {
                case 
"section":
                    if (
$pretext['s'] && $permlink_mode != 'year_month_day_title') {
                        
$fopts = array('s' => $pretext['s']) + $fopts;
                    }
                    break;
                case 
"article":
                    if (
$pretext['id']) {
                        
$fopts = array('id' => $pretext['id']) + $fopts;
                    }
                    break;
                case 
"category":
                    if (
$pretext['c']) {
                        
$fopts = array('c' => $pretext['c']) + $fopts;
                    }
                    break;
                case 
"author":
                    if (
gps('author')) {
                        
$fopts = array('author' => gps('author')) + $fopts;
                    }
                    break;
                case 
"date":
                    if (
gps('date')) {
                        
$fopts = array('date' => gps('date')) + $fopts;
                    }
                    break;
                case 
"pg":
                    if (
$pretext['pg']) {
                        
$fopts = array('pg' => $pretext['pg']) + $fopts;
                    }
                    break;
                case 
"calid":
                    if (
$this->tableID) {
                        
$fopts = array('calid' => $this->tableID) + $fopts;
                    }
                    break;
                default:
                    if (
gps($col)) {
                        
$fopts = array($col => gps($col)) + $fopts;
                    }
                    break;
            }
        }

        
$fopts array_unique($fopts);
        
$filters = array();
        
$filterHid = array();
        if (!
$static) {
            foreach(
$fopts as $key => $val) {
                
$filters[] = $key.'='.$val;
                
$filterHid[] = hInput($key$val);
            }
        }

        
// Week select list
        
if ($this->useSelector('week') && !$static) {
            
$currwk = ($this->week) ? $this->week date('W'safe_strtotime($curryr."-".$currmo."-1 12:00"));
            for ( 
$idx 1$idx <= 53$idx++ ) {
                
$tagatts ' value="'.$idx.'"';
                if ( 
$idx == $currwk $tagatts .= ' selected="selected"';
                
$optiontags[] = doTag($this->selpfx['week'].str_pad($idx2'0'STR_PAD_LEFT).$this->selsfx['week'], 'option'''$tagatts);
            }
            
$selector[] = doTag(join(n$optiontags), 'select', (($this->mywraptag) ? '' $this->myclass), ' name="'.$this->remap['w'].'"'.(($this->selbtn) ? '' ' onchange="this.form.submit()"'), '')
                . ((
$this->useSelector('year')) ? '' hInput($this->remap['y'], $curryr));
            
$optiontags = array(); // Blank out
        
}

        
// Month select list - note mktime has the day forced to 1. If not you get
        // bizarre repeated month names on the 31st of some months :-\
        
if (!$this->useSelector('week')) {
            if (
$this->useSelector('month') && !$static) {
                for ( 
$idx 1$idx <= 12$idx++ ) {
                    
$tagatts ' value="'.$idx.'"';
                    if ( 
$idx == $currmo $tagatts .= ' selected="selected"';
                    
$optiontags[] = doTag($this->selpfx['month'].((is_array($this->mthNameFmt)) ? $this->mthNames[date('n',mktime(12,0,0,$idx,1))] : safe_strftime($this->mthNameFmtmktime(12,0,0,$idx,1) )).$this->selsfx['month'], 'option'''$tagatts);
                }
                
$selector[] = doTag(join(n$optiontags), 'select', (($this->mywraptag) ? '' $this->myclass), ' name="'.$this->remap['m'].'"'.(($this->selbtn) ? '' ' onchange="this.form.submit()"'), '')
                    . ((
$this->useSelector('year')) ? '' hInput($this->remap['y'], $curryr));
                
$optiontags = array(); // Blank out
            
} else {
                
$selector[] = doTag($this->getMonthName(), 'span', (($this->mywraptag) ? '' $this->myclass));
            }
        }

        
// Year select list
        
$y0 $this->eyr;
        
$y1 $this->lyr;
        if (
$this->useSelector('year') && ($y0 != $y1) && !$static) {
            for ( 
$idx $y0$idx <= $y1$idx++ ) {
                
$tagatts ' value="'.$idx.'"';
                if ( 
$idx == $curryr $tagatts .= ' selected="selected"';
                
$optiontags[] = doTag($this->selpfx['year'].$idx.$this->selsfx['year'], 'option'''$tagatts);
            }
            
$selector[] = doTag(join(n$optiontags), 'select', (($this->mywraptag) ? '' $this->myclass), ' name="'.$this->remap['y'].'"'.(($this->selbtn) ? '' ' onchange="this.form.submit()"'), '')
                    . ((
$this->useSelector('month') || $this->useSelector('week')) ? '' hInput($this->remap['m'], $currmo));
        } else {
            
$selector[] = doTag($curryr'span', (($this->mywraptag) ? '' $this->myclass));
        }

        
$request serverSet('REQUEST_URI');
        
$redirect serverSet('REDIRECT_URL');
        if (!empty(
$redirect) && ($request != $redirect) && is_callable('_l10n_set_browse_language')) {
            
// MLP in da house: use the redirect URL instead
            
$request $redirect;
        }
        
$urlp parse_url($request);
        
$action $urlp['path'];

        if (
$permlink_mode == 'messy') {
            
$out makeOut('id','s','c','q','pg','p','month');
            foreach(
$out as $key => $val) {
                if (
$val) {
                    
$filters[] = $key.'='.$val;
                    
$filterHid[] = hInput($key$val);
                }
            }
        }
        
$filterHid array_unique($filterHid);
        
$filters array_unique($filters);

        
$extras '';
        if (!
$static && ( $this->useSelector('month') || $this->useSelector('year') )) {
            if (
$this->selbtn) {
                
$extras .= doTag('''input''smd_cal_input'' type="submit" value="'.$this->selbtn.'"');
            }
            
$extras .= join(n$filterHid);
        }

        
$selector '<form action="'.$action.'" method="get"'.(($navid) ? ' id="'.$navid.'"' '').'>'.doTag(join(sp$selector).$extras$this->mywraptag$this->myclass).'</form>';

        
$nav_back_link $this->navigation($curryr$currmo'-'$filters$urlp['path']);
        
$nav_fwd_link  $this->navigation($curryr$currmo'+'$filters$urlp['path']);

        
$nav_back = (!$static && $nav_back_link) ? '<a href="'.$nav_back_link.'"'.$navpclass.'>'.$navparrow.'</a>' '&nbsp;';
        
$nav_fwd  = (!$static && $nav_fwd_link) ? '<a href="'.$nav_fwd_link.'"'.$navnclass.'>'.$navnarrow.'</a>' '&nbsp;';

        
$c[] = doTag($nav_back,'th');
        
$c[] = '<th colspan="'.(($this->showISOWeek) ? 5).'">'.$selector.'</th>';
        
$c[] = doTag($nav_fwd,'th');

        return 
doTag(join('',$c),'tr''smd_cal_navrow');
    }

    function 
navigation($year$month$direction$flt$url '')
    {
        global 
$permlink_mode;

        if(
$direction == '-') {
            if(
$month 1) {
                
$month 12;
                
$year -= 1;
            } else {
                
$month -= 1;
            }
        } else {
            if(
$month 12) {
                
$month 1;
                
$year += 1;
            } else {
                
$month += 1;
            }
        }

        
// Abort if we're about to go out of range
        
if ($year $this->eyr || $year $this->lyr) {
            return 
'';
        }

        
$flt[] = $this->remap['m']."=$month";
        
$flt[] = $this->remap['y']."=$year";

        return 
$url "?" join(a$flt);
    }
}

/**
 * Basic Calendar data and display
 * http://www.oscarm.org/static/pg/calendarClass/
 * @author Oscar Merida
 * @created Jan 18 2004
 */
class SMD_Raw_Calendar
{
    var 
$gmt 1$lang$debug 0;
    var 
$year$eyr$lyr$month$week;
    var 
$dayNameFmt$mthNameFmt$dayNames$mthNames$startDay$endDay$firstDayOfWeek 0$startOffset 0;
    var 
$selectors$selbtn$selpfx$selsfx;
    var 
$showISOWeek$ISOWeekHead$ISOWeekCell;
    var 
$cls_lev$cls_pfx$fopts;
    var 
$evwraptag$mywraptag;
    var 
$rowclass$cellclass$emptyclass$isoclass$myclass$tableID$tblSummary$tblCaption;
    var 
$navpclass$navnclass$navparrow$navnarrow$navid;
    var 
$holidays$cellform$hdrform$maintain$remap;
    var 
$event_delim;

    
/**
     * Constructor
     *
     * @param integer, year
     * @param integer, month
     * @return object
     */
    
public function SMD_Raw_Calendar($yr$mo$debug 0)
    {
        
$this->setDebug($debug);
        
$this->setYear($yr);
        
$this->setMonth($mo);
        
$this->setClassPrefix('smd_cal_');

        
$this->startTime strtotime"$yr-$mo-01 00:00" );
        
$this->startDay date'D'$this->startTime );
        
$this->endDay date't'$this->startTime );
        
$this->endTime strtotime"$yr-$mo-".$this->endDay." 23:59:59" );
        if (
$this->debug) {
            echo 
"++ THIS MONTH'S RENDERED CALENDAR [ start stamp // end date // start day // end stamp // end date // end day number ] ++";
            
dmp($this->startTimedate('Y-m-d H:i:s'$this->startTime), $this->startDay$this->endTimedate('Y-m-d H:i:s'$this->endTime), $this->endDay);
        }
        
$this->setNameFormat('%a''d');
        
$this->setNameFormat('%B''m');
        
$this->setFirstDayOfWeek(0);
        
$this->setShowISOWeek('');
        
$this->setTableID('');
        
$this->setTableClass('');
    }

    function 
useSelector($val)
    {
        return 
in_array($val$this->selectors);
    }

    function 
getDayName($day)
    {
        return (
$this->dayNames[$day%7]);
    }

    function 
getMonthName()
    {
        if (
is_array($this->mthNameFmt)) {
            return 
$this->mthNames[date('n',$this->startTime)];
        } else {
            return 
strftime($this->mthNameFmt$this->startTime);
        }
    }

    function 
getNavInfo($type)
    {
        
$r '';
        switch (
$type) {
            case 
"id"$r $this->navid; break;
            case 
"pc"$r $this->navpclass; break;
            case 
"nc"$r $this->navnclass; break;
            case 
"pa"$r $this->navparrow; break;
            case 
"na"$r $this->navnarrow; break;
        }
        return 
$r;
    }

    function 
setDebug($d)
    {
        
$this->debug $d;
    }

    function 
setGMT($b)
    {
        
$this->gmt $b;
    }

    function 
setLang($code)
    {
        
$this->lang $code;
    }

    function 
setSummary($txt)
    {
        
$this->tblSummary $txt;
    }

    function 
setCaption($txt)
    {
        
$this->tblCaption $txt;
    }

    function 
setCellForm($frm)
    {
        
$this->cellform $frm;
    }

    function 
setHdrForm($frm)
    {
        
$this->hdrform $frm;
    }

    function 
setTableID($id)
    {
        
$this->tableID $id;
    }

    function 
setYear($yr)
    {
        
$this->year $yr;
    }

    function 
setEYear($yr)
    {
        
$this->eyr $yr;
    }

    function 
setLYear($yr)
    {
        
$this->lyr $yr;
    }

    function 
setMonth($mth)
    {
        
$this->month = (int)$mth;
    }

    function 
setWeek($wk)
    {
        if (
$wk) {
            
$wk str_pad($wk2'0'STR_PAD_LEFT);
            
$this->week $wk;
            
$this->month safe_strftime("%m"strtotime($this->year."W".$wk));
        }
    }

    function 
setNavKeep($ar)
    {
        
$this->maintain $ar;
    }

    function 
setShowISOWeek($val)
    {
        
$this->showISOWeek = ($val) ? true false;
        if (
$val) {
            
$val do_list($val);
            
$this->ISOWeekHead $val[0];
            
$this->ISOWeekCell = (isset($val[1])) ? $val[1] : '{week}';
        }
    }

    function 
setRemap($map)
    {
        
$this->remap $map;
    }

    function 
setClassLevels($cls)
    {
        
$this->cls_lev $cls;
    }

    function 
setClassPrefix($cls)
    {
        
$this->cls_pfx $cls;
    }

    function 
setEventWraptag($wrap)
    {
        
$this->evwraptag $wrap;
    }

    function 
setMYWraptag($wrap)
    {
        
$this->mywraptag $wrap;
    }

    function 
setTableClass($cls)
    {
        
$this->tableclass = ($cls) ? $this->cls_pfx.$cls '';
    }

    function 
setRowClass($cls)
    {
        
$this->rowclass = ($cls) ? $this->cls_pfx.$cls '';
    }

    function 
setCellClass($cls)
    {
        
$this->cellclass = ($cls) ? $this->cls_pfx.$cls '';
    }

    function 
setEmptyClass($cls)
    {
        
$this->emptyclass = ($cls) ? $this->cls_pfx.$cls '';
    }

    function 
setISOWeekClass($cls)
    {
        
$this->isoclass = ($cls) ? $this->cls_pfx.$cls '';
    }

    function 
setDelim($dlm)
    {
        
$this->event_delim $dlm;
    }

    function 
setNavInfo($clsp$clsn$arrp$arrn$nid)
    {
        
$this->navpclass = ($clsp) ? $this->cls_pfx.$clsp '';
        
$this->navnclass = ($clsn) ? $this->cls_pfx.$clsn '';
        
$this->navparrow = ($arrp) ? $arrp '';
        
$this->navnarrow = ($arrn) ? $arrn '';
        
$this->navid = ($nid) ? $this->cls_pfx.$nid '';
    }

    function 
setMYClass($cls)
    {
        
$this->myclass = ($cls) ? $this->cls_pfx.$cls '';
    }

    function 
setFilterOpts($f)
    {
        
$this->fopts $f;
    }

    function 
setHolidays($hols)
    {
        
$this->holidays $hols;
    }

    function 
setSelectors($sel$btn)
    {
        foreach (
$sel as $idx => $item) {
            
$selparts explode(":"$item);
            
$sel[$idx] = $selparts[0];
            
$this->selpfx[$selparts[0]] = (isset($selparts[1])) ? $selparts[1] : '';
            
$this->selsfx[$selparts[0]] = (isset($selparts[2])) ? $selparts[2] : '';
        }
        
$this->selectors $sel;
        
$this->selbtn $btn;
    }

    function 
setFirstDayOfWeek($d)
    {
        
$this->firstDayOfWeek = ((int)$d <= and (int)$d >= 0) ? (int)$d 0;
        
$this->startOffset date('w'$this->startTime) - $this->firstDayOfWeek;
        if ( 
$this->startOffset ) {
            
$this->startOffset abs($this->startOffset);
        }
    }

    
/**
     *
     * frm: any valid PHP strftime() string or ABBR/FULL
     * typ: d to set day, m to set month format
     */
    
function setNameFormat($frm$typ "d")
    {
        switch (
$frm) {
            case 
"full":
            case 
"FULL":
                
$fmt = ($typ == 'd') ? "%A" "%B";
                break;
            case 
"abbr":
            case 
"ABBR":
                
$fmt = ($typ == 'd') ? "%a" "%b";
                break;
            default:
                if (
strpos($frm'%') === 0) {
                    
$fmt $frm;
                } else {
                    
$frm trim($frm'{}');
                    
$frm do_list($frm);
                    
$fmt $frm;
                }
                break;
        }

        if (
$typ == "d") {
            
$this->dayNameFmt $fmt;
            
$this->dayNames = array();

            
// This is done to make sure Sunday is always the first day of our array
            
$start 0;
            
$end $start 7;
            
$sunday strtotime('1970-Jan-04 12:00:00');

            for(
$i=$start$i<$end$i++) {
                if (
is_array($fmt)) {
                    
$this->dayNames[] = $fmt[$i-$start];
                } else {
                    
$this->dayNames[] = ucfirst(strftime($fmt, ($sunday + (86400*$i))));
                }
            }
        } else {
            
$this->mthNameFmt $fmt;
            
$this->mthNames = array();
            for (
$i=0$i<12$i++) {
                if (
is_array($fmt)) {
                    
$this->mthNames[$i+1] = $fmt[$i];
                }
            }
        }
    }

    
/**
     * Return markup for displaying the calendar.
     *
     * @return
     * @public
     */
    
function display()
    {
        
$id = ($this->tableID) ? ' id="'.$this->tableID.'"' '';
        
$c[] = '<table'.$id.'>';
        
$c[] = '<thead>' $this->dspDayNames() . '</thead>';
        
$c[] = $this->dspDayCells();
        
$c[] = '</table>';

        return 
join('',$c);
    }

    
/**
     * Displays the row of day names.
     *
     * @return string
     * @private
     */
    
function dspDayNames()
    {
        if (
$this->hdrform) {
            
$reps = array(
                
'{firstday}' => $this->firstDayOfWeek,
                
'{daynames}' => join(','$this->dayNames),
                
'{isoweekhead}' => $this->ISOWeekHead,
                
'{week}' => date('W'$this->startTime),
                
'{month}' => date('n'$this->startTime),
                
'{year}' => date('Y'$this->startTime),
                
'{isoyear}' => date('o'$this->startTime),
            );

            return 
parse(strtr($this->hdrform$reps));
        } else {
            
$c[] = '<tr class="smd_cal_daynames">';

            
$i $this->firstDayOfWeek;
            
$j 0// count number of days displayed
            
$end false;

            if (
$this->showISOWeek) {
                
$c[] = "<th>".$this->ISOWeekHead."</th>";
            }
            for(
$j 0$j<=6$j++, $i++) {
                if(
$i == 7) { $i 0; }
                
$c[] = '<th>'.$this->getDayName($i)."</th>";
            }

            
$c[] = '</tr>';
            return 
join('',$c);
        }
    }

    
/**
     * Displays all day cells for the month.
     *
     * @return string
     * @private
     */
    
function dspDayCells()
    {
        
$i 0// cell counter
        
$emptyClass $this->emptyclass;
        
$isoClass $this->isoclass;
        
$rowClass $this->rowclass;
        
$rowClass = ($rowClass) ? ' class="'.$rowClass.'"' '';

        
$c[] = '<tr'.$rowClass.'>';

        if (
$this->showISOWeek) {
            
$reps = array(
                
'{week}' => date('W'$this->startTime),
                
'{month}' => date('n'$this->startTime),
                
'{year}' => date('Y'$this->startTime),
                
'{isoyear}' => date('o'$this->startTime),
            );
            
$wkcell strtr($this->ISOWeekCell$reps);
            
$c[] = '<td class="'.$isoClass.'">'.$wkcell.'</td>';
        }
        
// first display empty cells based on what weekday the month starts in
        
for( $j=0$j<$this->startOffset$j++ )    {
            
$i++;
            
$c[] = '<td class="'.$emptyClass.'">&nbsp;</td>';
        } 
// end offset cells

        // write out the rest of the days, at each sunday, start a new row.
        
for( $d=1$d<=$this->endDay$d++ ) {
            
$i++;
            
$c[] = $this->dspDayCell$d );
            if ( 
$i%== ) { $c[] = '</tr>'; }
            if ( 
$d<$this->endDay && $i%== ) {
                
$c[] = '<tr'.$rowClass.'>';
                if (
$this->showISOWeek) {
                    
// **Not** using safe_strtotime() here to cater for an operating timezone that differs from the server timezone.
                    // Probably should do this in other places too but no bugs have been filed yet so it can be done on a
                    // case-by-case basis
                    
$theTime strtotime($this->year."-".$this->month."-".(int)($d 1) ." 00:00");
                    
$reps = array(
                        
'{week}' => date('W'$theTime),
                        
'{month}' => date('n'$theTime),
                        
'{year}' => date('Y'$theTime),
                        
'{isoyear}' => date('o'$theTime),
                    );
                    
$wkcell strtr($this->ISOWeekCell$reps);
                    
$c[] = '<td class="'.$isoClass.'">'.$wkcell.'</td>';
                }
            }
        }
        
// fill in the final row
        
$left - ( $i%);
        if ( 
$left 7) {
            for ( 
$j=0$j<$left$j++ )    {
              
$c[] = '<td class="'.$emptyClass.'">&nbsp;</td>';
            }
            
$c[] = "\n\t</tr>";
        }
        return 
'<tbody>' join('',$c) . '</tbody>';
    }

    
/**
     * Outputs the contents for a given day.
     *
     * @param integer, day
     * @abstract
     */
    
function dspDayCell($day)
    {
        return 
'<td>'.$day.'</td>';
    }
}

function 
smd_cal_minilink($row$day$month$year$use_posted false)
{
    global 
$permlink_mode;

    
$lang '';
    
$request serverSet('REQUEST_URI');
    
$redirect serverSet('REDIRECT_URL');
    if (!empty(
$redirect) && ($request != $redirect) && is_callable('_l10n_set_browse_language')) {
        
// MLP in da house so extract the language currently in use -- is there an MLP-native method for this?
        
$reqparts explode('/'$request);
        
$redparts explode('/'$redirect);
        
$lang join(''array_diff($redparts$reqparts)) . '/';
    }

    if( 
$permlink_mode == 'year_month_day_title' ) {
        
$linkdate = ($use_posted) ? date('Y/m/d'$row['uPosted']) : $year.'/'.str_pad($month,2,"0",STR_PAD_LEFT).'/'.str_pad($day,2,"0",STR_PAD_LEFT);
        
$href ' href="'.hu.$lang.$linkdate.'"';
    } else {
        
$linkdate = ($use_posted) ? date('Y-m-d'$row['uPosted']) : $year.'-'.str_pad($month,2,"0",STR_PAD_LEFT).'-'.str_pad($day,2,"0",STR_PAD_LEFT);
        
$href ' href="'.hu.$lang.'?date='.$linkdate;
        if(
$row['Section']) { $href $href.a.'s='.$row['Section']; }
//      if($category) { $href = $href.a.'c='.$category; }
        
$href .= '"';
    }

    return 
tag($day'a'$href);
}

// Perform one of two types of test: a flag-based test, or an info-based test
function smd_if_cal($atts$thing)
{
    global 
$smd_cal_flag$smd_calinfo$smd_date;

    
extract(lAtts(array(
        
'flag'    => '',
        
'calid'   => '',
        
'isoyear' => '',
        
'year'    => '',
        
'month'   => '',
        
'week'    => '',
        
'day'     => '',
        
'logic'   => 'or',
        
'debug'   => '0',
    ), 
$atts));

    
$flag do_list($flag);
    
$ctr $num 0;

    if (
$debug) {
        
dmp($atts);
    }

    if (
$flag && $flag[0] != '') {
        
$num += count($flag);
        foreach (
$flag as $whatnot) {
            if (empty(
$whatnot)) continue;
            
$ctr += (in_array($whatnot$smd_cal_flag) || ($whatnot == 'SMD_ANY' && !empty($smd_cal_flag))) ? 0;
        }
    }
    if (
$calid) {
        
$num++;
        
$ctr += ($smd_calinfo['id'] === $calid) ? 0;
    }
    foreach (array(
"iy" => "isoyear""y" => "year""m" => "month""w" => "week""d" => "day") as $idx => $test) {
        
$tester = $$test;
        
$compare $smd_date[$idx];

        if (
$tester) {
            
$num++;
            
preg_match('/([!=<>]+)?([]+)/'$tester$matches);
            if (
$debug) {
                
dmp("TEST IF: "$compare. (($matches[1]) ? $matches[1] : '=') . $matches[2] );
            }
            switch (
$matches[1]) {
                case 
"!":
                    
$ctr += ($compare!=$matches[2]) ? 0;
                    break;
                case 
">":
                    
$ctr += ($compare>$matches[2]) ? 0;
                    break;
                case 
">=":
                    
$ctr += ($compare>=$matches[2]) ? 0;
                    break;
                case 
"<":
                    
$ctr += ($compare<$matches[2]) ? 0;
                    break;
                case 
"<=":
                    
$ctr += ($compare<=$matches[2]) ? 0;
                    break;
                default:
                    
$ctr += ($compare==$matches[2]) ? 0;
                    break;
            }
        }
    }
    
$result = (($ctr === $num && $logic == "and") || $ctr && $logic == "or") ? true false;
    return 
parse(EvalElse($thing$result));
}

// Convenient wrapper for smd_cal_info use="event"
function smd_event_info($atts)
{
    
$atts['use'] = 'event';
    return 
smd_cal_info($atts);
}

// Grab additional information about the current event
function smd_cal_info($atts)
{
    global 
$pretext$thisarticle$smd_cal_flag$smd_calinfo$smd_date$smd_eventinfo;

    
extract(lAtts(array(
        
'type'        => 'flag',
        
'join'        => ' ',
        
'join_prefix' => 'SMD_AUTO',
        
'html'        => 0,
        
'escape'      => 'html',
        
'use'         => 'cal'// 'cal' for calendar (uses $smd_calinfo) or 'event' for event lists (uses $smd_eventinfo). Not publically alterable
        
'debug'       => 0,
    ), 
$atts));

    
// Validate $use attribute
    
$use = (in_array($use, array('cal''event'))) ? $use 'cal';
    
$cal_global = ${'smd_'.$use.'info'};

    if (
$debug && $thisarticle) {
        echo 
'++ Event name ++';
        
dmp($thisarticle['title']);
    }
    if (
$debug && $cal_global) {
        echo 
'++ Available '.$use.' info ++';
        
dmp($cal_global);
    }

    if (
$debug && $smd_date) {
        echo 
'++ Available date info ++';
        
dmp($smd_date);
    }

    if (
$debug && $smd_cal_flag) {
        echo 
'++ Available flag info ++';
        
dmp($smd_cal_flag);
    }

    
// Type: 0=date, 1=smd_cal/eventinfo, 2=pretext, 3=thisarticle, 4(or other)=user value
    
$map = array(
        
'year' => array(0'y'),
        
'isoyear' => array(0'iy''y'),
        
'month' => array(0'm'),
        
'week' => array(0'w'),
        
'day' => array(0'd'),
        
'section' => array(3'''s'),
        
'category1' => array(3'''c'),
        
'category2' => array(3'''c'),
        
'thisid' => array(3'thisid''id'),
        
'article' => array(1'artid''id'),
        
'calid' => array(1'id''calid'),
        
'category' => array(2'c'),
        
'realname' => array(2'author'),
    );
    
$join = ($html) ? $join// html mode forces ampersand join
    
$type do_list($type);
    
$ret = array();
    foreach (
$type as $item) {
        
$pts do_list($item':');
        
$item $pts[0];

        if (empty(
$item)) continue;
        
// Default html id
        
$hid = (isset($map[$item])) ? ((isset($map[$item][2])) ? $map[$item][2] : $map[$item][1]) : $item;
        
// User-specified htmlid overrides it
        
$hid = (count($pts) > && !empty($pts[1])) ? $pts[1] : $hid;
        if (
$item == "flag") {
            
$ret[] = (($join_prefix=="SMD_AUTO") ? $join '').join($join$smd_cal_flag);
        } else if (
$item == "author" || $item == "realname") {
            
$currauthor = ($thisarticle == NULL) ? '' author(array());
            if (
$currauthor) {
                
$ret[] = (($html) ? $hid.'=' '') . $currauthor;
            }
        } else if (
$item == "s") {
            
$sec = (!empty($pretext['s'])) ? $pretext['s'] : ((isset($cal_global['s']) && !empty($cal_global['s'])) ? $cal_global['s'] : '');
            if (
$sec) {
                
$ret[] = (($html) ? $hid.'=' '') . $sec;
            }
        } else if (isset(
$map[$item])) {
            
$typ $map[$item][0];
            
$idx = empty($map[$item][1]) ? $item $map[$item][1];
            switch (
$typ) {
                case 
0:
                    if (
$smd_date[$idx]) {
                        
$ret[] = (($html) ? $hid.'=' '') . $smd_date[$idx];
                    }
                    break;
                case 
1:
                    if (!empty(
$cal_global[$idx])) {
                        
$ret[] = (($html) ? $hid.'=' '') . $cal_global[$idx];
                    }
                    break;
                case 
2:
                    if (!empty(
$pretext[$idx])) {
                        
$ret[] = (($html) ? $hid.'=' '') . $pretext[$idx];
                    }
                    break;
                case 
3:
                    if (
$thisarticle != NULL && isset($thisarticle[$idx]) && !empty($thisarticle[$idx])) {
                        
$ret[] = (($html) ? $hid.'=' '') . $thisarticle[$idx];
                    }
                    break;
            }
        } else if (
array_key_exists($item$pretext)) {
            if (
$pretext[$item]) {
                
$ret[] = (($html) ? $hid.'=' '') . $pretext[$item];
            }
        } else if (isset(
$cal_global[$item])) {
            if (!empty(
$cal_global[$item])) {
                
$ret[] = (($html) ? $hid.'=' '') . $cal_global[$item];
            }
        } else {
            if (
$thisarticle != NULL && isset($thisarticle[$item]) && !empty($thisarticle[$item])) {
                
$ret[] = (($html) ? $hid.'=' '') . $thisarticle[$item];
            }
        }
    }
//    $ret = array_unique($ret);
    
$out = (($join_prefix=="SMD_AUTO") ? (($html) ? '?' '') : $join_prefix).join($join$ret);
    return (
$escape=='html') ? htmlspecialchars($out) : $out;
}

// Return a formatted timestamp, with optional 'time now' override
function smd_cal_now($atts)
{
    global 
$dateformat;

    
extract(lAtts(array(
        
'format' => $dateformat,
        
'now'    => '',
        
'offset' => '',
        
'gmt'    => '',
        
'lang'   => '',
    ), 
$atts));

    
$theDay = (gps('d') && is_numeric(gps('d'))) ? (int)gps('d') : safe_strftime('%d');
    
$theMonth = (gps('m') && is_numeric(gps('m'))) ? (int)gps('m') : safe_strftime('%m');
    
$theYear = (gps('y') && is_numeric(gps('y'))) ? (int)gps('y') : safe_strftime('%Y');
    if (
$now) {
        
$now str_replace("?month"date('F'mktime(12,0,0,$theMonth,$theDay,$theYear)), $now);
        
$now str_replace("?year"$theYear$now);
        
$now str_replace("?day"$theDay$now);
        
$now is_numeric($now) ? $now strtotime($now);
    } else {
        
$now time();
    }

    if (
$offset) {
        
$now strtotime($offset$now);
    }

    
$format smd_cal_reformat_win($format$now);
    return 
safe_strftime($format$now$gmt$lang);
}

// Set user-defined classes for a cell
function smd_cal_class($atts)
{
    global 
$smd_cal_ucls;

    
extract(lAtts(array(
        
'name' => '',
    ), 
$atts));

    
$name do_list($name);
    
$smd_cal_ucls array_merge($smd_cal_ucls$name);
}

// <txp:article_custom /> replacement(ish) tag that understands how to handle recurring events
function smd_article_event($atts$thing null)
{
    global 
$prefs$pretext$thispage$thisarticle$smd_eventinfo$smd_cal_flag$smd_date;

    
extract(lAtts(array(
        
'time'        => 'any',
        
'type'        => 'standard,recur,multi',
        
'expired'     => '',
        
'id'          => '',
        
'category'    => null,
        
'section'     => '',
        
'author'      => '',
        
'realname'    => '',
        
'custom'      => '',
        
'status'      => 'live',
        
'param_delim' => ':',
        
'sort'        => '',
        
'form'        => '',
        
'stepfield'   => '',
        
'skipfield'   => '',
        
'omitfield'   => '',
        
'extrafield'  => '',
        
'allspanned'  => '0',
        
'datefields'  => '',
        
'month'       => '',
        
'from'        => '',
        
'to'          => '',
        
'offset'      => 0,
        
'limit'       => '10',
        
'eventlimit'  => '10',
        
'paging'      => '1',
        
'pageby'      => '',
        
'pgonly'      => '',
        
'wraptag'     => '',
        
'break'       => '',
        
'class'       => '',
        
'debug'       => 0,
    ), 
$atts));

    
// Phase 1 filters
    
$filtSQL = array();
    
$subSQL = array();
    if (
$category !== null) {
        
$uncats false;
        
$allcats do_list($category);
        if ((
$pos array_search('SMD_UNCAT'$allcats)) !== false) {
            
$uncats true;
            unset(
$allcats[$pos]);
            
$category join(','$allcats);
        }
        
$tmp doQuote(join("','"doSlash(do_list($category))));
        
$filtSQL[] = ($uncats "(Category1 = '' AND Category2 = '')" '') .
            (
$uncats && $allcats " OR " '') .
            (
$allcats '( Category1 IN ('.$tmp.') OR Category2 IN ('.$tmp.') )' '');
    }
    if(
$section) {
        
$filtSQL[] = 'Section IN ('.doQuote(join("','"doSlash(do_list($section)))).')';
    }
    if(
$realname) {
        
$authors safe_column('name''txp_users''RealName IN ('doQuote(join("','"doArray(do_list($realname), 'urldecode'))) .')' );
        
$author join(','$authors);
    }
    if(
$author) {
        
$filtSQL[] = 'AuthorID IN ('.doQuote(join("','"doSlash(do_list($author)))).')';
    }
    if(
$id) {
        
$filtSQL[] = 'ID IN ('.join(','array_map('intval'do_list($id))).')';
    }
    if(
$custom) {
        
$custs do_list($custom);
        
$validOps = array('=''!=''>''>=''<''<=''like''not''not like');
        foreach (
$custs as $set) {
            if (
strpos($set$param_delim) !== false) {
                
$clauseOpts do_list($set$param_delim);
                
$fld $clauseOpts[0];
                
$oper = ((count($clauseOpts) == 3) && (in_array(strtolower($clauseOpts[1]), $validOps))) ? $clauseOpts[1] : '=';
                
$clause = (count($clauseOpts) == 3) ? $clauseOpts[2] : ((count($clauseOpts) == 2) ? $clauseOpts[1] : '');
                
$filtSQL[] = $fld $oper " doQuote(doSlash($clause));
            }
        }
    }

    
$type do_list($type);
    
$pageby = (empty($pageby) ? $limit $pageby);

    foreach (
$type as $evtyp) {
        switch(
$evtyp) {
            case 
'standard':
                if (
$stepfield) {
                    
$subSQL[] = "(".$stepfield." = '' AND Expires IS NULL)";
                }
                break;
            case 
'recur':
                if (
$stepfield) {
                    
$subSQL[] = "(".$stepfield." != '')";
                }
                break;
            case 
'multi':
                if (
$stepfield) {
                    
$subSQL[] = "(".$stepfield." = '' AND Expires IS NOT NULL)";
                }
                break;
        }
    }
    if (
$subSQL) {
        
$filtSQL[] = '('.join(' OR '$subSQL).')';
    }

    
$status = ($status) ? $status 'live'// in case status has been emptied
    
$status do_list($status);
    
$stati = array();
    foreach (
$status as $stat) {
        if (empty(
$stat)) {
            continue;
        } else if (
is_numeric($stat)) {
            
$stati[] = $stat;
        } else {
            
$stati[] = getStatusNum($stat);
        }
    }
    
$filtSQL[] = 'Status IN ('.doQuote(join("','"$stati)).')';

    
$expired = ($expired) ? $expired $prefs['publish_expired_articles'];
    if (!
$expired) {
        
$filtSQL[] = '(now() <= Expires OR Expires IS NULL)';
    }

    
// Sorting rules: data is sorted once as it is extracted via SQL and then again after the fake dates have been inserted
    
$sort = (empty($sort)) ? 'Posted asc' $sort;
    
$sort do_list($sort);
    
$sortPrefix "SORT_";
    
$sortOrder = array();
    for (
$idx 0$idx count($sort); $idx++) {
        
$sorties explode(' '$sort[$idx]);
        if (
count($sorties) <= 1) {
            
$sorties[1] = "asc";
        }
        
$sorties[1] = $sortPrefix.(($sorties[1] == "desc") ? 'DESC' 'ASC');
        
$sortOrder[] = array("by" => $sorties[0], "dir" => $sorties[1]);
    }
    
$filtSQL join(' AND '$filtSQL);
    
$filtSQL .= ' ORDER BY '.join(',',doSlash($sort));

    
$grabCols '*, unix_timestamp(Posted) as uPosted, unix_timestamp(LastMod) as uLastMod, unix_timestamp(Expires) as uExpires';
    
$evlist safe_rows($grabCols'textpattern'$filtSQL$debug);

    if (
$debug>2) {
        echo 
"++ RECORD SET ++";
        
dmp($evlist);
    }
    
$all_evs = array();
    
$ev_tally = array();
    
$now time() + tz_offset();

    
$eventlimit do_list($eventlimit);
    if (
count($eventlimit) == 1) {
        
$eventlimit[1] = $eventlimit[0];
    }

    
$datefields do_list($datefields);

    
// Phase 2: expand any recurring dates and collate all events that fall within the alloted ranges
    
foreach ($evlist as $row) {
        
$ev_posted = (!empty($datefields[0]) && !empty($row[$datefields[0]]) && ($stdt strtotime($row[$datefields[0]])) !== false) ? $stdt $row['uPosted']+tz_offset($row['uPosted']);
        
$ev_expires = (isset($datefields[1]) && !empty($row[$datefields[1]]) && ($endt strtotime($row[$datefields[1]])) !== false) ? $endt : (($row['uExpires']==0) ? $row['uExpires']+tz_offset($row['uExpires']));

        
$skip = ($skipfield && $row[$skipfield] != '');
        
$omit = ($omitfield && $row[$omitfield] != '');
        
$recur = ($stepfield && $row[$stepfield] != '');
        
$extra = ($extrafield && $row[$extrafield] != '');
        
$multi = ($ev_expires $ev_posted && (date("d-m-Y"$ev_expires) != date("d-m-Y"$ev_posted))) ? true false;

        
// If end < start the user-specified dates cannot be trusted
        
if ($ev_expires != && $ev_expires <= $ev_posted) {
            
$ev_posted $row['uPosted']+tz_offset($row['uPosted']);
            
$ev_expires = (($row['uExpires']==0) ? $row['uExpires']+tz_offset($row['uExpires']));
            
trigger_error('Expiry cannot be before start date in "'.$row['Title'].'": ignored'E_USER_WARNING);
        }
        if (
$debug 1) {
            echo 
'++ EVENT START // END ++';
            
dmp($row['Title']);
            
dmp($ev_posteddate('Y-m-d H:i:s'$ev_posted), $ev_expiresdate('Y-m-d H:i:s'$ev_expires));
        }

        
// Rewrite the start/end dates in case they are user-defined
        
$row['uPosted'] = $ev_posted;
        
$row['Posted'] = date("Y-m-d H:i:s"$ev_posted);
        
$row['uExpires'] = $ev_expires;
        
$row['Expires'] = ($ev_expires==0) ? '0000-00-00 00:00:00' date("Y-m-d H:i:s"$ev_expires);

        
$diff = ($ev_expires == 0) ? $ev_expires $ev_posted;
        
$ev_month date('m'$ev_posted);
        
$ev_year date('Y'$ev_posted);
        
$ev_hr date('H'$ev_posted);
        
$ev_mn date('i'$ev_posted);
        
$ev_sc date('s'$ev_posted);
        
$ignore = array();

        
// Generate a skip array for this event
        
if ($skip) {
            
$ignores do_list($row[$skipfield]);
            foreach (
$ignores as $val) {
                
$igrng smd_expand_daterange($val$ev_posted$ev_expires);
                foreach (
$igrng as $theval) {
                    
$ignore[] = date("d-M-Y"$theval); // Force each date to a known format
                
}
            }
        }
        
// Append any omitted events
        
if ($omit) {
            
$omits do_list($row[$omitfield]);
            foreach (
$omits as $val) {
                
$omrng smd_expand_daterange($val$ev_posted$ev_expires);
                foreach (
$omrng as $theval) {
                    
$ignore[] = date("d-M-Y"$theval);
                }
            }
        }
        if (
$debug && $ignore) {
            echo 
'++ IGNORED DATES ++';
            
dmp($ignore);
        }

        
// Does the base event deserve to be in the results?
        
if (smd_include_event($ev_posted$now$ignore$time$from$to$month)) {
            
$all_evs[] = array('ev' => $row'flags' => ($multi ? array('multifirst') : array('standard')) );
            
$ev_tally[$row['uPosted']] = (isset($ev_tally[$row['uPosted']])) ? $ev_tally[$row['uPosted']]+1;
        }

        
// Add any extra dates for this event
        
if ($extra) {
            
$xtra do_list($row[$extrafield]);
            
$xtras = array();

            
// Make up an array of all extra dates
            
foreach ($xtra as $val) {
                if (
strpos($val"+") === false) {
                    
$exrng smd_expand_daterange($val);
                    
$xtras[] = date("Y-m-d"$exrng[0]);
                    
$spex 0;
                } else {
                    
$fake_diff safe_strtotime(date("Y-M-d"$ev_expires) . " 23:59:59");
                    
$fdiff = ($ev_expires==0) ? $fake_diff $ev_posted;
                    
$chk $allspanned && $multi && !$recur;
                    
$spidth $chk ceil($fdiff / (60*60*24)) : 1// days between dates
                    
$val rtrim($val'+');
                    for (
$jdx 1$jdx <= $spidth$jdx++) {
                        
$xtras[] = date("Y-m-d"safe_strtotime($val . (($jdx==1) ? '' '+'.($jdx-1).' days')));
                    }
                    
$spex $chk 0;
                }
            }

            
$xtras array_unique($xtras);

            
$stampoff = (int)(3600*$ev_hr) + (int)(60*$ev_mn) + (int)$ev_sc;
            foreach (
$xtras as $jdx => $val) {
                
$tm strtotime($val);
                
$flags = array('extra');

                
// No $ignore for additional events, as they always show up
                
if (smd_include_event($tm+$stampoff$now, array(), $time$from$to$month)) {
                    
$fakerow $row;
                    
$fakerow['Posted'] = date("Y-m-d H:i:s"$tm+$stampoff);
                    
$fakerow['uPosted'] = $tm+$stampoff;
                    if (
$diff 0) {
                        
$fakerow['Expires'] = date("Y-m-d H:i:s"$tm+$stampoff+$diff);
                        
$fakerow['uExpires'] = $tm+$stampoff+$diff;
                    }
                    if (
$spex) {
                        
$flags[] = ($jdx==0) ? 'multifirst' : (($jdx==$spidth-1) ? 'multilast' 'multi');
                    }
                    
$all_evs[] = array('ev' => $fakerow'flags' => $flags);
                    
$ev_tally[$fakerow['uPosted']] = (isset($ev_tally[$fakerow['uPosted']])) ? $ev_tally[$fakerow['uPosted']]+1;
                }
            }
        }

        if (
$recur) {
            
$flags = array('recurfirst');
            
$freq do_list($row[$stepfield]);
            
$monthly false;
            
$currmonth $ev_month;
            
$curryear $ev_year;
            foreach (
$freq as $interval) {
                
$fakerow $row;
                
$cstamp $ev_posted;
                for(
$idx 0$idx 99999$idx++) {
                    
$lstamp $cstamp;
                    if ((isset(
$ev_tally[$row['uPosted']]) && ($ev_tally[$row['uPosted']] >= $eventlimit[0])) || ($to && $cstamp safe_strtotime($to))) {
                        break;
                    }
                    
$ival str_replace("?month"date('F'mktime(0,0,0,$currmonth,1)), $interval);
                    
$ival str_replace("?year"$curryear$ival);

                    if (
strpos($ival"last") === 0) {
                        
$ival date("l, F jS Y"strtotime$ivalmktime(1200date("n"mktime(0,0,0,$currmonth,1,$curryear))+11$curryear) ));
                        
$monthly true;
                    } else if (
strpos($ival"first") === 0) {
                        
$ival date("l, F jS Y"strtotime$ivalmktime(1200, (($currmonth>1) ? $currmonth-12), date("t"mktime(0,0,0,$currmonth-1,1,(($currmonth==1) ? $curryear-1$curryear)) ), (($currmonth==1) ? $curryear-1$curryear)) ));
                        
$monthly true;
                    } else if (
strpos($ival"this") === 0) {
                        
$monthly true;
                    }
                    if (
strpos($interval"?month") || strpos($interval"?year")) {
                        
$monthly true;
                }

                    if (
$monthly) {
                        
$cstamp strtotime($ival);
                    } else {
                        
$cstamp strtotime($ival$cstamp);
                    }

                    
// This kludge takes account of timestamps like "last Thursday" (of the month). The last 'whatever day' of
                    // a month can only be a maximum of 31 days before the last timestamp we saw, so check for that (+/- 10 mins)
                    
$diffstamp $cstamp $lstamp;
                    if (
$diffstamp 0) {
                        if (
$diffstamp > -(60*60*24*31)+600) {
                            
$cstamp false// Some 'last weekday' of the previous month
                        
} else {
                            break; 
// PHP_INT_MAX exceeded
                        
}
                    }
                    if (
$cstamp !== false) {
                        if (
$debug 1) {
                            
dmp("INTERVAL: "$cstamp ' // ' .date('d-M-Y H:i:s'$cstamp));
                        }

                        if ((
$cstamp $ev_expires || $ev_expires == '0') && ($cstamp != $ev_posted)) {
                            
$show_me smd_include_event($cstamp$now$ignore$time$from$to$month);
                            if (
$show_me) {
                                
$flags[] = 'recur';
                                
$fakerow['Posted'] = date("Y-m-d H:i:s"$cstamp);
                                
$fakerow['uPosted'] = $cstamp;
                                
$all_evs[] = array('ev' => $fakerow'flags' => $flags);
                                
$ev_tally[$row['uPosted']] = (isset($ev_tally[$row['uPosted']])) ? $ev_tally[$row['uPosted']]+1;
                                
$flags = array(); // reset so recurfirst is removed
                            
}
                        } else {
                            break;
                        }
                    }
                    
// Increment the month/year ready for the next interval
                    
if ($monthly) {
                        
$curryear = ($currmonth==12) ? $curryear+$curryear;
                        
$currmonth = ($currmonth==12) ? $currmonth+1;
                    }
                }
                if (
$debug>1) {
                    if (isset(
$ev_tally[$row['uPosted']])) {
                        
dmp("TALLY: "$ev_tally[$row['uPosted']]);
                    }
                }
            }
        } else if (
$allspanned && date("Y-M-d"$ev_expires) != date("Y-M-d"$ev_posted)) {
            
$postdate date("Y-M-d H:i:s"$ev_posted);
            
$fake_diff safe_strtotime(date("Y-M-d"$ev_expires) . " 23:59:59");
            
$diff = ($ev_expires==0) ? $fake_diff $ev_posted;
            
$spidth ceil($diff / (60*60*24)); // days between dates
            
for ($jdx 1$jdx $spidth$jdx++) {
                
$flags = array();
                
$tm safe_strtotime($postdate.'+'.$jdx.' days');
                
$show_me smd_include_event($tm$now$ignore$time$from$to$month);
                if (
$show_me) {
                    
$flags[] = ($jdx==$spidth-1) ? 'multilast' 'multi';
                    
$fakerow $row;
                    
$fakerow['Posted'] = date("Y-m-d H:i:s"$tm);
                    
$fakerow['uPosted'] = $tm;
                    
$all_evs[] = array('ev' => $fakerow'flags' => $flags);
                }
            }
        }
    }

    if (
$debug>2) {
        echo 
"++ PRE-SORTED ++";
        
dmp($all_evs);
    }

    
// Make up an array_multisort arg list and execute it
    
foreach($all_evs as $key => $entry) {
        
$row $entry['ev'];
        foreach (
$row as $identifier => $item) {
            
$varname "col_".$identifier;
            ${
$varname}[$key] = $item;
        }
    }
    if(
count($all_evs) > 0) {
        for (
$idx 0$idx count($sortOrder); $idx++) {
            
$sortargs[] = '$col_'.$sortOrder[$idx]['by'];
            
$sortargs[] = $sortOrder[$idx]['dir'];
        }
        
$sortit 'array_multisort('.implode(", ",$sortargs).', $all_evs);';
        eval(
$sortit);
    }

    if (
$debug>2) {
        echo 
"++ POST-SORTED ++";
        
dmp($all_evs);
    }

    
// Handle paging
    
if ($paging) {
        
$grand_total count($all_evs);
        
$total $grand_total $offset;
        
$numPages ceil($total/$pageby);
        
$pg = (!$pretext['pg']) ? $pretext['pg'];
        
$pgoffset $offset + (($pg 1) * $pageby);
        
// send paging info to txp:newer and txp:older
        
$pageout['pg'] = $pg;
        
$pageout['numPages'] = $numPages;
        
$pageout['s'] = $pretext['s'];
        
$pageout['c'] = $pretext['c'];
        
$pageout['grand_total'] = $grand_total;
        
$pageout['total'] = $total;

        if (empty(
$thispage))
            
$thispage $pageout;
        if (
$pgonly)
            return;
    } else {
        
$pgoffset $offset;
    }

    
// Phase 3: iterate over the new array obeying any offset/limit. Anything in the range gets populated and parsed
    
$out = array();
    
$ctr 0;
    
article_push();
    
$lastposted 0;
    foreach (
$all_evs as $idx => $entry) {
        
$smd_cal_flag = array();
        
$smd_date = array();
        
$smd_eventinfo = array();

        if (
$idx >= $pgoffset && $ctr $limit) {
            
$row $entry['ev'];

            
$smd_cal_flag $entry['flags'];

            
$thisposted date('Y-m-d'$row['uPosted']);
            
$nextposted = isset($all_evs['ev'][$idx+1]) ? date('Y-m-d'$all_evs['ev'][$idx+1]['uPosted']) : 0;

            
// Adjust times so txp:posted/expires return correct stamps
            
$row['Posted'] = $row['Posted']-tz_offset(strtotime($row['Posted']));
            
$row['uPosted'] = $row['uPosted']-tz_offset($row['uPosted']);
            
$row['Expires'] = ($row['uExpires'] == 0) ? '0000-00-00 00:00:00' $row['Expires']-tz_offset(strtotime($row['Expires']));
            
$row['uExpires'] = ($row['uExpires'] == 0) ? $row['uExpires']-tz_offset($row['uExpires']);

            
// Populate additional event information
            
$fakestamp = ($row['uExpires'] == 0) ? strtotime(date("Y-M-d"$row['uExpires']) . " 23:59:59");
            
$smd_eventinfo['duration'] = ($row['uExpires'] == 0) ? $row['uExpires'] - $row['uPosted'];
            
$smd_eventinfo['durationdays'] = ($fakestamp) ? ceil(($fakestamp $row['uPosted']) / (60*60*24)) : 0;
            
$smd_date['y'] = strftime('%Y'$row['uPosted']);
            
$smd_date['m'] = strftime('%m'$row['uPosted']);
            
$smd_date['d'] = (int)strftime('%d'$row['uPosted']);
            
$smd_date['w'] = strftime(smd_cal_reformat_win('%V'$row['uPosted']), $row['uPosted']);
            
$smd_date['iy'] = strftime(smd_cal_reformat_win('%G'$row['uPosted']), $row['uPosted']);
            if (
$row['uExpires'] == 0) {
                
$smd_date['expy'] = $smd_date['expm'] = $smd_date['expd'] = $smd_date['expw'] = $smd_date['expiy'] = '';
            } else {
                
$smd_date['expy'] = strftime('%Y'$row['uExpires']);
                
$smd_date['expm'] = strftime('%m'$row['uExpires']);
                
$smd_date['expd'] = (int)strftime('%d'$row['uExpires']);
                
$smd_date['expw'] = strftime(smd_cal_reformat_win('%V'$row['uExpires']), $row['uExpires']);
                
$smd_date['expiy'] = strftime(smd_cal_reformat_win('%G'$row['uExpires']), $row['uExpires']);
            }
            
populateArticleData($row);
            
$thisarticle['is_first'] = ($thisposted != $lastposted);
            
$thisarticle['is_last'] = ($thisposted != $nextposted);
            
$lastposted $thisposted;
            
$out[] = ($thing) ? parse($thing) : (($form) ? parse_form($form) : href($row['Posted'], permlinkurl($row), ' title="'.$row['Title'].'"') );
            
$ctr++;
        }
    }
    
article_pop();
    return 
doWrap($out$wraptag$break$class);
}

// Try and output "nice" dates that read well across month/year boundaries.
// For example: 28 May - 05 Jun 2011 or May 11-16 2011, depending on format
function smd_event_duration($atts)
{
    
extract(lAtts(array(
        
'start'     => posted(array('format' => '%s')),
        
'end'       => expires(array('format' => '%s')),
        
'format'    => '%d %b %Y',
        
'separator' => ' &ndash; ',
        
'debug'     => 0,
    ), 
$atts));

    
// Extract the relevant portions of the format so we can muck about with them
    
preg_match_all('/\%([dejbBmhgGyY])/'$format$matches);

    
$indexes = array('day' => '''month' => '''year' => '');
    foreach(
$matches[1] as $idx => $token) {
        switch (
$token) {
            case 
'd':
            case 
'e':
            case 
'j':
                
$indexes['day'] = $idx;
                break;
            case 
'b':
            case 
'B':
            case 
'm':
            case 
'h':
                
$indexes['month'] = $idx;
                break;
            case 
'g':
            case 
'G':
            case 
'y':
            case 
'Y':
                
$indexes['year'] = $idx;
                break;
        }
    }

    
$day_first = ($indexes['day'] < $indexes['month']);
    
$year_first = ($indexes['year'] == 0);
    
$has_year false;

    if (
$end) {
        if (
strftime('%Y %m %d'$start) == strftime('%Y %m %d',$end)) {
             
// begin and end on same day
             
$s_format '';
             
$e_format $format;
             
$has_year true;
         } else {
            if (
strftime('%Y'$start) == strftime('%Y'$end)) {
                
// same year
                
if (strftime('%m'$start) == strftime('%m'$end)) {
                    
// and same month
                    
$re1 = ($day_first) ? '/\%[bBmhgGyY]/' '/\%[gGyY]/';
                    
$re2 = ($day_first) ? '/\%[gGyY]/' '/\%[bBmhgGyY]/';

                    
$s_format trim(preg_replace($re1''$format));
                    
$e_format = ($re2) ? trim(preg_replace($re2''$format)) : $format;
                } else {
                    
// not same month
                    
$s_format $e_format trim(preg_replace('/\%[gGyY]/'''$format));
                }
            } else {
                
// different year
                
$s_format $e_format $format;
                
$has_year true;
            }
            
$s_format .= $separator;
        }

        
// Add the year back in the correct position
        
$s_format = ($has_year) ? $s_format : (($year_first) ? $matches[0][$indexes['year']] . ' ' $s_format $s_format);
        
$e_format = ($has_year) ? $e_format : (($year_first) ? $e_format $e_format ' ' $matches[0][$indexes['year']]);
    } else {
        
$s_format $format;
        
$e_format ='';
    }

    return ((
$s_format) ? strftime($s_format$start) : '') . (($e_format) ? strftime($e_format$end) : '');
}

// An unoptimized workaround when "%V" and "%G" fails (usually on Windows)
// Algorithm adapted from http://www.personal.ecu.edu/mccartyr/ISOwdALG.txt with thanks.
// All other shortcut algorithms failed edge cases
function smd_cal_iso_week($format '%V'$time null)
{
    if (!
$time$time time();

    
$yr strftime("%Y"$time);
    
$leap = ( ( ($yr == 0) && ($yr 100 != 0) ) || $yr 400 == );
    
$leap_prev = ( ( (($yr-1) % == 0) && (($yr-1) % 100 != 0) ) || ($yr-1) % 400 == );
    
$day_of_year strftime('%j'$time);

    
// Find the weekday of Jan 1st in the given year
    
$yy = ($yr 1) % 100;
    
$c = ($yr 1) - $yy;
    
$g $yy + ($yy 4);
    
$jan1weekday + ((((($c 100) % 4) * 5) + $g) % 7);

    
// Find weekday ( could use: $weekday = strftime('%u', $time); )
    
$h $day_of_year + ($jan1weekday 1);
    
$weekday + ( ($h 1) % 7);

    
// Find if $time falls in iso_year Y-1, iso_week 52 or 53
    
if (($day_of_year <= ($jan1weekday)) && $jan1weekday 4) {
        
$iso_year $yr 1;
        if (
$jan1weekday == || ($jan1weekday == && $leap_prev)) {
            
$iso_week 53;
        } else {
            
$iso_week 52;
        }
   } else {
        
$iso_year $yr;
    }

    
// Find if $time falls in iso_year Y+1, iso_week 1
    
if ($iso_year == $yr) {
        
$idx = ($leap) ? 366 365;
        if ( (
$idx $day_of_year) < ($weekday) ) {
            
$iso_year $yr 1;
            
$iso_week 1;
        }
    }

    
// Find if $time falls in iso_year Y, iso_week 1 thru 53
    
if ($iso_year == $yr) {
        
$jdx $day_of_year + ($weekday) + ($jan1weekday 1);
        
$iso_week $jdx 7;
        if (
$jan1weekday 4) {
            
$iso_week--;
        }
    }

    
// Replacement array
    
$reps = array(
        
'%V' => str_pad($iso_week2'0'STR_PAD_LEFT),
        
'%G' => $iso_year,
        
'%g' => substr($iso_year2),
    );
    return 
strtr($format$reps);
}

// Adapted from: http://php.net/manual/en/function.strftime.php
function smd_cal_reformat_win($format$ts null)
{
    
// Only Win platforms need apply
    
if (!is_windows()) return $format;
    if (!
$ts$ts time();

    
$mapping = array(
        
'%C' => sprintf("%02d"date("Y"$ts) / 100),
        
'%D' => '%m/%d/%y',
        
'%e' => sprintf("%' 2d"date("j"$ts)),
        
'%F' => '%Y-%m-%d',
        
'%g' => smd_cal_iso_week('%g'$ts),
        
'%G' => smd_cal_iso_week('%G'$ts),
        
'%h' => '%b',
        
'%l' => sprintf("%' 2d"date("g"$ts)),
        
'%n' => "\n",
        
'%P' => date('a'$ts),
        
'%r' => date("h:i:s"$ts) . " %p",
        
'%R' => date("H:i"$ts),
        
'%s' => date('U'$ts),
        
'%t' => "\t",
        
'%T' => '%H:%M:%S',
        
'%u' => ($w date("w"$ts)) ? $w 7,
        
'%V' => smd_cal_iso_week('%V'$ts),
    );
    
$format str_replace(
        
array_keys($mapping),
        
array_values($mapping),
        
$format
    
);

    return 
$format;
}

// Find if the haystack contains one of the values in needle. Is there a cleverer way to do this?
function smd_cal_in_array($needle$haystack)
{
    foreach (
$haystack as $val) {
        if (
in_array($val$needle)) {
            return 
true;
        }
    }
    return 
false;
}

// Check the passed timestamp against every time restriction and return true if it passes them all
function smd_include_event($ts$now$ign$time$from$to$month)
{
    
$show = array();
    
$show[] = !in_array(date("d-M-Y"$ts), $ign);
    
$time do_list($time);
    
$showor false;
    foreach(
$time as $tm) {
        switch(
$tm) {
            case 
"any":
                
$showor true;
                break;
            case 
"future":
                
$showor $showor || (($ts $now) ? true false);
                break;
            case 
"today":
                
$showor $showor || (($ts >= strtotime(date('Y-m-d 00:00:00'$now)) && $ts <= strtotime(date('Y-m-d 23:59:59'$now))) ? true false);
                break;
            default :
                
$showor $showor || (($ts $now) ? true false);
                break;
        }
    }
    
$show[] = $showor;
    if (
$from) { $show[] = ($ts >= safe_strtotime($from)) ? true false; }
    if (
$to) { $show[] = ($ts <= safe_strtotime($to)) ? true false; }
    if (
$month) { $show[] = (date("Y-m"$ts) == $month) ? true false; }

    return (!
in_array(0$show)) ? true false;
}

// Convert date ranges like 24-Oct-08 => 5-Nov-08 to an array of discrete date entities
// Also, weekday vals such as {Sun:Mon:Wed} would return those days between $start and $end
function smd_expand_daterange($range$start ''$end ''$fmt '%s')
{
    
$out = array();
    
$rng do_list($range"=>");

    if (
count($rng) > 1) {
        
// Range expansion
        
$diff safe_strtotime($rng[1]) - safe_strtotime($rng[0]);
        
$diffdays ceil($diff / (60*60*24)); // days between dates
        
for ($jdx 0$jdx <= $diffdays$jdx++) {
            
$out[] = safe_strftime($fmtsafe_strtotime($rng[0] . (($jdx==0) ? '' '+'.$jdx.' days')));
        }
    } else if (
$start && $end && strpos($range'{') === && strpos($range'}') === strlen($range)-1) {
        
// Day of week expansion
        
$days do_list(trim($range,'{}'), ':');
        
$diffdays ceil(($end-$start) / (60*60*24));
        for (
$jdx 0$jdx <= $diffdays$jdx++) {
            
$tm $start + ($jdx*60*60*24);
            if (
in_array(date('D'$tm), $days)) {
                
$out[] = safe_strftime($fmt$tm);
            }
        }
    } else {
        
// Single date
        
$out[] = safe_strftime($fmtsafe_strtotime($rng[0]));
    }
    return 
$out;
}