PageRenderTime 97ms CodeModel.GetById 37ms RepoModel.GetById 0ms app.codeStats 1ms

/~enabled/smd_calendar.php

https://bitbucket.org/mrdale/txp-plugins
PHP | 3258 lines | 2916 code | 178 blank | 164 comment | 400 complexity | 9765c280f80ce3fb85716ea8c1d6be92 MD5 | raw file
  1. <?php
  2. // This is a PLUGIN TEMPLATE for Textpattern CMS.
  3. // Copy this file to a new name like abc_myplugin.php. Edit the code, then
  4. // run this file at the command line to produce a plugin for distribution:
  5. // $ php abc_myplugin.php > abc_myplugin-0.1.txt
  6. // Plugin name is optional. If unset, it will be extracted from the current
  7. // file name. Plugin names should start with a three letter prefix which is
  8. // unique and reserved for each plugin author ("abc" is just an example).
  9. // Uncomment and edit this line to override:
  10. $plugin['name'] = 'smd_calendar';
  11. // Allow raw HTML help, as opposed to Textile.
  12. // 0 = Plugin help is in Textile format, no raw HTML allowed (default).
  13. // 1 = Plugin help is in raw HTML. Not recommended.
  14. # $plugin['allow_html_help'] = 1;
  15. $plugin['version'] = '0.54';
  16. $plugin['author'] = 'Stef Dawson';
  17. $plugin['author_uri'] = 'http://stefdawson.com/';
  18. $plugin['description'] = 'Calendar / event / schedule system with events as Textpattern articles';
  19. // Plugin load order:
  20. // The default value of 5 would fit most plugins, while for instance comment
  21. // spam evaluators or URL redirectors would probably want to run earlier
  22. // (1...4) to prepare the environment for everything else that follows.
  23. // Values 6...9 should be considered for plugins which would work late.
  24. // This order is user-overrideable.
  25. $plugin['order'] = '5';
  26. // Plugin 'type' defines where the plugin is loaded
  27. // 0 = public : only on the public side of the website (default)
  28. // 1 = public+admin : on both the public and admin side
  29. // 2 = library : only when include_plugin() or require_plugin() is called
  30. // 3 = admin : only on the admin side (no AJAX)
  31. // 4 = admin+ajax : only on the admin side (AJAX supported)
  32. // 5 = public+admin+ajax : on both the public and admin side (AJAX supported)
  33. $plugin['type'] = '0';
  34. // Plugin "flags" signal the presence of optional capabilities to the core plugin loader.
  35. // Use an appropriately OR-ed combination of these flags.
  36. // The four high-order bits 0xf000 are available for this plugin's private use
  37. if (!defined('PLUGIN_HAS_PREFS')) define('PLUGIN_HAS_PREFS', 0x0001); // This plugin wants to receive "plugin_prefs.{$plugin['name']}" events
  38. if (!defined('PLUGIN_LIFECYCLE_NOTIFY')) define('PLUGIN_LIFECYCLE_NOTIFY', 0x0002); // This plugin wants to receive "plugin_lifecycle.{$plugin['name']}" events
  39. $plugin['flags'] = '0';
  40. // Plugin 'textpack' is optional. It provides i18n strings to be used in conjunction with gTxt().
  41. // Syntax:
  42. // ## arbitrary comment
  43. // #@event
  44. // #@language ISO-LANGUAGE-CODE
  45. // abc_string_name => Localized String
  46. /** Uncomment me, if you need a textpack
  47. $plugin['textpack'] = <<< EOT
  48. #@admin
  49. #@language en-gb
  50. abc_sample_string => Sample String
  51. abc_one_more => One more
  52. #@language de-de
  53. abc_sample_string => Beispieltext
  54. abc_one_more => Noch einer
  55. EOT;
  56. **/
  57. // End of textpack
  58. if (!defined('txpinterface'))
  59. @include_once('zem_tpl.php');
  60. # --- BEGIN PLUGIN CODE ---
  61. /**
  62. * smd_calendar
  63. *
  64. * A Textpattern CMS plugin for complete monthly event and calendar management
  65. * -> Originally based on mdp_calendar - thanks Marshall!
  66. * -> Full-size / mini calendar by month, with ISO week support
  67. * -> Txp articles are events. Future, past, and today's events are supported. Multi-day (spanned) events are based on article expiry
  68. * -> Next/prev month/year select list with customisable start/end years
  69. * -> Filter events by cat / section / author / status / time / expiry
  70. * -> Specify event frequency in custom field (1 week / 10 days / 3 months / etc)
  71. * -> Optionally (re)schedule / cancel / omit event dates
  72. * -> Customisable output for events and cells using multiple forms/container and classes
  73. * -> Conditional tags for building custom logic
  74. *
  75. * @author Stef Dawson
  76. * @link http://stefdawson.com/
  77. */
  78. // TODO:
  79. // * allow table header to be removed / restyled completely (the month/week dropdown & nav icons)
  80. // -- a form (navform?) for the header row with access to all vars such as which month is being displayed?
  81. // -- tools to allow the header to be generated from components and laid out in any manner?
  82. // * allow URL vars to be passed as POST (to bypass gbp_permlinks)
  83. // * add custom rows to the table (header, footer) -- header could be used to replace the current nav/dropdowns
  84. // * div-based calendar layout?
  85. // TOFIX:
  86. // * Expiry dates on extra+ allspanned dates in smd_article_event (and calendar?). They currently 'creep' a day for every day of a spanned event
  87. // * Ranges in stepfields (http://forum.textpattern.com/viewtopic.php?pid=254395#p254395 and http://forum.textpattern.com/viewtopic.php?pid=255617#p255617)
  88. if( $date = gps('date') ) {
  89. $_GET['month'] = $date;
  90. }
  91. function smd_calendar($atts, $thing='') {
  92. global $pretext, $thisarticle, $variable, $prefs, $smd_cal_flag, $smd_date, $smd_calinfo, $smd_cal_ucls;
  93. extract(lAtts(array(
  94. 'time' => 'any',
  95. 'size' => 'large',
  96. 'expired' => '',
  97. 'category' => null,
  98. 'subcats' => '',
  99. 'section' => '',
  100. 'author' => '',
  101. 'realname' => '',
  102. 'status' => 'live',
  103. 'showall' => '0',
  104. 'static' => '',
  105. 'form' => '',
  106. 'spanform' => 'SMD_SAME',
  107. 'recurform' => 'SMD_SAME',
  108. 'cellform' => '',
  109. 'headerform' => '',
  110. 'stepfield' => '',
  111. 'skipfield' => '',
  112. 'omitfield' => '',
  113. 'extrafield' => '',
  114. 'extrastrict' => '0',
  115. 'datefields' => '',
  116. 'showskipped' => '0',
  117. 'showspanned' => '1',
  118. 'holidays' => '',
  119. 'holidayflags' => 'standard',
  120. 'classlevels' => 'cell, event',
  121. 'linkposted' => 'recur, multi, multiprev, multilast',
  122. 'classprefixes' => 'smd_cal_, smd_cal_ev_',
  123. 'class' => '',
  124. 'rowclass' => '',
  125. 'cellclass' => '',
  126. 'emptyclass' => 'empty',
  127. 'isoweekclass' => 'week',
  128. 'navclass' => 'navprev, navnext',
  129. 'navarrow' => '&#60;, &#62;',
  130. 'navid' => '',
  131. 'eventclasses' => 'category',
  132. 'eventwraptag' => 'span',
  133. 'select' => '',
  134. 'selectbtn' => '',
  135. 'myclass' => '',
  136. 'mywraptag' => '',
  137. 'caption' => '',
  138. 'summary' => '',
  139. 'id' => '',
  140. 'week' => '',
  141. 'month' => '',
  142. 'year' => '',
  143. 'remap' => '',
  144. 'yearwidth' => '0',
  145. 'isoweeks' => '',
  146. 'dayformat' => 'ABBR',
  147. 'monthformat' => 'FULL',
  148. 'firstday' => 0,
  149. 'maintain' => 'calid',
  150. 'nameval' => '',
  151. 'event_delim' => ',',
  152. 'gmt' => 0,
  153. 'lang' => '',
  154. 'debug' => 0,
  155. ), $atts));
  156. $size = (in_array($size, array('small', 'large'))) ? $size : 'large';
  157. $status = ($status) ? $status : 'live'; // in case status is empty
  158. $firstday = ($isoweeks == '') ? $firstday : 1;
  159. $spanform = ($spanform == 'SMD_SAME') ? $form : $spanform;
  160. $recurform = ($recurform == 'SMD_SAME') ? $form : $recurform;
  161. $cellform = (empty($cellform)) ? '' : fetch_form($cellform);
  162. $headerform = (empty($headerform)) ? '' : fetch_form($headerform);
  163. $frontpage = ($section=='' && $pretext['s']=='default') ? true : false;
  164. // Set up the class prefixes
  165. $clevs = do_list($classlevels);
  166. $cls = do_list($classprefixes);
  167. $cls_pfx = $evc_pfx = $cls[0];
  168. if (count($cls) > 1){
  169. $evc_pfx = $cls[1];
  170. }
  171. // Set up the nav class(es)
  172. $maintain = do_list($maintain);
  173. $navarrow = do_list($navarrow);
  174. $navparr = $navarrow[0];
  175. $navnarr = (count($navarrow) > 1) ? $navarrow[1] : $navarrow[0];
  176. $navclass = do_list($navclass);
  177. $navpclass = $navclass[0];
  178. $navnclass = (count($navclass) > 1) ? $navclass[1] : $navclass[0];
  179. // Filters
  180. $fopts = array();
  181. $catSQL = $secSQL = $authSQL = $fpSQL = '';
  182. if($category !== null) {
  183. $uncats = false;
  184. $allcats = do_list($category);
  185. if (($pos = array_search('SMD_UNCAT', $allcats)) !== false) {
  186. $uncats = true;
  187. unset($allcats[$pos]);
  188. $category = join(',', $allcats);
  189. }
  190. $fopts['c'] = $category; // TODO: Can fopts take a list? Should it include subcats?
  191. $subcats = (empty($subcats)) ? 0 : ((strtolower($subcats)=="all") ? 99999 : intval($subcats));
  192. if ($subcats) {
  193. $outcats = array();
  194. foreach ($allcats as $cat) {
  195. $cats = getTree(doslash($cat), 'article');
  196. foreach ($cats as $jdx => $val) {
  197. if ($cats[$jdx]['level'] <= $subcats) {
  198. $outcats[] = $cats[$jdx]['name'];
  199. }
  200. }
  201. }
  202. $allcats = $outcats;
  203. }
  204. $catSQL = doQuote(join("','", doSlash($allcats)));
  205. $catSQL = ($uncats ? " AND (Category1 = '' AND Category2 = '')" : '') .
  206. ($uncats && $allcats ? " OR " : ($allcats ? " AND " : '')) .
  207. ($allcats ? "( Category1 IN (".$catSQL.") OR Category2 IN (".$catSQL.") ) " : '');
  208. }
  209. if($section) {
  210. $secs = do_list($section);
  211. $smd_calinfo['s'] = $secs[0];
  212. $secSQL = doQuote(join("','", doSlash($secs)));
  213. $secSQL = " AND Section IN (".$secSQL.") ";
  214. }
  215. if($realname) {
  216. $authors = safe_column('name', 'txp_users', 'RealName IN ('. doQuote(join("','", doArray(do_list($realname), 'urldecode'))) .')' );
  217. $author = join(',', $authors);
  218. }
  219. if($author) {
  220. $fopts['author'] = htmlentities(gps('author'));
  221. $authSQL = doQuote(join("','", doSlash(do_list($author))));
  222. $authSQL = " AND AuthorID IN (".$authSQL.") ";
  223. }
  224. if ($frontpage && !$showall) {
  225. $fpSQL = filterFrontPage();
  226. }
  227. $smd_calinfo['artid'] = $thisarticle['thisid'];
  228. $smd_calinfo['artitle'] = $thisarticle['url_title'];
  229. $nameval = do_list($nameval);
  230. foreach ($nameval as $nv) {
  231. $nv = explode("=", $nv);
  232. if ($nv[0]) {
  233. $fopts[$nv[0]] = ((isset($nv[1])) ? $nv[1] : '');
  234. }
  235. }
  236. $status = do_list($status);
  237. $stati = array();
  238. foreach ($status as $stat) {
  239. if (empty($stat)) {
  240. continue;
  241. } else if (is_numeric($stat)) {
  242. $stati[] = $stat;
  243. } else {
  244. $stati[] = getStatusNum($stat);
  245. }
  246. }
  247. $stati = " Status IN (".join(',', $stati).")";
  248. $expired = ($expired) ? $expired : $prefs['publish_expired_articles'];
  249. $expired = (($expired) ? '' : ' AND (now() <= Expires OR Expires = '.NULLDATETIME.')');
  250. $eventclasses = do_list($eventclasses);
  251. $holidayflags = do_list($holidayflags);
  252. $linkposted = do_list($linkposted);
  253. $datefields = do_list($datefields);
  254. // Work out the first and last posts to determine the year range - probably a better way of doing this than 3 queries
  255. $filt = $stati . (($category !== null) ? $catSQL : '') . (($section) ? $secSQL : '') . (($author) ? $authSQL : '') . $fpSQL;
  256. $earliest = safe_field('unix_timestamp(Posted) AS uPosted', 'textpattern', $filt .' ORDER BY Posted ASC LIMIT 0, 1', $debug);
  257. $lp = safe_field('unix_timestamp(Posted) AS uPosted', 'textpattern', $filt .' ORDER BY Posted DESC LIMIT 0, 1', $debug);
  258. $lm = safe_field('unix_timestamp(LastMod) AS uLastMod', 'textpattern', $filt .' ORDER BY LastMod DESC LIMIT 0, 1', $debug);
  259. $latest = ($time=="past") ? time() : (($lp > $lm) ? $lp : $lm);
  260. $yearwidth = do_list($yearwidth);
  261. $yearwidth[0] = (empty($yearwidth[0])) ? 0 : $yearwidth[0];
  262. if (count($yearwidth) == 1) {
  263. $yearwidth[1] = $yearwidth[0];
  264. }
  265. $usenow = array(false,false);
  266. foreach ($yearwidth as $yridx => $yritem) {
  267. if (strpos($yritem,"c") !== false) {
  268. $yearwidth[$yridx] = intval($yritem);
  269. $usenow[$yridx] = true;
  270. }
  271. }
  272. // Remap w/m/y to other vars if required
  273. $remap = do_list($remap);
  274. $dmap = array("y" => "y", "m" => "m", "w" => "w");
  275. foreach ($remap as $dpair) {
  276. $dpair = do_list($dpair, ':');
  277. $dmap[$dpair[0]] = (isset($dpair[1])) ? $dpair[1] : $dpair[0];
  278. }
  279. $earliest = date("Y", strtotime("-".$yearwidth[0]." year", ( (empty($earliest) || $usenow[0]==true) ? time() : $earliest) ) );
  280. $latest = date("Y", strtotime("+".$yearwidth[1]." year", ( (empty($latest) || $usenow[1]==true) ? time() : $latest) ) );
  281. // Check the URL for current date and calendar target info
  282. $in_calid = gps('calid');
  283. $in_year = (gps($dmap["y"]) and is_numeric(gps($dmap["y"]))) ? (int)gps($dmap["y"]) : '';
  284. $in_month = (gps($dmap["m"]) and is_numeric(gps($dmap["m"]))) ? (int)gps($dmap["m"]) : '';
  285. $in_week = (gps($dmap["w"]) and is_numeric(gps($dmap["w"]))) ? (int)gps($dmap["w"]) : '';
  286. if($static) { // if we're static w/o any supplied vars, use the current date
  287. if(!$year) { $year = safe_strftime('%Y'); }
  288. if(!$month) { $month = safe_strftime('%m'); }
  289. } else { // otherwise use current date only if there's nothing else
  290. if( $id == $in_calid ) { // use incoming
  291. $year = ($in_year) ? $in_year : (($year) ? $year : safe_strftime('%Y'));
  292. $month = ($in_month) ? $in_month : (($month) ? $month : safe_strftime('%m'));
  293. // If week is used, adjust month so it encompasses the given week
  294. $week = $in_week;
  295. if ($week) {
  296. $month = safe_strftime("%m", strtotime($year."W".str_pad($week, 2, '0', STR_PAD_LEFT))); // Get the month from the week
  297. }
  298. } else { // use current
  299. if(!$year) { $year = safe_strftime('%Y'); }
  300. if(!$month) { $month = safe_strftime('%m'); }
  301. if($week) { $month = safe_strftime("%m", strtotime($year."W".str_pad($week, 2, '0', STR_PAD_LEFT))); }
  302. }
  303. }
  304. $smd_calinfo['id'] = ($in_calid) ? $in_calid : $id;
  305. $smd_date['y'] = $year; $smd_date['m'] = $month; // $week/day/isoyear are set per event later
  306. $ts_first = mktime(0, 0, 0, $month, 1, $year);
  307. $ts_last = mktime(23, 59, 59, $month, date('t',$ts_first), $year);
  308. $ts_lastoff = $ts_last - tz_offset($ts_last);
  309. if ($debug) {
  310. echo "++ THIS MONTH'S CALENDAR [ start stamp // end date // end stamp // end date // tz offset (end) ] ++";
  311. dmp($ts_first, date('Y-m-d H:i:s', $ts_first), $ts_last, date('Y-m-d H:i:s', $ts_last), $ts_lastoff);
  312. }
  313. $extrasql = $catSQL . $secSQL . $authSQL . $fpSQL;
  314. switch($time) {
  315. case "any" : break;
  316. case "future" : $extrasql .= " AND Posted > now()"; break;
  317. default : $extrasql .= " AND Posted < now()"; break; // The past
  318. }
  319. // Holidays are global 'exclusions', either defined directly or in a txp:variable
  320. $holidays = do_list($holidays);
  321. $txphols = do_list($holidays[0], ":");
  322. if ($txphols[0] == "txpvar") {
  323. $holidays = do_list($variable[$txphols[1]]);
  324. }
  325. // Force each holiday to a known format. Holidays without years use current year
  326. foreach ($holidays as $key => $val) {
  327. if (empty($val)) continue;
  328. $numparts = preg_match('/^([\d\w]+).?([\d\w]+).?([\d\w]+)?$/', $val, $parts);
  329. if ($numparts) {
  330. if (count($parts) == 3) {
  331. $parts[3] = $year;
  332. }
  333. $val = str_pad($parts[1], 2, '0', STR_PAD_LEFT).'-'.str_pad($parts[2], 2, '0', STR_PAD_LEFT).'-'.$parts[3];
  334. }
  335. $holidays[$key] = date("d-M-Y", safe_strtotime($val));
  336. }
  337. if ($debug > 0 && !empty($holidays) && $holidays[0] != '') {
  338. echo "++ HOLIDAYS ++ ";
  339. dmp($holidays);
  340. }
  341. // Get all matching articles in (and before) this month
  342. $events = array();
  343. $uposted_field = (empty($datefields[0])) ? 'uPosted' : "UNIX_TIMESTAMP($datefields[0])";
  344. $sql2 = $stati . " HAVING $uposted_field <= ".$ts_lastoff. $expired . $extrasql ." ORDER BY Posted ASC";
  345. $grabCols = '*, unix_timestamp(Posted) as uPosted, unix_timestamp(LastMod) as uLastMod, unix_timestamp(Expires) as uExpires';
  346. $evlist = safe_rows($grabCols, 'textpattern', $sql2, $debug);
  347. article_push();
  348. // If any events recur and fall within the current month, add those as well
  349. // If any dates are to be excluded, the entry is skipped UNLESS showskipped indicates otherwise
  350. foreach ($evlist as $row) {
  351. $idx = 0; // In case the 1st day of the month is a continuation of an event from the end of the previous month
  352. $start = (!empty($datefields[0]) && !empty($row[$datefields[0]]) && ($stdt = strtotime($row[$datefields[0]])) !== false) ? $stdt : $row['uPosted'] + tz_offset($row['uPosted']);
  353. $start_date = date("Y-m-d", $start); // For recurring/spanned events on a minical, this is the event the cell links to
  354. $real_end = (isset($datefields[1]) && !empty($row[$datefields[1]]) && ($endt = strtotime($row[$datefields[1]])) !== false) ? $endt : (($row['uExpires']==0) ? 0 : $row['uExpires'] + tz_offset($row['uExpires']));
  355. // If end < start the user-specified dates cannot be trusted
  356. if ($real_end != 0 && $real_end <= $start) {
  357. $start = $row['uPosted'] + tz_offset($row['uPosted']);
  358. $real_end = $row['uExpires'] + tz_offset($row['uExpires']);
  359. trigger_error('Expiry cannot be before start date in "'.$row['Title'].'": ignored', E_USER_WARNING);
  360. }
  361. $end = ($real_end != 0 && $real_end < $ts_last) ? $real_end : $ts_last;
  362. $real_diff = ($real_end==0) ? 0 : $real_end - $start;
  363. $real_end_month = ($real_end==0) ? 0 : date('m', $real_end);
  364. $real_end_year = ($real_end==0) ? 0 : date('Y', $real_end);
  365. $fake_diff = strtotime(date("Y-M-d", $real_end) . " 23:59:59");
  366. $diff = ($real_end==0) ? 0 : $fake_diff - $start;
  367. $smd_cal_flag = array();
  368. $smd_cal_ucls = array();
  369. $ev_month = date('m', $start);
  370. $ev_year = date('Y', $start);
  371. $ev_hr = date('H', $start);
  372. $ev_mn = date('i', $start);
  373. $ev_sc = date('s', $start);
  374. if ($debug > 1) {
  375. echo '++ EVENT START // END // (if non-zero) REAL END ++';
  376. 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) ));
  377. dmp($row['Title']);
  378. if ($debug > 2) {
  379. dmp($row);
  380. }
  381. }
  382. $multi = (($end > $start) && ($real_end > $start) && ($real_end > $ts_first) && (date("d-m-Y", $real_end) != date("d-m-Y", $start))) ? true : false;
  383. $recur = (empty($row[$stepfield])) ? false : true;
  384. $hol_hit = in_array(date("d-M-Y", $start), $holidays);
  385. $evclasses = array();
  386. foreach ($eventclasses as $evcls) {
  387. switch ($evcls) {
  388. case "":
  389. break;
  390. case "gcat":
  391. if (isset($pretext['c']) && !empty($pretext['c'])) {
  392. $evclasses[] = $evc_pfx.$pretext['c'];
  393. }
  394. break;
  395. case "category":
  396. if (isset($row['Category1']) && !empty($row['Category1'])) {
  397. $evclasses[] = $evc_pfx.$row['Category1'];
  398. }
  399. if (isset($row['Category2']) && !empty($row['Category2'])) {
  400. $evclasses[] = $evc_pfx.$row['Category2'];
  401. }
  402. break;
  403. case "section":
  404. if (isset($pretext['s']) && !empty($pretext['s'])) {
  405. $evclasses[] = $evc_pfx.$pretext['s'];
  406. }
  407. break;
  408. case "author":
  409. if (isset($pretext['author']) && !empty($pretext['author'])) {
  410. $evclasses[] = $evc_pfx.$pretext['author'];
  411. }
  412. break;
  413. default:
  414. if (isset($row[$evcls]) && !empty($row[$evcls])) {
  415. $evclasses[] = $evc_pfx.$row[$evcls];
  416. }
  417. break;
  418. }
  419. }
  420. $ignore = array();
  421. $omit = array();
  422. $cflag = array();
  423. if ($debug > 1 && $evclasses) {
  424. echo '++ EVENT CLASSES ++';
  425. dmp($evclasses);
  426. }
  427. // Events that start or are added this month
  428. if (($start < $end) && ($start > $ts_first)) {
  429. populateArticleData($row);
  430. // a standard event or start of a multi
  431. if ($showspanned && $multi && !$recur) {
  432. $smd_cal_flag[] = 'multifirst';
  433. }
  434. if ($recur) {
  435. $smd_cal_flag[] = 'recurfirst';
  436. }
  437. if (!$smd_cal_flag) {
  438. $smd_cal_flag[] = 'standard';
  439. }
  440. 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) ) ) {
  441. $smd_cal_flag[] = 'cancel';
  442. }
  443. foreach ($smd_cal_flag as $item) {
  444. $cflag[] = $cls_pfx.$item;
  445. }
  446. $idx = $smd_date['d'] = (int)strftime('%d', $start);
  447. $smd_date['w'] = strftime(smd_cal_reformat_win('%V', $start), $start);
  448. $smd_date['iy'] = strftime(smd_cal_reformat_win('%G', $start), $start);
  449. $use_posted = in_array('standard', $linkposted);
  450. $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'].'"')) );
  451. $events[$idx][] = array('ev' => $op, 'evid' => $row['ID'], 'flag' => $smd_cal_flag, 'classes' => array_merge($cflag, $smd_cal_ucls, $evclasses), 'posted' => $start_date);
  452. $smd_cal_flag = array();
  453. $cflag = array();
  454. $smd_cal_ucls = array();
  455. $use_posted = '';
  456. }
  457. // Generate a skip array for this event
  458. if ($skipfield && $row[$skipfield] != '') {
  459. $ignores = do_list($row[$skipfield]);
  460. foreach ($ignores as $val) {
  461. $igrng = smd_expand_daterange($val, $start, $end);
  462. foreach ($igrng as $theval) {
  463. $ignore[] = date("d-M-Y", $theval); // Force each date to a known format
  464. }
  465. }
  466. }
  467. // Generate an omit array for this event
  468. if ($omitfield && $row[$omitfield] != '') {
  469. $omits = do_list($row[$omitfield]);
  470. foreach ($omits as $val) {
  471. $omrng = smd_expand_daterange($val, $start, $end);
  472. foreach ($omrng as $theval) {
  473. $omit[] = date("d-M-Y", $theval);
  474. }
  475. }
  476. }
  477. if ($debug > 1 && ($ignore || $omit)) {
  478. echo '++ OMITTED DATES ++';
  479. dmp($omit);
  480. echo '++ CANCELLED DATES ++';
  481. dmp($ignore);
  482. }
  483. // Calculate the date offsets and check recurring events that fall within the month of interest
  484. if ($stepfield && $row[$stepfield] != '') {
  485. $freq = do_list($row[$stepfield]);
  486. $stampoff = (int)(3600*$ev_hr) + (int)(60*$ev_mn) + (int)$ev_sc;
  487. foreach ($freq as $interval) {
  488. $max_loop = 99999; // Yuk, but practically limitless
  489. $origerval = $interval;
  490. $interval = str_replace("?month", date('F', mktime(0,0,0,$month,1)), $interval);
  491. $interval = str_replace("?year", $year, $interval);
  492. if (strpos($interval, "last") === 0) {
  493. $interval = date("l, F jS Y", strtotime( $interval, mktime(12, 0, 0, date("n", mktime(0,0,0,$month,1,$year))+1, 1, $year) ));
  494. $max_loop = 1;
  495. } else if (strpos($interval, "first") === 0) {
  496. $interval = date("l, F jS Y", strtotime( $interval, mktime(12, 0, 0, (($month>1) ? $month-1 : 12), date("t", mktime(0,0,0,$month-1,1,(($month==1) ? $year-1: $year))), (($month==1) ? $year-1: $year)) ));
  497. $max_loop = 1;
  498. } else if (strpos($interval, "this") === 0) {
  499. $max_loop = 1;
  500. }
  501. $ts_loop = 0;
  502. $ts_curr = $start;
  503. if (strpos($origerval, "?month") || strpos($origerval, "?year")) {
  504. $max_loop = 1;
  505. }
  506. // $rng = smd_expand_daterange($interval);
  507. //dmp($interval, $rng);
  508. while ($ts_curr < $end && $ts_loop < $max_loop) {
  509. if ($max_loop == 1) {
  510. $ts_curr = strtotime($interval);
  511. $ts_curr = ($ts_curr < $start || $ts_curr > $end) ? $start : $ts_curr;
  512. } else {
  513. $ts_curr = strtotime($interval, $ts_curr);
  514. }
  515. if ($ts_curr === false) {
  516. $ts_loop++;
  517. break;
  518. } else {
  519. if ($debug > 1) {
  520. dmp("INTERVAL: ". date('d-M-Y H:i:s', $ts_curr+$stampoff));
  521. }
  522. if ($ts_curr < $end && $ts_curr >= $ts_first && $ts_curr != $start) {
  523. // A recurring event. Check it isn't a holiday or to be ignored
  524. populateArticleData($row);
  525. $op = '';
  526. $idx = (int)strftime('%d', $ts_curr);
  527. $smd_cal_flag[] = 'recur';
  528. $thisdate = date("d-M-Y", $ts_curr);
  529. $omit_me = in_array($thisdate, $omit);
  530. $show_me = !in_array($thisdate, $ignore);
  531. $hol_hit = in_array($thisdate, $holidays);
  532. $show_hol = ($hol_hit && !in_array('recur',$holidayflags) ) ? false : true;
  533. $use_posted = smd_cal_in_array(array('recur', 'recurfirst'), $linkposted);
  534. if ( $omit_me ) {
  535. $smd_cal_flag[] = 'omit';
  536. }
  537. if ( (!$show_me || !$show_hol) && !$omit_me ) {
  538. $smd_cal_flag[] = 'cancel';
  539. }
  540. foreach ($smd_cal_flag as $item) {
  541. $cflag[] = $cls_pfx.$item;
  542. }
  543. // Create the events that appear in the cell but only if they've not appeared before, or are to be ignored/omitted
  544. if (!$omit_me) {
  545. if (($show_me && $show_hol) || $showskipped) {
  546. $smd_date['d'] = $idx;
  547. $smd_date['w'] = strftime(smd_cal_reformat_win('%V', $ts_curr), $ts_curr);
  548. $smd_date['iy'] = strftime(smd_cal_reformat_win('%G', $ts_curr), $ts_curr);
  549. $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'].'"')) );
  550. }
  551. }
  552. $used = array();
  553. if (isset($events[$idx]) && $events[$idx] != NULL) {
  554. foreach ($events[$idx] as $ev) {
  555. $used[] = $ev['ev'];
  556. }
  557. }
  558. if (isset($events[$idx]) && $events[$idx] == NULL || !in_array($op, $used)) {
  559. $events[$idx][] = array('ev' => $op, 'evid' => $row['ID'], 'flag' => $smd_cal_flag, 'classes' => array_merge($cflag, $smd_cal_ucls, $evclasses), 'posted' => $start_date);
  560. }
  561. $smd_cal_flag = array();
  562. $cflag = array();
  563. $smd_cal_ucls = array();
  564. $use_posted = '';
  565. }
  566. $ts_loop++;
  567. }
  568. }
  569. }
  570. } else if ($showspanned && $multi) {
  571. // Non-recurring events may span more than one date but they must still respect ignored dates and holidays
  572. populateArticleData($row);
  573. $lastday = (int)strftime('%d', $end);
  574. $real_lastday = (int)strftime('%d', $real_end);
  575. while (++$idx <= $lastday) {
  576. $op = '';
  577. $multiflag = (($year==$real_end_year) && ($month==$real_end_month) && ($idx==$real_lastday)) ? 'multilast' : (($idx==1) ? 'multiprev' : 'multi');
  578. $smd_cal_flag[] = $multiflag;
  579. $thistime = mktime(0, 0, 0, $month, $idx, $year);
  580. $thisdate = date("d-M-Y", $thistime);
  581. $omit_me = in_array($thisdate, $omit);
  582. $show_me = !in_array($thisdate, $ignore);
  583. $hol_hit = in_array($thisdate, $holidays);
  584. $show_hol = ($hol_hit && !in_array('multi',$holidayflags) ) ? false : true;
  585. $use_posted = smd_cal_in_array(array('multi', 'multifirst', 'multilast', 'multiprev'), $linkposted);
  586. if ( $omit_me ) {
  587. $smd_cal_flag[] = 'omit';
  588. }
  589. if ( (!$show_me || !$show_hol) && !$omit_me ) {
  590. $smd_cal_flag[] = 'cancel';
  591. }
  592. foreach ($smd_cal_flag as $item) {
  593. $cflag[] = $cls_pfx.$item;
  594. }
  595. // Create the spanned event that appears in the cell
  596. if (!$omit_me) {
  597. if ( ($show_me && $show_hol) || $showskipped) {
  598. $smd_date['d'] = $idx;
  599. $smd_date['w'] = strftime(smd_cal_reformat_win('%V', $thistime), $thistime);
  600. $smd_date['iy'] = strftime(smd_cal_reformat_win('%G', $thistime), $thistime);
  601. $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'].'"')) );
  602. }
  603. }
  604. $events[$idx][] = array('ev' => $op, 'evid' => $row['ID'], 'flag' => $smd_cal_flag, 'classes' => array_merge($cflag, $smd_cal_ucls, $evclasses), 'posted' => $start_date);
  605. $smd_cal_flag = array();
  606. $cflag = array();
  607. $smd_cal_ucls = array();
  608. $use_posted = '';
  609. }
  610. }
  611. // Add any extra dates for this event that are within the current month
  612. if ($extrafield && $row[$extrafield] != '') {
  613. $xtra = do_list($row[$extrafield]);
  614. $ev_hr = date('H', $start);
  615. $ev_mn = date('i', $start);
  616. $ev_sc = date('s', $start);
  617. $stampoff = (int)(3600*$ev_hr) + (int)(60*$ev_mn) + (int)$ev_sc;
  618. foreach ($xtra as $val) {
  619. if (strpos($val, "+") === false) {
  620. $exrng = smd_expand_daterange($val);
  621. $val = date("Y-m-d", $exrng[0]);
  622. $spidth = count($exrng);
  623. $spex = 0;
  624. } else {
  625. $chk = $showspanned && !$recur;
  626. $spidth = $chk ? ceil($diff / (60*60*24)) : 1; // days between dates
  627. $val = rtrim($val, '+');
  628. $spex = $chk ? 1 : 0;
  629. }
  630. for ($jdx = 1; $jdx <= $spidth; $jdx++) {
  631. $tm = safe_strtotime($val . (($jdx==1) ? '' : '+'.($jdx-1).' days'));
  632. if ($diff > 0 && $jdx == 1) {
  633. $expstamp = $tm+$stampoff+$real_diff;
  634. }
  635. $idx = $smd_date['d'] = (int)strftime('%d', $tm);
  636. $dt = date("Y-m-d", $tm);
  637. $lst = ($extrastrict) ? $end : $ts_last;
  638. if ($tm < $lst && $tm >= $ts_first) {
  639. $fakerow = $row;
  640. $fakerow['Posted'] = date("Y-m-d H:i:s", $tm+$stampoff);
  641. $fakerow['uPosted'] = $tm+$stampoff;
  642. if ($diff>0) {
  643. $fakerow['Expires'] = date("Y-m-d H:i:s", $expstamp);
  644. $fakerow['uExpires'] = $expstamp;
  645. }
  646. populateArticleData($fakerow);
  647. $smd_cal_flag[] = 'extra';
  648. $cflag[] = $cls_pfx.'extra';
  649. $omit_me = false;
  650. $show_me = $show_hol = true;
  651. if ($spex) {
  652. $multiflag = ($jdx==1) ? 'multifirst' : (($jdx==$spidth) ? 'multilast' : (($idx==1) ? 'multiprev' : 'multi'));
  653. $thisdate = date("d-M-Y", $tm);
  654. $omit_me = in_array($thisdate, $omit);
  655. $show_me = !in_array($thisdate, $ignore);
  656. $hol_hit = in_array($thisdate, $holidays);
  657. $show_hol = ($hol_hit && !in_array('multi',$holidayflags) ) ? false : true;
  658. $use_posted = in_array('extra', $linkposted);
  659. if ($omit_me) {
  660. $smd_cal_flag[] = 'omit';
  661. }
  662. if ( (!$show_me || !$show_hol) && !$omit_me ) {
  663. $smd_cal_flag[] = 'cancel';
  664. }
  665. $smd_cal_flag[] = $multiflag;
  666. $cflag[] = $cls_pfx.$multiflag;
  667. }
  668. if (!$omit_me) {
  669. if ( ($show_me && $show_hol) || $showskipped) {
  670. $smd_date['w'] = strftime(smd_cal_reformat_win('%V', $tm), $tm);
  671. $smd_date['iy'] = strftime(smd_cal_reformat_win('%G', $tm), $tm);
  672. $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'].'"')) ));
  673. $events[$idx][] = array('ev' => $op, 'evid' => $row['ID'], 'flag' => $smd_cal_flag, 'classes' => array_merge($cflag, $smd_cal_ucls, $evclasses), 'posted' => $dt);
  674. $smd_cal_flag = array();
  675. $cflag = array();
  676. $smd_cal_ucls = array();
  677. $use_posted = '';
  678. }
  679. }
  680. }
  681. }
  682. }
  683. }
  684. }
  685. article_pop();
  686. if ($debug > 1 && $events) {
  687. echo '++ ALL EVENTS ++';
  688. dmp($events);
  689. }
  690. // Generate the calendar
  691. $calendar = new SMD_Calendar($size, $year, $month, $events, $section, $category, $debug);
  692. $calendar->setWeek($week);
  693. $calendar->setFirstDayOfWeek($firstday);
  694. $calendar->setGMT($gmt);
  695. $calendar->setLang($lang);
  696. $calendar->setClassLevels($clevs);
  697. $calendar->setClassPrefix($cls_pfx);
  698. $calendar->setEventWraptag($eventwraptag);
  699. $calendar->setCellForm($cellform);
  700. $calendar->setHdrForm($headerform);
  701. $calendar->setMYWraptag($mywraptag);
  702. $calendar->setSummary($summary);
  703. $calendar->setCaption($caption);
  704. $calendar->setTableID($id);
  705. $calendar->setTableClass($class);
  706. $calendar->setRowClass($rowclass);
  707. $calendar->setCellClass($cellclass);
  708. $calendar->setEmptyClass($emptyclass);
  709. $calendar->setISOWeekClass($isoweekclass);
  710. $calendar->setNavInfo($navpclass,$navnclass,$navparr,$navnarr,$navid);
  711. $calendar->setNavKeep($maintain);
  712. $calendar->setMYClass($myclass);
  713. $calendar->setNameFormat($dayformat, "d");
  714. $calendar->setNameFormat($monthformat, "m");
  715. $calendar->setRemap($dmap);
  716. $calendar->setShowISOWeek($isoweeks);
  717. $calendar->setEYear($earliest);
  718. $calendar->setLYear($latest);
  719. $calendar->setFilterOpts($fopts);
  720. $calendar->setDelim($event_delim);
  721. $calendar->setHolidays($holidays);
  722. $calendar->setSelectors(do_list($select), $selectbtn);
  723. return $calendar->display($static);
  724. }
  725. class SMD_Calendar extends SMD_Raw_Calendar {
  726. // Override Constructor
  727. // Permits multiple events to show per day
  728. var $section = '';
  729. var $category = '';
  730. var $size = '';
  731. var $debug = 0;
  732. var $events = array();
  733. function SMD_Calendar($size,$year,$month,$events,$section,$category, $debug=0) {
  734. $this->debug = $debug;
  735. $this->section = $section;
  736. $this->category = $category;
  737. $this->events = $events;
  738. $this->size = $size;
  739. $this->smd_Raw_Calendar($year,$month,$debug);
  740. }
  741. // Override dspDayCell to display stuff right
  742. function dspDayCell($theday) {
  743. global $smd_cal_flag, $smd_calinfo, $smd_cal_ucls, $smd_date, $permlink_mode;
  744. $smd_cal_flag = array();
  745. $smd_cal_ucls = array();
  746. $tdclass = array();
  747. $hasarticle = isset($this->events[$theday]);
  748. $now = time() + tz_offset();
  749. $thedate = mktime(0, 0, 0, $this->month, $theday, $this->year);
  750. $hol_hit = in_array(date("d-M-Y", $thedate), $this->holidays);
  751. if ($hasarticle) {
  752. $smd_cal_flag[] = 'event';
  753. }
  754. if ($hol_hit) {
  755. $smd_cal_flag[] = 'hols';
  756. }
  757. $cflag = array();
  758. foreach ($smd_cal_flag as $item) {
  759. $cflag[] = $this->cls_pfx.$item;
  760. }
  761. if ($this->cellclass) {
  762. $tdclass[] = $this->cellclass;
  763. }
  764. $tdclass = array_merge($tdclass, $cflag);
  765. $runningclass = (in_array("cell", $this->cls_lev) || in_array("cellplus", $this->cls_lev)) ? $tdclass : array();
  766. if($this->year == date('Y',$now) and $this->month == date('n',$now) and $theday == date('j',$now) ) {
  767. $smd_cal_flag[] = 'today';
  768. $runningclass[] = $this->cls_pfx.'today';
  769. }
  770. $out = array();
  771. $flags = array();
  772. $evid = array();
  773. $fout = array('standard'=>array(),'recur'=>array(),'recurfirst'=>array(),'multifirst'=>array(),'multi'=>array(),'multiprev'=>array(),'multilast'=>array(),'cancel'=>array(),'extra'=>array());
  774. if (empty($this->cellform) && $this->size == 'large') {
  775. $out[] = hed($theday,4);
  776. }
  777. $evcnt = 0;
  778. if( isset($this->events[$theday]) ) {
  779. $days_events = $this->events[$theday];
  780. foreach($days_events as $ev) {
  781. $evclass = $ev['classes'];
  782. $evid[] = $ev['evid'];
  783. $flags = array_merge($flags, $ev['flag']);
  784. if (in_array("cellplus", $this->cls_lev)) {
  785. $runningclass = array_merge($runningclass, $evclass);
  786. }
  787. $cls = ($evclass && in_array("event", $this->cls_lev)) ? ' class="'.join(' ', $evclass).'"' : '';
  788. $op = ($this->evwraptag) ? tag($ev['ev'], $this->evwraptag, $cls) : $ev['ev'];
  789. foreach ($ev['flag'] as $flev) {
  790. $fout[$flev][] = $op;
  791. }
  792. $out[] = $op;
  793. $evcnt++;
  794. if ($this->size == 'small' && $evcnt == 1) {
  795. break;
  796. }
  797. }
  798. } elseif ($this->size == 'small') {
  799. $out[] = hed($theday,4);
  800. }
  801. // Amalgamate the event-level classes and cell-level classes if required
  802. $runningclass = array_unique($runningclass);
  803. if (in_array("cellplus", $this->cls_lev)) {
  804. $smd_cal_flag = array_merge($smd_cal_flag, $flags);
  805. }
  806. if ($this->cellform) {
  807. $thistime = mktime(0, 0, 0, $this->month, $theday, $this->year);
  808. $smd_calinfo['id'] = $this->tableID;
  809. $smd_date['y'] = $this->year;
  810. $smd_date['m'] = $this->month;
  811. $smd_date['w'] = strftime(smd_cal_reformat_win('%V', $thistime), $thistime);
  812. $smd_date['iy'] = strftime(smd_cal_reformat_win('%G', $thistime), $thistime);
  813. $smd_date['d'] = $theday;
  814. $reps = array(
  815. '{evid}' => join($this->event_delim, $evid),
  816. '{standard}' => join('',$fout['standard']),
  817. '{recur}' => join('',$fout['recur']),
  818. '{recurfirst}' => join('',$fout['recurfirst']),
  819. '{allrecur}' => join('',array_merge($fout['recur'], $fout['recurfirst'])),
  820. '{multifirst}' => join('',$fout['multifirst']),
  821. '{multiprev}' => join('',$fout['multiprev']),
  822. '{multi}' => join('',$fout['multilast']),
  823. '{multilast}' => join('',$fout['multilast']),
  824. '{allmulti}' => join('',array_merge($fout['multifirst'],$fout['multi'],$fout['multiprev'],$fout['multilast'])),
  825. '{cancel}' => join('',$fout['cancel']),
  826. '{extra}' => join('',$fout['extra']),
  827. '{events}' => join('',$out),
  828. '{numevents}' => $evcnt,
  829. '{day}' => $theday,
  830. '{dayzeros}' => str_pad($theday, 2, '0', STR_PAD_LEFT),
  831. '{weekday}' => ((is_array($this->dayNameFmt)) ? $this->dayNames[date('w',$thistime)] : strftime($this->dayNameFmt, $thistime)),
  832. '{weekdayabbr}' => strftime('%a', $thistime),
  833. '{weekdayfull}' => strftime('%A', $thistime),
  834. '{week}' => $smd_date['w'],
  835. '{month}' => $this->month,
  836. '{monthzeros}' => str_pad($this->month, 2, '0', STR_PAD_LEFT),
  837. '{monthname}' => ((is_array($this->mthNameFmt)) ? $this->mthNames[date('n',$thistime)] : strftime($this->mthNameFmt, $thistime)),
  838. '{monthnameabbr}' => strftime('%b', $thistime),
  839. '{monthnamefull}' => strftime('%B', $thistime),
  840. '{year}' => $this->year,
  841. '{shortyear}' => strftime('%y', $thistime),
  842. '{isoyear}' => $smd_date['iy'],
  843. '{shortisoyear}' => strftime(smd_cal_reformat_win('%g', $thistime), $thistime),
  844. );
  845. $cellout = parse(strtr($this->cellform, $reps));
  846. $carray = array_merge($runningclass, $smd_cal_ucls);
  847. $smd_cal_ucls = array();
  848. return doTag($cellout,'td',join(' ',$carray));
  849. } else {
  850. return doTag(join('',$out),'td',join(' ',$runningclass));
  851. }
  852. }
  853. function display($static=false) {
  854. $sum = ($this->tblSummary) ? ' summary="'.$this->tblSummary.'"' : '';
  855. $id = ($this->tableID) ? ' id="'.$this->tableID.'"' : '';
  856. $c[] = ($this->tblCaption) ? '<caption>'.$this->tblCaption.'</caption>' : '';
  857. $c[] = '<thead>';
  858. $c[] = $this->dspHeader($static);
  859. $c[] = $this->dspDayNames();
  860. $c[] = '</thead>';
  861. $c[] = $this->dspDayCells();
  862. return doTag(join('',$c),'table',$this->tableclass,$sum.$id);
  863. }
  864. function dspHeader($static) {
  865. global $pretext, $smd_calinfo, $permlink_mode;
  866. $currmo = $this->month;
  867. $curryr = $this->year;
  868. $navpclass = $this->getNavInfo("pc");
  869. $navnclass = $this->getNavInfo("nc");
  870. $navparrow = $this->getNavInfo("pa");
  871. $navnarrow = $this->getNavInfo("na");
  872. $navid = $this->getNavInfo("id");
  873. $navpclass = ($navpclass) ? ' class="'.$navpclass.'"' : '';
  874. $navnclass = ($navnclass) ? ' class="'.$navnclass.'"' : '';
  875. $fopts = $this->fopts;
  876. $sec = (isset($smd_calinfo['s']) && !empty($smd_calinfo['s'])) ? $smd_calinfo['s'] : '';
  877. foreach ($this->maintain as $col) {
  878. switch ($col) {
  879. case "section":
  880. if ($pretext['s'] && $permlink_mode != 'year_month_day_title') {
  881. $fopts = array('s' => $pretext['s']) + $fopts;
  882. }
  883. break;
  884. case "article":
  885. if ($pretext['id']) {
  886. $fopts = array('id' => $pretext['id']) + $fopts;
  887. }
  888. break;
  889. case "category":
  890. if ($pretext['c']) {
  891. $fopts = array('c' => $pretext['c']) + $fopts;
  892. }
  893. break;
  894. case "author":
  895. if (gps('author')) {
  896. $fopts = array('author' => gps('author')) + $fopts;
  897. }
  898. break;
  899. case "date":
  900. if (gps('date')) {
  901. $fopts = array('date' => gps('date')) + $fopts;
  902. }
  903. break;
  904. case "pg":
  905. if ($pretext['pg']) {
  906. $fopts = array('pg' => $pretext['pg']) + $fopts;
  907. }
  908. break;
  909. case "calid":
  910. if ($this->tableID) {
  911. $fopts = array('calid' => $this->tableID) + $fopts;
  912. }
  913. break;
  914. default:
  915. if (gps($col)) {
  916. $fopts = array($col => gps($col)) + $fopts;
  917. }
  918. break;
  919. }
  920. }
  921. $fopts = array_unique($fopts);
  922. $filters = array();
  923. $filterHid = array();
  924. if (!$static) {
  925. foreach($fopts as $key => $val) {
  926. $filters[] = $key.'='.$val;
  927. $filterHid[] = hInput($key, $val);
  928. }
  929. }
  930. // Week select list
  931. if ($this->useSelector('week') && !$static) {
  932. $currwk = ($this->week) ? $this->week : date('W', safe_strtotime($curryr."-".$currmo."-1 12:00"));
  933. for ( $idx = 1; $idx <= 53; $idx++ ) {
  934. $tagatts = ' value="'.$idx.'"';
  935. if ( $idx == $currwk ) $tagatts .= ' selected="selected"';
  936. $optiontags[] = doTag($this->selpfx['week'].str_pad($idx, 2, '0', STR_PAD_LEFT).$this->selsfx['week'], 'option', '', $tagatts);
  937. }
  938. $selector[] = doTag(join(n, $optiontags), 'select', (($this->mywraptag) ? '' : $this->myclass), ' name="'.$this->remap['w'].'"'.(($this->selbtn) ? '' : ' onchange="this.form.submit()"'), '')
  939. . (($this->useSelector('year')) ? '' : hInput($this->remap['y'], $curryr));
  940. $optiontags = array(); // Blank out
  941. }
  942. // Month select list - note mktime has the day forced to 1. If not you get
  943. // bizarre repeated month names on the 31st of some months :-\
  944. if (!$this->useSelector('week')) {
  945. if ($this->useSelector('month') && !$static) {
  946. for ( $idx = 1; $idx <= 12; $idx++ ) {
  947. $tagatts = ' value="'.$idx.'"';
  948. if ( $idx == $currmo ) $tagatts .= ' selected="selected"';
  949. $optiontags[] = doTag($this->selpfx['month'].((is_array($this->mthNameFmt)) ? $this->mthNames[date('n',mktime(12,0,0,$idx,1))] : safe_strftime($this->mthNameFmt, mktime(12,0,0,$idx,1) )).$this->selsfx['month'], 'option', '', $tagatts);
  950. }
  951. $selector[] = doTag(join(n, $optiontags), 'select', (($this->mywraptag) ? '' : $this->myclass), ' name="'.$this->remap['m'].'"'.(($this->selbtn) ? '' : ' onchange="this.form.submit()"'), '')
  952. . (($this->useSelector('year')) ? '' : hInput($this->remap['y'], $curryr));
  953. $optiontags = array(); // Blank out
  954. } else {
  955. $selector[] = doTag($this->getMonthName(), 'span', (($this->mywraptag) ? '' : $this->myclass));
  956. }
  957. }
  958. // Year select list
  959. $y0 = $this->eyr;
  960. $y1 = $this->lyr;
  961. if ($this->useSelector('year') && ($y0 != $y1) && !$static) {
  962. for ( $idx = $y0; $idx <= $y1; $idx++ ) {
  963. $tagatts = ' value="'.$idx.'"';
  964. if ( $idx == $curryr ) $tagatts .= ' selected="selected"';
  965. $optiontags[] = doTag($this->selpfx['year'].$idx.$this->selsfx['year'], 'option', '', $tagatts);
  966. }
  967. $selector[] = doTag(join(n, $optiontags), 'select', (($this->mywraptag) ? '' : $this->myclass), ' name="'.$this->remap['y'].'"'.(($this->selbtn) ? '' : ' onchange="this.form.submit()"'), '')
  968. . (($this->useSelector('month') || $this->useSelector('week')) ? '' : hInput($this->remap['m'], $currmo));
  969. } else {
  970. $selector[] = doTag($curryr, 'span', (($this->mywraptag) ? '' : $this->myclass));
  971. }
  972. $request = serverSet('REQUEST_URI');
  973. $redirect = serverSet('REDIRECT_URL');
  974. if (!empty($redirect) && ($request != $redirect) && is_callable('_l10n_set_browse_language')) {
  975. // MLP in da house: use the redirect URL instead
  976. $request = $redirect;
  977. }
  978. $urlp = parse_url($request);
  979. $action = $urlp['path'];
  980. if ($permlink_mode == 'messy') {
  981. $out = makeOut('id','s','c','q','pg','p','month');
  982. foreach($out as $key => $val) {
  983. if ($val) {
  984. $filters[] = $key.'='.$val;
  985. $filterHid[] = hInput($key, $val);
  986. }
  987. }
  988. }
  989. $filterHid = array_unique($filterHid);
  990. $filters = array_unique($filters);
  991. $extras = '';
  992. if (!$static && ( $this->useSelector('month') || $this->useSelector('year') )) {
  993. if ($this->selbtn) {
  994. $extras .= doTag('', 'input', 'smd_cal_input', ' type="submit" value="'.$this->selbtn.'"');
  995. }
  996. $extras .= join(n, $filterHid);
  997. }
  998. $selector = '<form action="'.$action.'" method="get"'.(($navid) ? ' id="'.$navid.'"' : '').'>'.doTag(join(sp, $selector).$extras, $this->mywraptag, $this->myclass).'</form>';
  999. $nav_back_link = $this->navigation($curryr, $currmo, '-', $filters, $urlp['path']);
  1000. $nav_fwd_link = $this->navigation($curryr, $currmo, '+', $filters, $urlp['path']);
  1001. $nav_back = (!$static && $nav_back_link) ? '<a href="'.$nav_back_link.'"'.$navpclass.'>'.$navparrow.'</a>' : '&nbsp;';
  1002. $nav_fwd = (!$static && $nav_fwd_link) ? '<a href="'.$nav_fwd_link.'"'.$navnclass.'>'.$navnarrow.'</a>' : '&nbsp;';
  1003. $c[] = doTag($nav_back,'th');
  1004. $c[] = '<th colspan="'.(($this->showISOWeek) ? 6 : 5).'">'.$selector.'</th>';
  1005. $c[] = doTag($nav_fwd,'th');
  1006. return doTag(join('',$c),'tr', 'smd_cal_navrow');
  1007. }
  1008. function navigation($year,$month,$direction,$flt,$url='') {
  1009. global $permlink_mode;
  1010. if($direction == '-') {
  1011. if($month - 1 < 1) {
  1012. $month = 12;
  1013. $year -= 1;
  1014. } else {
  1015. $month -= 1;
  1016. }
  1017. } else {
  1018. if($month + 1 > 12) {
  1019. $month = 1;
  1020. $year += 1;
  1021. } else {
  1022. $month += 1;
  1023. }
  1024. }
  1025. // Abort if we're about to go out of range
  1026. if ($year < $this->eyr || $year > $this->lyr) {
  1027. return '';
  1028. }
  1029. $flt[] = $this->remap['m']."=$month";
  1030. $flt[] = $this->remap['y']."=$year";
  1031. return $url . "?" . join(a, $flt);
  1032. }
  1033. }
  1034. /**
  1035. * Basic Calendar data and display
  1036. * http://www.oscarm.org/static/pg/calendarClass/
  1037. * @author Oscar Merida
  1038. * @created Jan 18 2004
  1039. */
  1040. class SMD_Raw_Calendar {
  1041. var $gmt = 1, $lang, $debug = 0;
  1042. var $year, $eyr, $lyr, $month, $week;
  1043. var $dayNameFmt, $mthNameFmt, $dayNames, $mthNames, $startDay, $endDay, $firstDayOfWeek = 0, $startOffset = 0;
  1044. var $selectors, $selbtn, $selpfx, $selsfx;
  1045. var $showISOWeek, $ISOWeekHead, $ISOWeekCell;
  1046. var $cls_lev, $cls_pfx, $fopts;
  1047. var $evwraptag, $mywraptag;
  1048. var $rowclass, $cellclass, $emptyclass, $isoclass, $myclass, $tableID, $tblSummary, $tblCaption;
  1049. var $navpclass, $navnclass, $navparrow, $navnarrow, $navid;
  1050. var $holidays, $cellform, $hdrform, $maintain, $remap;
  1051. var $event_delim;
  1052. /**
  1053. * Constructor
  1054. *
  1055. * @param integer, year
  1056. * @param integer, month
  1057. * @return object
  1058. * @public
  1059. */
  1060. function SMD_Raw_Calendar ($yr, $mo, $debug=0) {
  1061. $this->setDebug($debug);
  1062. $this->setYear($yr);
  1063. $this->setMonth($mo);
  1064. $this->setClassPrefix('smd_cal_');
  1065. $this->startTime = strtotime( "$yr-$mo-01 00:00" );
  1066. $this->startDay = date( 'D', $this->startTime );
  1067. $this->endDay = date( 't', $this->startTime );
  1068. $this->endTime = strtotime( "$yr-$mo-".$this->endDay." 23:59:59" );
  1069. if ($this->debug) {
  1070. echo "++ THIS MONTH'S RENDERED CALENDAR [ start stamp // end date // start day // end stamp // end date // end day number ] ++";
  1071. dmp($this->startTime, date('Y-m-d H:i:s', $this->startTime), $this->startDay, $this->endTime, date('Y-m-d H:i:s', $this->endTime), $this->endDay);
  1072. }
  1073. $this->setNameFormat('%a', 'd');
  1074. $this->setNameFormat('%B', 'm');
  1075. $this->setFirstDayOfWeek(0);
  1076. $this->setShowISOWeek('');
  1077. $this->setTableID('');
  1078. $this->setTableClass('');
  1079. }
  1080. // === end Calendar ===
  1081. // Getters
  1082. function useSelector($val) { return in_array($val, $this->selectors); }
  1083. function getDayName($day) { return ($this->dayNames[$day%7]); }
  1084. function getMonthName() {
  1085. if (is_array($this->mthNameFmt)) {
  1086. return $this->mthNames[date('n',$this->startTime)];
  1087. } else {
  1088. return strftime($this->mthNameFmt, $this->startTime);
  1089. }
  1090. }
  1091. function getNavInfo($type) {
  1092. $r = '';
  1093. switch ($type) {
  1094. case "id": $r = $this->navid; break;
  1095. case "pc": $r = $this->navpclass; break;
  1096. case "nc": $r = $this->navnclass; break;
  1097. case "pa": $r = $this->navparrow; break;
  1098. case "na": $r = $this->navnarrow; break;
  1099. }
  1100. return $r;
  1101. }
  1102. // Setters
  1103. function setDebug($d){ $this->debug = $d; }
  1104. function setGMT($b){ $this->gmt = $b; }
  1105. function setLang($code){ $this->lang = $code; }
  1106. function setSummary($txt){ $this->tblSummary = $txt; }
  1107. function setCaption($txt){ $this->tblCaption = $txt; }
  1108. function setCellForm($frm){ $this->cellform = $frm; }
  1109. function setHdrForm($frm){ $this->hdrform = $frm; }
  1110. function setTableID($id){ $this->tableID = $id; }
  1111. function setYear($yr){ $this->year = $yr; }
  1112. function setEYear($yr){ $this->eyr = $yr; }
  1113. function setLYear($yr){ $this->lyr = $yr; }
  1114. function setMonth($mth){ $this->month = (int)$mth; }
  1115. function setWeek($wk){
  1116. if ($wk) {
  1117. $wk = str_pad($wk, 2, '0', STR_PAD_LEFT);
  1118. $this->week = $wk;
  1119. $this->month = safe_strftime("%m", strtotime($this->year."W".$wk));
  1120. }
  1121. }
  1122. function setNavKeep($ar){ $this->maintain = $ar; }
  1123. function setShowISOWeek($val) {
  1124. $this->showISOWeek = ($val) ? true : false;
  1125. if ($val) {
  1126. $val = do_list($val);
  1127. $this->ISOWeekHead = $val[0];
  1128. $this->ISOWeekCell = (isset($val[1])) ? $val[1] : '{week}';
  1129. }
  1130. }
  1131. function setRemap($map){ $this->remap = $map; }
  1132. function setClassLevels($cls){ $this->cls_lev = $cls; }
  1133. function setClassPrefix($cls){ $this->cls_pfx = $cls; }
  1134. function setEventWraptag($wrap){ $this->evwraptag = $wrap; }
  1135. function setMYWraptag($wrap){ $this->mywraptag = $wrap; }
  1136. function setTableClass($cls) { $this->tableclass = ($cls) ? $this->cls_pfx.$cls : ''; }
  1137. function setRowClass($cls){ $this->rowclass = ($cls) ? $this->cls_pfx.$cls : ''; }
  1138. function setCellClass($cls){ $this->cellclass = ($cls) ? $this->cls_pfx.$cls : ''; }
  1139. function setEmptyClass($cls){ $this->emptyclass = ($cls) ? $this->cls_pfx.$cls : ''; }
  1140. function setISOWeekClass($cls){ $this->isoclass = ($cls) ? $this->cls_pfx.$cls : ''; }
  1141. function setDelim($dlm){ $this->event_delim = $dlm; }
  1142. function setNavInfo($clsp, $clsn, $arrp, $arrn, $nid){
  1143. $this->navpclass = ($clsp) ? $this->cls_pfx.$clsp : '';
  1144. $this->navnclass = ($clsn) ? $this->cls_pfx.$clsn : '';
  1145. $this->navparrow = ($arrp) ? $arrp : '';
  1146. $this->navnarrow = ($arrn) ? $arrn : '';
  1147. $this->navid = ($nid) ? $this->cls_pfx.$nid : '';
  1148. }
  1149. function setMYClass($cls){ $this->myclass = ($cls) ? $this->cls_pfx.$cls : ''; }
  1150. function setFilterOpts($f) { $this->fopts = $f; }
  1151. function setHolidays($hols) { $this->holidays = $hols; }
  1152. function setSelectors($sel, $btn) {
  1153. foreach ($sel as $idx => $item) {
  1154. $selparts = explode(":", $item);
  1155. $sel[$idx] = $selparts[0];
  1156. $this->selpfx[$selparts[0]] = (isset($selparts[1])) ? $selparts[1] : '';
  1157. $this->selsfx[$selparts[0]] = (isset($selparts[2])) ? $selparts[2] : '';
  1158. }
  1159. $this->selectors = $sel;
  1160. $this->selbtn = $btn;
  1161. }
  1162. function setFirstDayOfWeek($d) {
  1163. $this->firstDayOfWeek = ((int)$d <= 6 and (int)$d >= 0) ? (int)$d : 0;
  1164. $this->startOffset = date('w', $this->startTime) - $this->firstDayOfWeek;
  1165. if ( $this->startOffset < 0 ) {
  1166. $this->startOffset = 7 - abs($this->startOffset);
  1167. }
  1168. }
  1169. /**
  1170. * frm: any valid PHP strftime() string or ABBR/FULL
  1171. * typ: d to set day, m to set month format
  1172. */
  1173. function setNameFormat($frm, $typ="d") {
  1174. switch ($frm) {
  1175. case "full":
  1176. case "FULL":
  1177. $fmt = ($typ == 'd') ? "%A" : "%B";
  1178. break;
  1179. case "abbr":
  1180. case "ABBR":
  1181. $fmt = ($typ == 'd') ? "%a" : "%b";
  1182. break;
  1183. default:
  1184. if (strpos($frm, '%') === 0) {
  1185. $fmt = $frm;
  1186. } else {
  1187. $frm = trim($frm, '{}');
  1188. $frm = do_list($frm);
  1189. $fmt = $frm;
  1190. }
  1191. break;
  1192. }
  1193. if ($typ == "d") {
  1194. $this->dayNameFmt = $fmt;
  1195. $this->dayNames = array();
  1196. // This is done to make sure Sunday is always the first day of our array
  1197. $start = 0;
  1198. $end = $start + 7;
  1199. $sunday = strtotime('1970-Jan-04 12:00:00');
  1200. for($i=$start; $i<$end; $i++) {
  1201. if (is_array($fmt)) {
  1202. $this->dayNames[] = $fmt[$i-$start];
  1203. } else {
  1204. $this->dayNames[] = ucfirst(strftime($fmt, ($sunday + (86400*$i))));
  1205. }
  1206. }
  1207. } else {
  1208. $this->mthNameFmt = $fmt;
  1209. $this->mthNames = array();
  1210. for ($i=0; $i<12; $i++) {
  1211. if (is_array($fmt)) {
  1212. $this->mthNames[$i+1] = $fmt[$i];
  1213. }
  1214. }
  1215. }
  1216. }
  1217. /**
  1218. * Return markup for displaying the calendar.
  1219. * @return
  1220. * @public
  1221. */
  1222. function display ( ) {
  1223. $id = ($this->tableID) ? ' id="'.$this->tableID.'"' : '';
  1224. $c[] = '<table'.$id.'>';
  1225. $c[] = '<thead>' . $this->dspDayNames() . '</thead>';
  1226. $c[] = $this->dspDayCells();
  1227. $c[] = '</table>';
  1228. return join('',$c);
  1229. }
  1230. // === end display ===
  1231. /**
  1232. * Displays the row of day names.
  1233. * @return string
  1234. * @private
  1235. */
  1236. function dspDayNames ( ) {
  1237. if ($this->hdrform) {
  1238. $reps = array(
  1239. '{firstday}' => $this->firstDayOfWeek,
  1240. '{daynames}' => join(',', $this->dayNames),
  1241. '{isoweekhead}' => $this->ISOWeekHead,
  1242. '{week}' => date('W', $this->startTime),
  1243. '{month}' => date('n', $this->startTime),
  1244. '{year}' => date('Y', $this->startTime),
  1245. '{isoyear}' => date('o', $this->startTime),
  1246. );
  1247. return parse(strtr($this->hdrform, $reps));
  1248. } else {
  1249. $c[] = '<tr class="smd_cal_daynames">';
  1250. $i = $this->firstDayOfWeek;
  1251. $j = 0; // count number of days displayed
  1252. $end = false;
  1253. if ($this->showISOWeek) {
  1254. $c[] = "<th>".$this->ISOWeekHead."</th>";
  1255. }
  1256. for($j = 0; $j<=6; $j++, $i++) {
  1257. if($i == 7) { $i = 0; }
  1258. $c[] = '<th>'.$this->getDayName($i)."</th>";
  1259. }
  1260. $c[] = '</tr>';
  1261. return join('',$c);
  1262. }
  1263. }
  1264. // === end dspDayNames ===
  1265. /**
  1266. * Displays all day cells for the month
  1267. *
  1268. * @return string
  1269. * @private
  1270. */
  1271. function dspDayCells ( ) {
  1272. $i = 0; // cell counter
  1273. $emptyClass = $this->emptyclass;
  1274. $isoClass = $this->isoclass;
  1275. $rowClass = $this->rowclass;
  1276. $rowClass = ($rowClass) ? ' class="'.$rowClass.'"' : '';
  1277. $c[] = '<tr'.$rowClass.'>';
  1278. if ($this->showISOWeek) {
  1279. $reps = array(
  1280. '{week}' => date('W', $this->startTime),
  1281. '{month}' => date('n', $this->startTime),
  1282. '{year}' => date('Y', $this->startTime),
  1283. '{isoyear}' => date('o', $this->startTime),
  1284. );
  1285. $wkcell = strtr($this->ISOWeekCell, $reps);
  1286. $c[] = '<td class="'.$isoClass.'">'.$wkcell.'</td>';
  1287. }
  1288. // first display empty cells based on what weekday the month starts in
  1289. for( $j=0; $j<$this->startOffset; $j++ ) {
  1290. $i++;
  1291. $c[] = '<td class="'.$emptyClass.'">&nbsp;</td>';
  1292. } // end offset cells
  1293. // write out the rest of the days, at each sunday, start a new row.
  1294. for( $d=1; $d<=$this->endDay; $d++ ) {
  1295. $i++;
  1296. $c[] = $this->dspDayCell( $d );
  1297. if ( $i%7 == 0 ) { $c[] = '</tr>'; }
  1298. if ( $d<$this->endDay && $i%7 == 0 ) {
  1299. $c[] = '<tr'.$rowClass.'>';
  1300. if ($this->showISOWeek) {
  1301. // **Not** using safe_strtotime() here to cater for an operating timezone that differs from the server timezone.
  1302. // Probably should do this in other places too but no bugs have been filed yet so it can be done on a
  1303. // case-by-case basis
  1304. $theTime = strtotime($this->year."-".$this->month."-".(int)($d + 1) ." 00:00");
  1305. $reps = array(
  1306. '{week}' => date('W', $theTime),
  1307. '{month}' => date('n', $theTime),
  1308. '{year}' => date('Y', $theTime),
  1309. '{isoyear}' => date('o', $theTime),
  1310. );
  1311. $wkcell = strtr($this->ISOWeekCell, $reps);
  1312. $c[] = '<td class="'.$isoClass.'">'.$wkcell.'</td>';
  1313. }
  1314. }
  1315. }
  1316. // fill in the final row
  1317. $left = 7 - ( $i%7 );
  1318. if ( $left < 7) {
  1319. for ( $j=0; $j<$left; $j++ ) {
  1320. $c[] = '<td class="'.$emptyClass.'">&nbsp;</td>';
  1321. }
  1322. $c[] = "\n\t</tr>";
  1323. }
  1324. return '<tbody>' . join('',$c) . '</tbody>';
  1325. }
  1326. // === end dspDayCells ===
  1327. /**
  1328. * outputs the contents for a given day
  1329. *
  1330. * @param integer, day
  1331. * @abstract
  1332. */
  1333. function dspDayCell ( $day ) {
  1334. return '<td>'.$day.'</td>';
  1335. }
  1336. // === end dayCell ===
  1337. } // end class
  1338. function smd_cal_minilink($row, $day, $month, $year, $use_posted=false) {
  1339. global $permlink_mode;
  1340. $lang = '';
  1341. $request = serverSet('REQUEST_URI');
  1342. $redirect = serverSet('REDIRECT_URL');
  1343. if (!empty($redirect) && ($request != $redirect) && is_callable('_l10n_set_browse_language')) {
  1344. // MLP in da house so extract the language currently in use -- is there an MLP-native method for this?
  1345. $reqparts = explode('/', $request);
  1346. $redparts = explode('/', $redirect);
  1347. $lang = join('', array_diff($redparts, $reqparts)) . '/';
  1348. }
  1349. if( $permlink_mode == 'year_month_day_title' ) {
  1350. $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);
  1351. $href = ' href="'.hu.$lang.$linkdate.'"';
  1352. } else {
  1353. $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);
  1354. $href = ' href="'.hu.$lang.'?date='.$linkdate;
  1355. if($row['Section']) { $href = $href.a.'s='.$row['Section']; }
  1356. // if($category) { $href = $href.a.'c='.$category; }
  1357. $href .= '"';
  1358. }
  1359. return tag($day, 'a', $href);
  1360. }
  1361. // Perform one of two types of test: a flag-based test, or an info-based test
  1362. function smd_if_cal($atts, $thing) {
  1363. global $smd_cal_flag, $smd_calinfo, $smd_date;
  1364. extract(lAtts(array(
  1365. 'flag' => '',
  1366. 'calid' => '',
  1367. 'isoyear' => '',
  1368. 'year' => '',
  1369. 'month' => '',
  1370. 'week' => '',
  1371. 'day' => '',
  1372. 'logic' => 'or',
  1373. 'debug' => '0',
  1374. ), $atts));
  1375. $flag = do_list($flag);
  1376. $ctr = $num = 0;
  1377. if ($debug) {
  1378. dmp($atts);
  1379. }
  1380. if ($flag && $flag[0] != '') {
  1381. $num += count($flag);
  1382. foreach ($flag as $whatnot) {
  1383. if (empty($whatnot)) continue;
  1384. $ctr += (in_array($whatnot, $smd_cal_flag) || ($whatnot == 'SMD_ANY' && !empty($smd_cal_flag))) ? 1 : 0;
  1385. }
  1386. }
  1387. if ($calid) {
  1388. $num++;
  1389. $ctr += ($smd_calinfo['id'] === $calid) ? 1 : 0;
  1390. }
  1391. foreach (array("iy" => "isoyear", "y" => "year", "m" => "month", "w" => "week", "d" => "day") as $idx => $test) {
  1392. $tester = $$test;
  1393. $compare = $smd_date[$idx];
  1394. if ($tester) {
  1395. $num++;
  1396. preg_match('/([!=<>]+)?([\d]+)/', $tester, $matches);
  1397. if ($debug) {
  1398. dmp("TEST IF: ". $compare. (($matches[1]) ? $matches[1] : '=') . $matches[2] );
  1399. }
  1400. switch ($matches[1]) {
  1401. case "!":
  1402. $ctr += ($compare!=$matches[2]) ? 1 : 0;
  1403. break;
  1404. case ">":
  1405. $ctr += ($compare>$matches[2]) ? 1 : 0;
  1406. break;
  1407. case ">=":
  1408. $ctr += ($compare>=$matches[2]) ? 1 : 0;
  1409. break;
  1410. case "<":
  1411. $ctr += ($compare<$matches[2]) ? 1 : 0;
  1412. break;
  1413. case "<=":
  1414. $ctr += ($compare<=$matches[2]) ? 1 : 0;
  1415. break;
  1416. default:
  1417. $ctr += ($compare==$matches[2]) ? 1 : 0;
  1418. break;
  1419. }
  1420. }
  1421. }
  1422. $result = (($ctr === $num && $logic == "and") || $ctr > 0 && $logic == "or") ? true : false;
  1423. return parse(EvalElse($thing, $result));
  1424. }
  1425. // Convenient wrapper for smd_cal_info use="event"
  1426. function smd_event_info($atts) {
  1427. $atts['use'] = 'event';
  1428. return smd_cal_info($atts);
  1429. }
  1430. // Grab additional information about the current event
  1431. function smd_cal_info($atts) {
  1432. global $pretext, $thisarticle, $smd_cal_flag, $smd_calinfo, $smd_date, $smd_eventinfo;
  1433. extract(lAtts(array(
  1434. 'type' => 'flag',
  1435. 'join' => ' ',
  1436. 'join_prefix' => 'SMD_AUTO',
  1437. 'html' => 0,
  1438. 'escape' => 'html',
  1439. 'use' => 'cal', // 'cal' for calendar (uses $smd_calinfo) or 'event' for event lists (uses $smd_eventinfo). Not publically alterable
  1440. 'debug' => 0,
  1441. ), $atts));
  1442. // Validate $use attribute
  1443. $use = (in_array($use, array('cal', 'event'))) ? $use : 'cal';
  1444. $cal_global = ${'smd_'.$use.'info'};
  1445. if ($debug && $thisarticle) {
  1446. echo '++ Event name ++';
  1447. dmp($thisarticle['title']);
  1448. }
  1449. if ($debug && $cal_global) {
  1450. echo '++ Available '.$use.' info ++';
  1451. dmp($cal_global);
  1452. }
  1453. if ($debug && $smd_date) {
  1454. echo '++ Available date info ++';
  1455. dmp($smd_date);
  1456. }
  1457. if ($debug && $smd_cal_flag) {
  1458. echo '++ Available flag info ++';
  1459. dmp($smd_cal_flag);
  1460. }
  1461. // Type: 0=date, 1=smd_cal/eventinfo, 2=pretext, 3=thisarticle, 4(or other)=user value
  1462. $map = array(
  1463. 'year' => array(0, 'y'),
  1464. 'isoyear' => array(0, 'iy', 'y'),
  1465. 'month' => array(0, 'm'),
  1466. 'week' => array(0, 'w'),
  1467. 'day' => array(0, 'd'),
  1468. 'section' => array(3, '', 's'),
  1469. 'category1' => array(3, '', 'c'),
  1470. 'category2' => array(3, '', 'c'),
  1471. 'thisid' => array(3, 'thisid', 'id'),
  1472. 'article' => array(1, 'artid', 'id'),
  1473. 'calid' => array(1, 'id', 'calid'),
  1474. 'category' => array(2, 'c'),
  1475. 'realname' => array(2, 'author'),
  1476. );
  1477. $join = ($html) ? a : $join; // html mode forces ampersand join
  1478. $type = do_list($type);
  1479. $ret = array();
  1480. foreach ($type as $item) {
  1481. $pts = do_list($item, ':');
  1482. $item = $pts[0];
  1483. if (empty($item)) continue;
  1484. // Default html id
  1485. $hid = (isset($map[$item])) ? ((isset($map[$item][2])) ? $map[$item][2] : $map[$item][1]) : $item;
  1486. // User-specified htmlid overrides it
  1487. $hid = (count($pts) > 1 && !empty($pts[1])) ? $pts[1] : $hid;
  1488. if ($item == "flag") {
  1489. $ret[] = (($join_prefix=="SMD_AUTO") ? $join : '').join($join, $smd_cal_flag);
  1490. } else if ($item == "author" || $item == "realname") {
  1491. $currauthor = ($thisarticle == NULL) ? '' : author(array());
  1492. if ($currauthor) {
  1493. $ret[] = (($html) ? $hid.'=' : '') . $currauthor;
  1494. }
  1495. } else if ($item == "s") {
  1496. $sec = (!empty($pretext['s'])) ? $pretext['s'] : ((isset($cal_global['s']) && !empty($cal_global['s'])) ? $cal_global['s'] : '');
  1497. if ($sec) {
  1498. $ret[] = (($html) ? $hid.'=' : '') . $sec;
  1499. }
  1500. } else if (isset($map[$item])) {
  1501. $typ = $map[$item][0];
  1502. $idx = empty($map[$item][1]) ? $item : $map[$item][1];
  1503. switch ($typ) {
  1504. case 0:
  1505. if ($smd_date[$idx]) {
  1506. $ret[] = (($html) ? $hid.'=' : '') . $smd_date[$idx];
  1507. }
  1508. break;
  1509. case 1:
  1510. if (!empty($cal_global[$idx])) {
  1511. $ret[] = (($html) ? $hid.'=' : '') . $cal_global[$idx];
  1512. }
  1513. break;
  1514. case 2:
  1515. if (!empty($pretext[$idx])) {
  1516. $ret[] = (($html) ? $hid.'=' : '') . $pretext[$idx];
  1517. }
  1518. break;
  1519. case 3:
  1520. if ($thisarticle != NULL && isset($thisarticle[$idx]) && !empty($thisarticle[$idx])) {
  1521. $ret[] = (($html) ? $hid.'=' : '') . $thisarticle[$idx];
  1522. }
  1523. break;
  1524. }
  1525. } else if (array_key_exists($item, $pretext)) {
  1526. if ($pretext[$item]) {
  1527. $ret[] = (($html) ? $hid.'=' : '') . $pretext[$item];
  1528. }
  1529. } else if (isset($cal_global[$item])) {
  1530. if (!empty($cal_global[$item])) {
  1531. $ret[] = (($html) ? $hid.'=' : '') . $cal_global[$item];
  1532. }
  1533. } else {
  1534. if ($thisarticle != NULL && isset($thisarticle[$item]) && !empty($thisarticle[$item])) {
  1535. $ret[] = (($html) ? $hid.'=' : '') . $thisarticle[$item];
  1536. }
  1537. }
  1538. }
  1539. $ret = array_unique($ret);
  1540. $out = (($join_prefix=="SMD_AUTO") ? (($html) ? '?' : '') : $join_prefix).join($join, $ret);
  1541. return ($escape=='html') ? htmlspecialchars($out) : $out;
  1542. }
  1543. // Return a formatted timestamp, with optional 'time now' override
  1544. function smd_cal_now($atts) {
  1545. global $dateformat;
  1546. extract(lAtts(array(
  1547. 'format' => $dateformat,
  1548. 'now' => '',
  1549. 'offset' => '',
  1550. 'gmt' => '',
  1551. 'lang' => '',
  1552. ), $atts));
  1553. $theDay = (gps('d') && is_numeric(gps('d'))) ? (int)gps('d') : safe_strftime('%d');
  1554. $theMonth = (gps('m') && is_numeric(gps('m'))) ? (int)gps('m') : safe_strftime('%m');
  1555. $theYear = (gps('y') && is_numeric(gps('y'))) ? (int)gps('y') : safe_strftime('%Y');
  1556. if ($now) {
  1557. $now = str_replace("?month", date('F', mktime(12,0,0,$theMonth,$theDay,$theYear)), $now);
  1558. $now = str_replace("?year", $theYear, $now);
  1559. $now = str_replace("?day", $theDay, $now);
  1560. $now = is_numeric($now) ? $now : strtotime($now);
  1561. } else {
  1562. $now = time();
  1563. }
  1564. if ($offset) {
  1565. $now = strtotime($offset, $now);
  1566. }
  1567. $format = smd_cal_reformat_win($format, $now);
  1568. return safe_strftime($format, $now, $gmt, $lang);
  1569. }
  1570. // Set user-defined classes for a cell
  1571. function smd_cal_class($atts) {
  1572. global $smd_cal_ucls;
  1573. extract(lAtts(array(
  1574. 'name' => '',
  1575. ), $atts));
  1576. $name = do_list($name);
  1577. $smd_cal_ucls = array_merge($smd_cal_ucls, $name);
  1578. }
  1579. // <txp:article_custom /> replacement(ish) tag that understands how to handle recurring events
  1580. function smd_article_event($atts, $thing=NULL) {
  1581. global $prefs, $pretext, $thispage, $thisarticle, $smd_eventinfo, $smd_cal_flag, $smd_date;
  1582. extract(lAtts(array(
  1583. 'time' => 'any',
  1584. 'type' => 'standard,recur,multi',
  1585. 'expired' => '',
  1586. 'id' => '',
  1587. 'category' => null,
  1588. 'section' => '',
  1589. 'author' => '',
  1590. 'realname' => '',
  1591. 'custom' => '',
  1592. 'status' => 'live',
  1593. 'param_delim' => ':',
  1594. 'sort' => '',
  1595. 'form' => '',
  1596. 'stepfield' => '',
  1597. 'skipfield' => '',
  1598. 'omitfield' => '',
  1599. 'extrafield' => '',
  1600. 'allspanned' => '0',
  1601. 'datefields' => '',
  1602. 'month' => '',
  1603. 'from' => '',
  1604. 'to' => '',
  1605. 'offset' => 0,
  1606. 'limit' => '10',
  1607. 'eventlimit' => '10',
  1608. 'paging' => '1',
  1609. 'pageby' => '',
  1610. 'pgonly' => '',
  1611. 'wraptag' => '',
  1612. 'break' => '',
  1613. 'class' => '',
  1614. 'debug' => 0,
  1615. ), $atts));
  1616. // Phase 1 filters
  1617. $filtSQL = array();
  1618. $subSQL = array();
  1619. if ($category !== null) {
  1620. $uncats = false;
  1621. $allcats = do_list($category);
  1622. if (($pos = array_search('SMD_UNCAT', $allcats)) !== false) {
  1623. $uncats = true;
  1624. unset($allcats[$pos]);
  1625. $category = join(',', $allcats);
  1626. }
  1627. $tmp = doQuote(join("','", doSlash(do_list($category))));
  1628. $filtSQL[] = ($uncats ? "(Category1 = '' AND Category2 = '')" : '') .
  1629. ($uncats && $allcats ? " OR " : '') .
  1630. ($allcats ? '( Category1 IN ('.$tmp.') OR Category2 IN ('.$tmp.') )' : '');
  1631. }
  1632. if($section) {
  1633. $filtSQL[] = 'Section IN ('.doQuote(join("','", doSlash(do_list($section)))).')';
  1634. }
  1635. if($realname) {
  1636. $authors = safe_column('name', 'txp_users', 'RealName IN ('. doQuote(join("','", doArray(do_list($realname), 'urldecode'))) .')' );
  1637. $author = join(',', $authors);
  1638. }
  1639. if($author) {
  1640. $filtSQL[] = 'AuthorID IN ('.doQuote(join("','", doSlash(do_list($author)))).')';
  1641. }
  1642. if($id) {
  1643. $filtSQL[] = 'ID IN ('.join(',', array_map('intval', do_list($id))).')';
  1644. }
  1645. if($custom) {
  1646. $custs = do_list($custom);
  1647. $validOps = array('=', '!=', '>', '>=', '<', '<=', 'like', 'not', 'not like');
  1648. foreach ($custs as $set) {
  1649. if (strpos($set, $param_delim) !== false) {
  1650. $clauseOpts = do_list($set, $param_delim);
  1651. $fld = $clauseOpts[0];
  1652. $oper = ((count($clauseOpts) == 3) && (in_array(strtolower($clauseOpts[1]), $validOps))) ? $clauseOpts[1] : '=';
  1653. $clause = (count($clauseOpts) == 3) ? $clauseOpts[2] : ((count($clauseOpts) == 2) ? $clauseOpts[1] : '');
  1654. $filtSQL[] = $fld . " $oper " . doQuote(doSlash($clause));
  1655. }
  1656. }
  1657. }
  1658. $type = do_list($type);
  1659. $pageby = (empty($pageby) ? $limit : $pageby);
  1660. foreach ($type as $evtyp) {
  1661. switch($evtyp) {
  1662. case 'standard':
  1663. if ($stepfield) {
  1664. $subSQL[] = "(".$stepfield." = '' AND Expires = ".NULLDATETIME.")";
  1665. }
  1666. break;
  1667. case 'recur':
  1668. if ($stepfield) {
  1669. $subSQL[] = "(".$stepfield." != '')";
  1670. }
  1671. break;
  1672. case 'multi':
  1673. if ($stepfield) {
  1674. $subSQL[] = "(".$stepfield." = '' AND Expires != ".NULLDATETIME.")";
  1675. }
  1676. break;
  1677. }
  1678. }
  1679. if ($subSQL) {
  1680. $filtSQL[] = '('.join(' OR ', $subSQL).')';
  1681. }
  1682. $status = ($status) ? $status : 'live'; // in case status has been emptied
  1683. $status = do_list($status);
  1684. $stati = array();
  1685. foreach ($status as $stat) {
  1686. if (empty($stat)) {
  1687. continue;
  1688. } else if (is_numeric($stat)) {
  1689. $stati[] = $stat;
  1690. } else {
  1691. $stati[] = getStatusNum($stat);
  1692. }
  1693. }
  1694. $filtSQL[] = 'Status IN ('.doQuote(join("','", $stati)).')';
  1695. $expired = ($expired) ? $expired : $prefs['publish_expired_articles'];
  1696. if (!$expired) {
  1697. $filtSQL[] = '(now() <= Expires OR Expires = '.NULLDATETIME.')';
  1698. }
  1699. // Sorting rules: data is sorted once as it is extracted via SQL and then again after the fake dates have been inserted
  1700. $sort = (empty($sort)) ? 'Posted asc' : $sort;
  1701. $sort = do_list($sort);
  1702. $sortPrefix = "SORT_";
  1703. $sortOrder = array();
  1704. for ($idx = 0; $idx < count($sort); $idx++) {
  1705. $sorties = explode(' ', $sort[$idx]);
  1706. if (count($sorties) <= 1) {
  1707. $sorties[1] = "asc";
  1708. }
  1709. $sorties[1] = $sortPrefix.(($sorties[1] == "desc") ? 'DESC' : 'ASC');
  1710. $sortOrder[] = array("by" => $sorties[0], "dir" => $sorties[1]);
  1711. }
  1712. $filtSQL = join(' AND ', $filtSQL);
  1713. $filtSQL .= ' ORDER BY '.join(',',doSlash($sort));
  1714. $grabCols = '*, unix_timestamp(Posted) as uPosted, unix_timestamp(LastMod) as uLastMod, unix_timestamp(Expires) as uExpires';
  1715. $evlist = safe_rows($grabCols, 'textpattern', $filtSQL, $debug);
  1716. if ($debug>2) {
  1717. echo "++ RECORD SET ++";
  1718. dmp($evlist);
  1719. }
  1720. $all_evs = array();
  1721. $ev_tally = array();
  1722. $now = time() + tz_offset();
  1723. $eventlimit = do_list($eventlimit);
  1724. if (count($eventlimit) == 1) {
  1725. $eventlimit[1] = $eventlimit[0];
  1726. }
  1727. $datefields = do_list($datefields);
  1728. // Phase 2: expand any recurring dates and collate all events that fall within the alloted ranges
  1729. foreach ($evlist as $row) {
  1730. $ev_posted = (!empty($datefields[0]) && !empty($row[$datefields[0]]) && ($stdt = strtotime($row[$datefields[0]])) !== false) ? $stdt : $row['uPosted']+tz_offset($row['uPosted']);
  1731. $ev_expires = (isset($datefields[1]) && !empty($row[$datefields[1]]) && ($endt = strtotime($row[$datefields[1]])) !== false) ? $endt : (($row['uExpires']==0) ? 0 : $row['uExpires']+tz_offset($row['uExpires']));
  1732. $skip = ($skipfield && $row[$skipfield] != '');
  1733. $omit = ($omitfield && $row[$omitfield] != '');
  1734. $recur = ($stepfield && $row[$stepfield] != '');
  1735. $extra = ($extrafield && $row[$extrafield] != '');
  1736. $multi = ($ev_expires > $ev_posted && (date("d-m-Y", $ev_expires) != date("d-m-Y", $ev_posted))) ? true : false;
  1737. // If end < start the user-specified dates cannot be trusted
  1738. if ($ev_expires != 0 && $ev_expires <= $ev_posted) {
  1739. $ev_posted = $row['uPosted']+tz_offset($row['uPosted']);
  1740. $ev_expires = (($row['uExpires']==0) ? 0 : $row['uExpires']+tz_offset($row['uExpires']));
  1741. trigger_error('Expiry cannot be before start date in "'.$row['Title'].'": ignored', E_USER_WARNING);
  1742. }
  1743. if ($debug > 1) {
  1744. echo '++ EVENT START // END ++';
  1745. dmp($row['Title']);
  1746. dmp($ev_posted, date('Y-m-d H:i:s', $ev_posted), $ev_expires, date('Y-m-d H:i:s', $ev_expires));
  1747. }
  1748. // Rewrite the start/end dates in case they are user-defined
  1749. $row['uPosted'] = $ev_posted;
  1750. $row['Posted'] = date("Y-m-d H:i:s", $ev_posted);
  1751. $row['uExpires'] = $ev_expires;
  1752. $row['Expires'] = ($ev_expires==0) ? '0000-00-00 00:00:00' : date("Y-m-d H:i:s", $ev_expires);
  1753. $diff = ($ev_expires == 0) ? 0 : $ev_expires - $ev_posted;
  1754. $ev_month = date('m', $ev_posted);
  1755. $ev_year = date('Y', $ev_posted);
  1756. $ev_hr = date('H', $ev_posted);
  1757. $ev_mn = date('i', $ev_posted);
  1758. $ev_sc = date('s', $ev_posted);
  1759. $ignore = array();
  1760. // Generate a skip array for this event
  1761. if ($skip) {
  1762. $ignores = do_list($row[$skipfield]);
  1763. foreach ($ignores as $val) {
  1764. $igrng = smd_expand_daterange($val, $ev_posted, $ev_expires);
  1765. foreach ($igrng as $theval) {
  1766. $ignore[] = date("d-M-Y", $theval); // Force each date to a known format
  1767. }
  1768. }
  1769. }
  1770. // Append any omitted events
  1771. if ($omit) {
  1772. $omits = do_list($row[$omitfield]);
  1773. foreach ($omits as $val) {
  1774. $omrng = smd_expand_daterange($val, $ev_posted, $ev_expires);
  1775. foreach ($omrng as $theval) {
  1776. $ignore[] = date("d-M-Y", $theval);
  1777. }
  1778. }
  1779. }
  1780. if ($debug > 1 && $ignore) {
  1781. echo '++ IGNORED DATES ++';
  1782. dmp($ignore);
  1783. }
  1784. // Does the base event deserve to be in the results?
  1785. if (smd_include_event($ev_posted, $now, $ignore, $time, $from, $to, $month)) {
  1786. $all_evs[] = array('ev' => $row, 'flags' => ($multi ? array('multifirst') : array('standard')) );
  1787. $ev_tally[$row['uPosted']] = (isset($ev_tally[$row['uPosted']])) ? $ev_tally[$row['uPosted']]+1 : 1;
  1788. }
  1789. // Add any extra dates for this event
  1790. if ($extra) {
  1791. $xtra = do_list($row[$extrafield]);
  1792. $xtras = array();
  1793. // Make up an array of all extra dates
  1794. foreach ($xtra as $val) {
  1795. if (strpos($val, "+") === false) {
  1796. $exrng = smd_expand_daterange($val);
  1797. $xtras[] = date("Y-m-d", $exrng[0]);
  1798. $spex = 0;
  1799. } else {
  1800. $fake_diff = safe_strtotime(date("Y-M-d", $ev_expires) . " 23:59:59");
  1801. $fdiff = ($ev_expires==0) ? 0 : $fake_diff - $ev_posted;
  1802. $chk = $allspanned && $multi && !$recur;
  1803. $spidth = $chk ? ceil($fdiff / (60*60*24)) : 1; // days between dates
  1804. $val = rtrim($val, '+');
  1805. for ($jdx = 1; $jdx <= $spidth; $jdx++) {
  1806. $xtras[] = date("Y-m-d", safe_strtotime($val . (($jdx==1) ? '' : '+'.($jdx-1).' days')));
  1807. }
  1808. $spex = $chk ? 1 : 0;
  1809. }
  1810. }
  1811. $xtras = array_unique($xtras);
  1812. $stampoff = (int)(3600*$ev_hr) + (int)(60*$ev_mn) + (int)$ev_sc;
  1813. foreach ($xtras as $jdx => $val) {
  1814. $tm = strtotime($val);
  1815. $flags = array('extra');
  1816. // No $ignore for additional events, as they always show up
  1817. if (smd_include_event($tm+$stampoff, $now, array(), $time, $from, $to, $month)) {
  1818. $fakerow = $row;
  1819. $fakerow['Posted'] = date("Y-m-d H:i:s", $tm+$stampoff);
  1820. $fakerow['uPosted'] = $tm+$stampoff;
  1821. if ($diff > 0) {
  1822. $fakerow['Expires'] = date("Y-m-d H:i:s", $tm+$stampoff+$diff);
  1823. $fakerow['uExpires'] = $tm+$stampoff+$diff;
  1824. }
  1825. if ($spex) {
  1826. $flags[] = ($jdx==0) ? 'multifirst' : (($jdx==$spidth-1) ? 'multilast' : 'multi');
  1827. }
  1828. $all_evs[] = array('ev' => $fakerow, 'flags' => $flags);
  1829. $ev_tally[$fakerow['uPosted']] = (isset($ev_tally[$fakerow['uPosted']])) ? $ev_tally[$fakerow['uPosted']]+1 : 1;
  1830. }
  1831. }
  1832. }
  1833. if ($recur) {
  1834. $flags = array('recurfirst');
  1835. $freq = do_list($row[$stepfield]);
  1836. $monthly = false;
  1837. $currmonth = $ev_month;
  1838. $curryear = $ev_year;
  1839. foreach ($freq as $interval) {
  1840. $fakerow = $row;
  1841. $cstamp = $ev_posted;
  1842. for($idx = 0; $idx < 99999; $idx++) {
  1843. $lstamp = $cstamp;
  1844. if ((isset($ev_tally[$row['uPosted']]) && ($ev_tally[$row['uPosted']] >= $eventlimit[0])) || ($to && $cstamp > safe_strtotime($to))) {
  1845. break;
  1846. }
  1847. $ival = str_replace("?month", date('F', mktime(0,0,0,$currmonth,1)), $interval);
  1848. $ival = str_replace("?year", $curryear, $ival);
  1849. if (strpos($ival, "last") === 0) {
  1850. $ival = date("l, F jS Y", strtotime( $ival, mktime(12, 0, 0, date("n", mktime(0,0,0,$currmonth,1,$curryear))+1, 1, $curryear) ));
  1851. $monthly = true;
  1852. } else if (strpos($ival, "first") === 0) {
  1853. $ival = date("l, F jS Y", strtotime( $ival, mktime(12, 0, 0, (($currmonth>1) ? $currmonth-1 : 12), date("t", mktime(0,0,0,$currmonth-1,1,(($currmonth==1) ? $curryear-1: $curryear)) ), (($currmonth==1) ? $curryear-1: $curryear)) ));
  1854. $monthly = true;
  1855. } else if (strpos($ival, "this") === 0) {
  1856. $monthly = true;
  1857. }
  1858. if (strpos($interval, "?month") || strpos($interval, "?year")) {
  1859. $monthly = true;
  1860. }
  1861. if ($monthly) {
  1862. $cstamp = strtotime($ival);
  1863. } else {
  1864. $cstamp = strtotime($ival, $cstamp);
  1865. }
  1866. // This kludge takes account of timestamps like "last Thursday" (of the month). The last 'whatever day' of
  1867. // a month can only be a maximum of 31 days before the last timestamp we saw, so check for that (+/- 10 mins)
  1868. $diffstamp = $cstamp - $lstamp;
  1869. if ($diffstamp < 0) {
  1870. if ($diffstamp > -(60*60*24*31)+600) {
  1871. $cstamp = false; // Some 'last weekday' of the previous month
  1872. } else {
  1873. break; // PHP_INT_MAX exceeded
  1874. }
  1875. }
  1876. if ($cstamp !== false) {
  1877. if ($debug > 1) {
  1878. dmp("INTERVAL: ". $cstamp . ' // ' .date('d-M-Y H:i:s', $cstamp));
  1879. }
  1880. if (($cstamp < $ev_expires || $ev_expires == '0') && ($cstamp != $ev_posted)) {
  1881. $show_me = smd_include_event($cstamp, $now, $ignore, $time, $from, $to, $month);
  1882. if ($show_me) {
  1883. $flags[] = 'recur';
  1884. $fakerow['Posted'] = date("Y-m-d H:i:s", $cstamp);
  1885. $fakerow['uPosted'] = $cstamp;
  1886. $all_evs[] = array('ev' => $fakerow, 'flags' => $flags);
  1887. $ev_tally[$row['uPosted']] = (isset($ev_tally[$row['uPosted']])) ? $ev_tally[$row['uPosted']]+1 : 1;
  1888. $flags = array(); // reset so recurfirst is removed
  1889. }
  1890. } else {
  1891. break;
  1892. }
  1893. }
  1894. // Increment the month/year ready for the next interval
  1895. if ($monthly) {
  1896. $curryear = ($currmonth==12) ? $curryear+1 : $curryear;
  1897. $currmonth = ($currmonth==12) ? 1 : $currmonth+1;
  1898. }
  1899. }
  1900. if ($debug>1) {
  1901. if (isset($ev_tally[$row['uPosted']])) {
  1902. dmp("TALLY: ". $ev_tally[$row['uPosted']]);
  1903. }
  1904. }
  1905. }
  1906. } else if ($allspanned && date("Y-M-d", $ev_expires) != date("Y-M-d", $ev_posted)) {
  1907. $postdate = date("Y-M-d H:i:s", $ev_posted);
  1908. $fake_diff = safe_strtotime(date("Y-M-d", $ev_expires) . " 23:59:59");
  1909. $diff = ($ev_expires==0) ? 0 : $fake_diff - $ev_posted;
  1910. $spidth = ceil($diff / (60*60*24)); // days between dates
  1911. for ($jdx = 1; $jdx < $spidth; $jdx++) {
  1912. $flags = array();
  1913. $tm = safe_strtotime($postdate.'+'.$jdx.' days');
  1914. $show_me = smd_include_event($tm, $now, $ignore, $time, $from, $to, $month);
  1915. if ($show_me) {
  1916. $flags[] = ($jdx==$spidth-1) ? 'multilast' : 'multi';
  1917. $fakerow = $row;
  1918. $fakerow['Posted'] = date("Y-m-d H:i:s", $tm);
  1919. $fakerow['uPosted'] = $tm;
  1920. $all_evs[] = array('ev' => $fakerow, 'flags' => $flags);
  1921. }
  1922. }
  1923. }
  1924. }
  1925. if ($debug>2) {
  1926. echo "++ PRE-SORTED ++";
  1927. dmp($all_evs);
  1928. }
  1929. // Make up an array_multisort arg list and execute it
  1930. foreach($all_evs as $key => $entry) {
  1931. $row = $entry['ev'];
  1932. foreach ($row as $identifier => $item) {
  1933. $varname = "col_".$identifier;
  1934. ${$varname}[$key] = $item;
  1935. }
  1936. }
  1937. if(count($all_evs) > 0) {
  1938. for ($idx = 0; $idx < count($sortOrder); $idx++) {
  1939. $sortargs[] = '$col_'.$sortOrder[$idx]['by'];
  1940. $sortargs[] = $sortOrder[$idx]['dir'];
  1941. }
  1942. $sortit = 'array_multisort('.implode(", ",$sortargs).', $all_evs);';
  1943. eval($sortit);
  1944. }
  1945. if ($debug>2) {
  1946. echo "++ POST-SORTED ++";
  1947. dmp($all_evs);
  1948. }
  1949. // Handle paging
  1950. if ($paging) {
  1951. $grand_total = count($all_evs);
  1952. $total = $grand_total - $offset;
  1953. $numPages = ceil($total/$pageby);
  1954. $pg = (!$pretext['pg']) ? 1 : $pretext['pg'];
  1955. $pgoffset = $offset + (($pg - 1) * $pageby);
  1956. // send paging info to txp:newer and txp:older
  1957. $pageout['pg'] = $pg;
  1958. $pageout['numPages'] = $numPages;
  1959. $pageout['s'] = $pretext['s'];
  1960. $pageout['c'] = $pretext['c'];
  1961. $pageout['grand_total'] = $grand_total;
  1962. $pageout['total'] = $total;
  1963. if (empty($thispage))
  1964. $thispage = $pageout;
  1965. if ($pgonly)
  1966. return;
  1967. } else {
  1968. $pgoffset = $offset;
  1969. }
  1970. // Phase 3: iterate over the new array obeying any offset/limit. Anything in the range gets populated and parsed
  1971. $out = array();
  1972. $ctr = 0;
  1973. article_push();
  1974. $lastposted = 0;
  1975. foreach ($all_evs as $idx => $entry) {
  1976. $smd_cal_flag = array();
  1977. $smd_date = array();
  1978. $smd_eventinfo = array();
  1979. if ($idx >= $pgoffset && $ctr < $limit) {
  1980. $row = $entry['ev'];
  1981. $smd_cal_flag = $entry['flags'];
  1982. $thisposted = date('Y-m-d', $row['uPosted']);
  1983. $nextposted = isset($all_evs['ev'][$idx+1]) ? date('Y-m-d', $all_evs['ev'][$idx+1]['uPosted']) : 0;
  1984. // Adjust times so txp:posted/expires return correct stamps
  1985. $row['Posted'] = $row['Posted']-tz_offset(strtotime($row['Posted']));
  1986. $row['uPosted'] = $row['uPosted']-tz_offset($row['uPosted']);
  1987. $row['Expires'] = ($row['uExpires'] == 0) ? '0000-00-00 00:00:00' : $row['Expires']-tz_offset(strtotime($row['Expires']));
  1988. $row['uExpires'] = ($row['uExpires'] == 0) ? 0 : $row['uExpires']-tz_offset($row['uExpires']);
  1989. // Populate additional event information
  1990. $fakestamp = ($row['uExpires'] == 0) ? 0 : strtotime(date("Y-M-d", $row['uExpires']) . " 23:59:59");
  1991. $smd_eventinfo['duration'] = ($row['uExpires'] == 0) ? 0 : $row['uExpires'] - $row['uPosted'];
  1992. $smd_eventinfo['durationdays'] = ($fakestamp) ? ceil(($fakestamp - $row['uPosted']) / (60*60*24)) : 0;
  1993. $smd_date['y'] = strftime('%Y', $row['uPosted']);
  1994. $smd_date['m'] = strftime('%m', $row['uPosted']);
  1995. $smd_date['d'] = (int)strftime('%d', $row['uPosted']);
  1996. $smd_date['w'] = strftime(smd_cal_reformat_win('%V', $row['uPosted']), $row['uPosted']);
  1997. $smd_date['iy'] = strftime(smd_cal_reformat_win('%G', $row['uPosted']), $row['uPosted']);
  1998. if ($row['uExpires'] == 0) {
  1999. $smd_date['expy'] = $smd_date['expm'] = $smd_date['expd'] = $smd_date['expw'] = $smd_date['expiy'] = '';
  2000. } else {
  2001. $smd_date['expy'] = strftime('%Y', $row['uExpires']);
  2002. $smd_date['expm'] = strftime('%m', $row['uExpires']);
  2003. $smd_date['expd'] = (int)strftime('%d', $row['uExpires']);
  2004. $smd_date['expw'] = strftime(smd_cal_reformat_win('%V', $row['uExpires']), $row['uExpires']);
  2005. $smd_date['expiy'] = strftime(smd_cal_reformat_win('%G', $row['uExpires']), $row['uExpires']);
  2006. }
  2007. populateArticleData($row);
  2008. $thisarticle['is_first'] = ($thisposted != $lastposted);
  2009. $thisarticle['is_last'] = ($thisposted != $nextposted);
  2010. $lastposted = $thisposted;
  2011. $out[] = ($thing) ? parse($thing) : (($form) ? parse_form($form) : href($row['Posted'], permlinkurl($row), ' title="'.$row['Title'].'"') );
  2012. $ctr++;
  2013. }
  2014. }
  2015. article_pop();
  2016. return doWrap($out, $wraptag, $break, $class);
  2017. }
  2018. // Try and output "nice" dates that read well across month/year boundaries.
  2019. // For example: 28 May - 05 Jun 2011 or May 11-16 2011, depending on format
  2020. function smd_event_duration($atts) {
  2021. extract(lAtts(array(
  2022. 'start' => posted(array('format' => '%s')),
  2023. 'end' => expires(array('format' => '%s')),
  2024. 'format' => '%d %b %Y',
  2025. 'separator' => ' &ndash; ',
  2026. 'debug' => 0,
  2027. ), $atts));
  2028. // Extract the relevant portions of the format so we can muck about with them
  2029. preg_match_all('/\%([dejbBmhgGyY])/', $format, $matches);
  2030. $indexes = array('day' => '', 'month' => '', 'year' => '');
  2031. foreach($matches[1] as $idx => $token) {
  2032. switch ($token) {
  2033. case 'd':
  2034. case 'e':
  2035. case 'j':
  2036. $indexes['day'] = $idx;
  2037. break;
  2038. case 'b':
  2039. case 'B':
  2040. case 'm':
  2041. case 'h':
  2042. $indexes['month'] = $idx;
  2043. break;
  2044. case 'g':
  2045. case 'G':
  2046. case 'y':
  2047. case 'Y':
  2048. $indexes['year'] = $idx;
  2049. break;
  2050. }
  2051. }
  2052. $day_first = ($indexes['day'] < $indexes['month']);
  2053. $year_first = ($indexes['year'] == 0);
  2054. $has_year = false;
  2055. if ($end) {
  2056. if (strftime('%Y %m %d', $start) == strftime('%Y %m %d',$end)) {
  2057. // begin and end on same day
  2058. $s_format = '';
  2059. $e_format = $format;
  2060. $has_year = true;
  2061. } else {
  2062. if (strftime('%Y', $start) == strftime('%Y', $end)) {
  2063. // same year
  2064. if (strftime('%m', $start) == strftime('%m', $end)) {
  2065. // and same month
  2066. $re1 = ($day_first) ? '/\%[bBmhgGyY]/' : '/\%[gGyY]/';
  2067. $re2 = ($day_first) ? '/\%[gGyY]/' : '/\%[bBmhgGyY]/';
  2068. $s_format = trim(preg_replace($re1, '', $format));
  2069. $e_format = ($re2) ? trim(preg_replace($re2, '', $format)) : $format;
  2070. } else {
  2071. // not same month
  2072. $s_format = $e_format = trim(preg_replace('/\%[gGyY]/', '', $format));
  2073. }
  2074. } else {
  2075. // different year
  2076. $s_format = $e_format = $format;
  2077. $has_year = true;
  2078. }
  2079. $s_format .= $separator;
  2080. }
  2081. // Add the year back in the correct position
  2082. $s_format = ($has_year) ? $s_format : (($year_first) ? $matches[0][$indexes['year']] . ' ' . $s_format : $s_format);
  2083. $e_format = ($has_year) ? $e_format : (($year_first) ? $e_format : $e_format . ' ' . $matches[0][$indexes['year']]);
  2084. } else {
  2085. $s_format = $format;
  2086. $e_format ='';
  2087. }
  2088. return (($s_format) ? strftime($s_format, $start) : '') . (($e_format) ? strftime($e_format, $end) : '');
  2089. }
  2090. // An unoptimized workaround when "%V" and "%G" fails (usually on Windows)
  2091. // Algorithm adapted from http://www.personal.ecu.edu/mccartyr/ISOwdALG.txt with thanks.
  2092. // All other shortcut algorithms failed edge cases
  2093. function smd_cal_iso_week($format='%V', $time = null) {
  2094. if (!$time) $time = time();
  2095. $yr = strftime("%Y", $time);
  2096. $leap = ( ( ($yr % 4 == 0) && ($yr % 100 != 0) ) || $yr % 400 == 0 );
  2097. $leap_prev = ( ( (($yr-1) % 4 == 0) && (($yr-1) % 100 != 0) ) || ($yr-1) % 400 == 0 );
  2098. $day_of_year = strftime('%j', $time);
  2099. // Find the weekday of Jan 1st in the given year
  2100. $yy = ($yr - 1) % 100;
  2101. $c = ($yr - 1) - $yy;
  2102. $g = $yy + ($yy / 4);
  2103. $jan1weekday = 1 + ((((($c / 100) % 4) * 5) + $g) % 7);
  2104. // Find weekday ( could use: $weekday = strftime('%u', $time); )
  2105. $h = $day_of_year + ($jan1weekday - 1);
  2106. $weekday = 1 + ( ($h - 1) % 7);
  2107. // Find if $time falls in iso_year Y-1, iso_week 52 or 53
  2108. if (($day_of_year <= (8 - $jan1weekday)) && $jan1weekday > 4) {
  2109. $iso_year = $yr - 1;
  2110. if ($jan1weekday == 5 || ($jan1weekday == 6 && $leap_prev)) {
  2111. $iso_week = 53;
  2112. } else {
  2113. $iso_week = 52;
  2114. }
  2115. } else {
  2116. $iso_year = $yr;
  2117. }
  2118. // Find if $time falls in iso_year Y+1, iso_week 1
  2119. if ($iso_year == $yr) {
  2120. $idx = ($leap) ? 366 : 365;
  2121. if ( ($idx - $day_of_year) < (4 - $weekday) ) {
  2122. $iso_year = $yr + 1;
  2123. $iso_week = 1;
  2124. }
  2125. }
  2126. // Find if $time falls in iso_year Y, iso_week 1 thru 53
  2127. if ($iso_year == $yr) {
  2128. $jdx = $day_of_year + (7 - $weekday) + ($jan1weekday - 1);
  2129. $iso_week = $jdx / 7;
  2130. if ($jan1weekday > 4) {
  2131. $iso_week--;
  2132. }
  2133. }
  2134. // Replacement array
  2135. $reps = array(
  2136. '%V' => str_pad($iso_week, 2, '0', STR_PAD_LEFT),
  2137. '%G' => $iso_year,
  2138. '%g' => substr($iso_year, 2),
  2139. );
  2140. return strtr($format, $reps);
  2141. }
  2142. // Adapted from: http://php.net/manual/en/function.strftime.php
  2143. function smd_cal_reformat_win($format, $ts = null) {
  2144. // Only Win platforms need apply
  2145. if (!is_windows()) return $format;
  2146. if (!$ts) $ts = time();
  2147. $mapping = array(
  2148. '%C' => sprintf("%02d", date("Y", $ts) / 100),
  2149. '%D' => '%m/%d/%y',
  2150. '%e' => sprintf("%' 2d", date("j", $ts)),
  2151. '%F' => '%Y-%m-%d',
  2152. '%g' => smd_cal_iso_week('%g', $ts),
  2153. '%G' => smd_cal_iso_week('%G', $ts),
  2154. '%h' => '%b',
  2155. '%l' => sprintf("%' 2d", date("g", $ts)),
  2156. '%n' => "\n",
  2157. '%P' => date('a', $ts),
  2158. '%r' => date("h:i:s", $ts) . " %p",
  2159. '%R' => date("H:i", $ts),
  2160. '%s' => date('U', $ts),
  2161. '%t' => "\t",
  2162. '%T' => '%H:%M:%S',
  2163. '%u' => ($w = date("w", $ts)) ? $w : 7,
  2164. '%V' => smd_cal_iso_week('%V', $ts),
  2165. );
  2166. $format = str_replace(
  2167. array_keys($mapping),
  2168. array_values($mapping),
  2169. $format
  2170. );
  2171. return $format;
  2172. }
  2173. // Find if the haystack contains one of the values in needle. Is there a cleverer way to do this?
  2174. function smd_cal_in_array($needle, $haystack) {
  2175. foreach ($haystack as $val) {
  2176. if (in_array($val, $needle)) {
  2177. return true;
  2178. }
  2179. }
  2180. return false;
  2181. }
  2182. // Check the passed timestamp against every time restriction and return true if it passes them all
  2183. function smd_include_event($ts, $now, $ign, $time, $from, $to, $month) {
  2184. $show = array();
  2185. $show[] = !in_array(date("d-M-Y", $ts), $ign);
  2186. $time = do_list($time);
  2187. $showor = false;
  2188. foreach($time as $tm) {
  2189. switch($tm) {
  2190. case "any":
  2191. $showor = true;
  2192. break;
  2193. case "future":
  2194. $showor = $showor || (($ts > $now) ? true : false);
  2195. break;
  2196. case "today":
  2197. $showor = $showor || (($ts >= strtotime(date('Y-m-d 00:00:00', $now)) && $ts <= strtotime(date('Y-m-d 23:59:59', $now))) ? true : false);
  2198. break;
  2199. default :
  2200. $showor = $showor || (($ts < $now) ? true : false);
  2201. break;
  2202. }
  2203. }
  2204. $show[] = $showor;
  2205. if ($from) { $show[] = ($ts >= safe_strtotime($from)) ? true : false; }
  2206. if ($to) { $show[] = ($ts <= safe_strtotime($to)) ? true : false; }
  2207. if ($month) { $show[] = (date("Y-m", $ts) == $month) ? true : false; }
  2208. return (!in_array(0, $show)) ? true : false;
  2209. }
  2210. // Convert date ranges like 24-Oct-08 => 5-Nov-08 to an array of discrete date entities
  2211. // Also, weekday vals such as {Sun:Mon:Wed} would return those days between $start and $end
  2212. function smd_expand_daterange($range, $start='', $end='', $fmt='%s') {
  2213. $out = array();
  2214. $rng = do_list($range, "=>");
  2215. if (count($rng) > 1) {
  2216. // Range expansion
  2217. $diff = safe_strtotime($rng[1]) - safe_strtotime($rng[0]);
  2218. $diffdays = ceil($diff / (60*60*24)); // days between dates
  2219. for ($jdx = 0; $jdx <= $diffdays; $jdx++) {
  2220. $out[] = safe_strftime($fmt, safe_strtotime($rng[0] . (($jdx==0) ? '' : '+'.$jdx.' days')));
  2221. }
  2222. } else if ($start && $end && strpos($range, '{') === 0 && strpos($range, '}') === strlen($range)-1) {
  2223. // Day of week expansion
  2224. $days = do_list(trim($range,'{}'), ':');
  2225. $diffdays = ceil(($end-$start) / (60*60*24));
  2226. for ($jdx = 0; $jdx <= $diffdays; $jdx++) {
  2227. $tm = $start + ($jdx*60*60*24);
  2228. if (in_array(date('D', $tm), $days)) {
  2229. $out[] = safe_strftime($fmt, $tm);
  2230. }
  2231. }
  2232. } else {
  2233. // Single date
  2234. $out[] = safe_strftime($fmt, safe_strtotime($rng[0]));
  2235. }
  2236. return $out;
  2237. }
  2238. # --- END PLUGIN CODE ---
  2239. if (0) {
  2240. ?>
  2241. <!--
  2242. # --- BEGIN PLUGIN HELP ---
  2243. h1. smd_calendar
  2244. Render a calendar with one or more articles as events on each day. Useful for gig guides, "what's on" or scheduling apps.
  2245. h2. Features
  2246. * Full-size / mini "calendar":#smdcal by month, with ISO week support
  2247. * Nav: next/prev month or month/year dropdown. Year range can be from first/last article or +/- N years
  2248. * One article = one event, native to TXP: posted date = the date it appears in the calendar (overrideable)
  2249. * Filter events by cat / section / author / status / time / expiry
  2250. * Specify event frequency in custom field (1 week / 10 days / 3 months / etc)
  2251. * Custom fields for in/exclusions (dates on which an event is (re)scheduled/cancelled/omitted)
  2252. * Multi-day spanned events based on article expiry
  2253. * Display future, expired or sticky events
  2254. * Holidays per-calendar
  2255. * Pass each event to a form/container. Spanned and/or recurring events can be sent to a separate form. Cell format also customisable
  2256. * "Conditional":#ifcal tests for flags and dates so you can build your own logic
  2257. * Table-, row-, cell-, and event-level classes for indicating different scenarios
  2258. * Tags to "display recurring events":#artev; event/calendar "characteristics":#calinfo; or the "current date/time":#calnow
  2259. h2. Installation / Uninstallation
  2260. p(warning). Requires Textpattern 4.4.1+
  2261. Download the plugin from either "textpattern.org":http://textpattern.org/plugins/1052/smd_calendar, or the "software page":http://stefdawson.com/sw, paste the code into the TXP Admin->Plugins pane, install and enable the plugin. Create any needed "custom fields":#fldatts. Visit the "forum thread":http://forum.textpattern.com/viewtopic.php?id=29375 for more info or to report on the success or otherwise of the plugin.
  2262. To uninstall, delete from the Admin->Plugins page.
  2263. h2. Naming convention
  2264. The core of the plugin is the smd_calendar tag, which renders a standard calendar. Each cell contains one or more events that occur on that day. Before diving into the tags here are the basics:
  2265. * *calid% : you may put more than one calendar on a page: each can be uniquely referenced with a calendar ID so they may be controlled independently
  2266. * *event% : an article. Any article written in the given section(s) will appear on the calendar as long as its expiry hasn't been met. More than one event may appear in each cell
  2267. * *event flag% : events can either be "standard":#events, they can "recur":#recur, they can span "multiple":#multi days, dates can be "unscheduled or omitted":#cancel from the calendar, or "extra":#extra dates can be added
  2268. * *cell flag% : a series of flags are used to label each cell according to what it contains. Cells can either be empty (i.e. no date: the filler cells at the start/end of the month), they can be a regular day (no flag), they can contain an event, or may be a "holiday":#holidays
  2269. Flags provide information _about_ the cell / event -- and can be tested with the conditional tag -- while the corresponding class names are just there for your CSS use.
  2270. When assigned as classes, flags *always* take the 'class' prefix (i.e. the 1st item given in smd_calendar's @classprefixes@ attribute) -- whether they appear at the _event_ or _cell_ levels. The only classes that take the optional 2nd 'event' prefix are the fields you specify in the @eventclasses@ attribute.
  2271. h2(#events). Standard cells and events
  2272. The following cumulative naming rules apply:
  2273. * Normal (day) cells don't have a class assigned to them unless you specify one with @cellclass@
  2274. * Any cell that contains an event (of any type) is flagged as an @event@
  2275. * Any cell that falls on a holiday is given the flag @hols@
  2276. * The current day is given the flag @today@
  2277. A single event (aka article) with a Posted date will be flagged @standard@, unless it recurs or spans more than one day.
  2278. h2(#recur). Recurring events
  2279. If you have nominated a field as your @stepfield@ you may enter a comma-separated list of frequencies at which the event is to repeat. The format of the repetition is *number interval*, e.g. @4 weeks@ or @10 days@ or @6 months@. The plugin will do its best to figure out what you mean. See "date input formats":http://www.gnu.org/software/tar/manual/html_node/Date-input-formats.html for more.
  2280. A few points:
  2281. * @second tuesday@ will show an event _every_ 2nd Tuesday from the start date (i.e. fortnightly)
  2282. * @second tuesday ?month ?year@ will substitute the current month and year in first, and then calculate the date, resulting in the event only recurring on the 2nd Tuesday of each month. You *must* use both @?month@ and @?year@ if you choose this type of date or your event won't appear
  2283. * @20 ?month@ will show the event on the 20th of every month
  2284. * things like @first thursday@ or @last friday@ or @this tuesday@ don't need the @?month@ or @?year@ because they can _only_ occur monthly, and will only show the event on the given day of the month
  2285. If you can find a use for it, you may specify multiple frequencies, for example @3 days, 1 week@. That means you would see the event on the 1st day, then the 3rd, 6th, 7th, 9th, 12th, 14th, 15th, 18th, 21st, 24th... days thereafter. Note that the event only occurs once on the 21st day even though the two frequencies clash.
  2286. Recur rules:
  2287. * The repetition will continue forever[1] unless you specify an expiry time
  2288. * The very first event of a recurring set will be flagged with @recurfirst@
  2289. * Each following cell that contains a recurring event will be flagged with @recur@
  2290. * Both flags are applied to the event as classes using the class prefix
  2291. * Repeated events may be "cancelled or omitted":#cancel on a per-date basis
  2292. fn1(small). 'forever' is limited to 99999 recurrences per event, or 274 years' worth of daily events. Either UNIX will run out of dates or PHP will run out of memory long before you reach this limit :-)
  2293. h2(#multi). Multi-day (spanned) events
  2294. Any event that has:
  2295. * a start date
  2296. * an expiry date that is a day or more later than the start date, and
  2297. * _does not_ have any repetition in its @stepfield@
  2298. is flagged as a @multi@. By default the first event is displayed in full and each 'continuation' event is shown only as a right arrow in subsequent cells. Events may span months or years and the plugin figures everything out using flag rules:
  2299. * The first cell of a spanned set is @multifirst@
  2300. * The last cell is @multilast@
  2301. * Every other cell is a @multi@ _except_ if the event rolls over into the next month. In that case, the entry on the 1st of the month is a @multiprev@ to indicate it belongs to a previous event
  2302. * If the 1st day of the month is the last day of the event, the @multiprev@ is dropped in favour of @multilast@
  2303. Switch off spanning completely with @showspanned="0"@. Events that have an expiry then become standard events.
  2304. 'Continuation' cells may be processed with a separate form (@spanform@). If you choose not to use a @spanform@, the standard @form@ or container will be used and you will have to distinguish between the different multi flags yourself using the "conditional tag":#ifcal.
  2305. You can also "cancel or omit":#cancel days of a spanned event in the same manner as you do with "recurring events":#recur, except you cannot cancel the first day; you should move the event start date and apologise!
  2306. h2(#cancel). Cancelling and omitting events
  2307. Plans change and you may find that a gig has to be cancelled. Perhaps you advertise a weekly boot fair but the field is waterlogged one week. No problem: nominate a @skipfield@ in your @smd_calendar@ tag and enter the date of the cancelled event.
  2308. Or you might run a theatre web site that has a three-week production performance on weeknights only. Instead of setting up three separate events -- one for each week -- nominate an @omitfield@ and list the dates on which the performance does not air. Omitted dates will not, under any circumstances, appear on the calendar and they override cancelled dates/holidays. If @cellplus@ mode is used, you will however see a cell flag labelled @omit@ (plus class prefix) in case you do wish to style such cells.
  2309. To specify the dates in either field, use any acceptable date format, but derivatives of dd-monthname-yyyy or monthname-dd-yyyy are the most unambiguous to avoid problems (e.g. is @1/5@ Jan 5th or May 1st). You can specify as many cancellations or omissions as you like; comma-separate each one. You may also specify ranges of dates to omit/cancel by using the notation @start date => end date@.
  2310. By default, cancelled events will not appear on the calendar. If you use @showskipped="1"@ the event will appear in the cell as normal and the @cancel@ flag will apply to the event (and cell if you choose) so you may detect/style it.
  2311. h2(#extra). Extra dates
  2312. When events are "cancelled":#cancel you may elect to reschedule the event on a different date instead of having to create a new event with identical details. Use the @extrafield@ to simply add it to the calendar on its new day. You might also use this feature if you have a "recurring":#recur event on the 1st of every month but for some reason you have to move one of the events a day or two. Simply cancel the offending date and add the new date using @extrafield@.
  2313. The list of dates can be in any standard date format (including date ranges using @start => end@). They will be added to the calendar and flagged as @extra@. Also see the @extrastrict@ attribute.
  2314. If your original event spans more than one day, you may elect to schedule the entire block again. Add a '+' after any date you wish to repeat in its entirety, e.g. @2009-Mar-12+, 2009-Jun-18+, 2009-Feb-19@ would copy the entire event block to March and June, but only the 1st date to Feb 19th. Events/cells are flagged as both @extra@ and @multi@ where applicable.
  2315. Notes:
  2316. * if you schedule an extra event on a day that already contains the same event (perhaps on a spanned or recurring date) you may see two identical events on the same day. Try and avoid this, unless it's your intended behaviour
  2317. * date ranges and the '+' syntax are mutually exlcusive on any date (e.g. @2009-Mar-10+ => 2009-Mar-15@ is illegal)
  2318. h2(#holidays). Holidays
  2319. Public holidays need not be a nuisance to your events. Give the plugin a holiday list and it'll make sure any recurring or multi events are not scheduled on those days. "Standard":#events one-off events are permitted by default because you might want to organise a special event on that day, though you can forbid those too if you wish.
  2320. The list of dates you specify can be entered directly in a string in the @holidays@ attribute or in a @<txp:variable />@:
  2321. bc. <txp:variable name="nat_hols"
  2322. value="Dec 25, 26 Dec, 31/Dec, Jan-1,
  2323. May 4 2009, 2009-08-31" />
  2324. <txp:smd_calendar holidays="txpvar:nat_hols" />
  2325. That list shows some of the variety and breadth of formats the plugin allows. There are more. Note that the events without a year occur on the same date every year, whereas the ones with a year will only occur on that specific date.
  2326. Once your dates are defined you can control which events are allowed to fall on those dates via @holidayflags@. Combining that attribute, @showspanned@, @showskipped@ and the forms -- along with the conditional tag -- can give a wide variety of ways to display events and cells.
  2327. Holiday cells are given the flag @hols@, and any event that is not specifically permitted by the @holidayflags@ is automatically assigned a @cancel@ flag if it falls on one of the days. Omitted dates will, however, cause the @cancel@ flag to be removed.
  2328. h2(#smdcal). Tag: @<txp:smd_calendar>@
  2329. Put this tag wherever you want your calendar to appear. Use the following attributes to control its output. The default value is unset unless stated otherwise.
  2330. h3. Display attributes
  2331. ; *size*
  2332. : Calendar size. Options:
  2333. :: large
  2334. :: small
  2335. : The small is more geared towards a minical, although functionally there "isn't much between them":#sizediff
  2336. : Default: large
  2337. ; *firstday*
  2338. : First day of the week to show in the calendar. 0=Sunday, 1=Monday ... 6=Saturday
  2339. : Default: 0
  2340. ; *dayformat*
  2341. : Way in which day names are rendered. Options:
  2342. :: ABBR shows abbreviated day names; Mon, Tue, Wed, etc
  2343. :: FULL uses full names
  2344. :: Any valid "strftime()":http://uk2.php.net/strftime codes. Locale-specific names are returned
  2345. :: A comma-separated list of custom day names surrounded by @{}@ brackets
  2346. : Example, for two-letter German weekdays: @dayformat="{So,Mo,Di,Mi,Do,Fr,Sa}"@. The first day in the list *must* represent Sunday or things will break
  2347. : Default: ABBR
  2348. ; *monthformat*
  2349. : Way in which the month names are rendered. Options:
  2350. :: FULL shows full month names
  2351. :: ABBR uses abbreviated names
  2352. :: Any valid strftime() codes. Locale-specific names are returned
  2353. :: A comma-separated list of custom month names surrounded by @{}@ brackets
  2354. : Example, for single-letter month names: @monthformat="{J,F,M,A,M,J,J,A,S,O,N,D}"@. The first month in the list *must* represent January or things will break
  2355. : Default: FULL
  2356. ; *select*
  2357. : Use a select dropdown for rapid access to weeks, months or years instead of fixed names. Choose one or more of:
  2358. :: week
  2359. :: month
  2360. :: year
  2361. : Note: week and month are mutually exclusive
  2362. : You may also specify up to two extra arguments, separated by @:@ chars. These add text in front of and behind the week/month/year, respectively
  2363. : Example: @select="week:WEEK#, year:<:>"@ displays a select list with entries like this: @WEEK#15 <2009>@
  2364. ; *selectbtn*
  2365. : Add a dedicated submit button to week/month/year select lists. Specify the text you wish to appear on the button. It will have the CSS class name @smd_cal_input@
  2366. : Default: unset (i.e. auto-submit on select list change)
  2367. ; *isoweeks*
  2368. : Show ISO week numbers as a column at the start of the calendar. Any text in this attribute enables the feature, and becomes the heading of the ISO week column
  2369. : You may change the default week number in each cell by adding a comma and some text; whatever you enter will be put in each ISO week cell. Use the following replacement codes in your markup to insert the relevant info:
  2370. :: {week}
  2371. :: {month}
  2372. :: {year}
  2373. :: {isoyear}
  2374. : Example: @isoweeks="wk, #{week}"@ will put 'wk' at the top of the column and something like @#24@, @#25@, @#26@... beneath it. If the first item is omitted, there will be no column heading
  2375. : Note that if this feature is enabled, @firstday@ will be forced to start on a Monday as governed by the ISO specification
  2376. ; *navarrow*
  2377. : Comma-separated pair of items you want to appear as prev/next arrows in the calendar
  2378. : Default: @&#60;, &#62;@
  2379. ; *caption*
  2380. : Add a caption to the calendar
  2381. ; *summary*
  2382. : Add a summary to the calendar
  2383. h3. Filter attributes
  2384. ; *time*
  2385. : Which events to display. Options:
  2386. :: any
  2387. :: past
  2388. :: future
  2389. :: today
  2390. : Default: any
  2391. ; *expired*
  2392. : Hide or show expired events. Options:
  2393. :: unset (use the TXP Preference 'Publish expired articles')
  2394. :: 0 (hide)
  2395. :: 1 (show)
  2396. ; *status*
  2397. : Events in this status list are published on the calendar. Options:
  2398. :: live
  2399. :: sticky
  2400. : Default: live
  2401. ; *category*
  2402. : Filter events by this list of categories. Use @SMD_UNCAT@ for uncategorised events
  2403. ; *subcats*
  2404. : Consider sub-categories. Choose a numeric nesting level to consider, or the word @all@
  2405. ; *section*
  2406. : Filter events by this list of sections
  2407. ; *author*
  2408. : Filter events by this list of author login names
  2409. ; *realname*
  2410. : Filter events by this list of author Real Names (note: may add one extra query)
  2411. ; *showall*
  2412. : If your calendar appears on the front page of your site and you have _not_ used the @section@ attribute, then:
  2413. :: 0: only shows events from sections marked with @On front page@
  2414. :: 1: shows all events from all sections
  2415. : Default: 0
  2416. ; *month*
  2417. : Start the calendar on this month from 1 (Jan) to 12 (Dec). Normal calendar navigation overrides this value
  2418. : If @static@ is used, this month becomes the only one you may view
  2419. : If unset, and no @m=@ value appears on the URL line, the current month is used
  2420. : *week*
  2421. : Start the calendar during the month containing this ISO week (from 1 to 53)
  2422. : Ignored if @static@ or one of the calendar navigation controls is used
  2423. : If a @w=@ value appears on the URL line, the given week overrides any month value
  2424. ; *year*
  2425. : Start the calendar at this 4-digit year. Normal calendar navigation overrides this value
  2426. : If @static@ is used, this year becomes the only one you may view
  2427. : If unset, and no @y=@ value appears on the URL line, the current year is used
  2428. ; *static*
  2429. : Force the calendar to be fixed to one month/year (i.e. no navigation). Month and year decided by attributes @month@ and @year@ or, if omitted, the current date will be used
  2430. h3. Form attributes
  2431. ; *form*
  2432. : Use the given TXP form to process each event
  2433. : If the smd_calendar tag is used as a container it will be used in preference to the form
  2434. : If neither are used, a default is used (Large: hyperlinked article title, Small: empty event)
  2435. ; *spanform*
  2436. : Display spanned events differently to standard events; they usually use the same @form@/container
  2437. : If neither are specified, a right-arrow will be used to indicate continuation of the previous day's event
  2438. : Note: the first day of a spanned set is _not_ passed to the @spanform@; only continuation cells are passed to it
  2439. ; *recurform*
  2440. : Display recurring events differently to standard events
  2441. : Note: the first event of a recurring set is _not_ passed to @recurform@
  2442. ; *cellform* (%(warning)only on large calendars%)
  2443. : Use if you wish to build each cell entirely from scratch
  2444. : There are some "replacement variables":#cellform you can use to insert dynamic pieces of information in your cells
  2445. : Note: you cannot use TXP article or plugin tags in the Form
  2446. ; *headerform*
  2447. : Use if you wish to build the header yourself
  2448. : There are some "replacement variables":#hdrform you can use to insert dynamic pieces of information in your header
  2449. h3(#fldatts). Field-based attributes (custom fields or other article fields)
  2450. ; *stepfield*
  2451. : ID of a field within which an event may be told to repeat
  2452. : Note: it is the field's ID _not its name_, so for custom fields you must use @custom_1@ or @custom_2@ etc
  2453. : Without this attribute, no "recurring events":#recur may be defined
  2454. ; *omitfield*
  2455. : ID (not name) of a field that contains a list of dates on which this event is to be "omitted":#cancel
  2456. ; *skipfield*
  2457. : ID (not name) of a field that contains a list of dates on which this event is "cancelled":#cancel
  2458. ; *extrafield*
  2459. : ID (not name) of a field from which a list of "additional event dates":#extra may be given; the same event details will be copied to the new day(s)
  2460. ; *extrastrict*
  2461. : Control visibility of extra dates on the calendar. Options:
  2462. :: 0: @extrafield@ dates automatically appear on the calendar
  2463. :: 1: restrict new dates from appearing after the event's expiry date
  2464. : Default: 0
  2465. ; *showskipped*
  2466. : Control visibility of cancelled events on the calendar. Options:
  2467. :: 0 (hide)
  2468. :: 1 (show)
  2469. : Default: 0
  2470. ; *showspanned*
  2471. : Control visibility of spanned events on the calendar. Options:
  2472. :: 0 (hide)
  2473. :: 1 (show)
  2474. : Default: 1
  2475. ; *datefields*
  2476. : IDs (not names) of up to two fields from which posted and expiry date stamps may be read. Comma-separate the field names; the first will be used as the Posted date and the second as the Expiry
  2477. : If either is omitted or mangled, the article's "real" posted/expiry date will be used instead
  2478. : If the expiry date occurs before the start date, your datefields will be ignored and a warning will be issued
  2479. h3. Config attributes
  2480. ; *holidays*
  2481. : List of dates that are decreed as "holidays":#holidays
  2482. : May be deferred to a @<txp:variable />@, in which case define your list in a named variable and use @holidays="txpvar:my_var_name"@ to read them into the plugin
  2483. ; *holidayflags*
  2484. : Permit certain event flags to be scheduled on holidays. List one or more of:
  2485. :: standard
  2486. :: recur
  2487. :: multi
  2488. : Default: @standard@
  2489. ; *id*
  2490. : HTML ID to apply to the table that holds the calendar. This becomes the value of the URL variable @calid@
  2491. : Use this if you have more than one calendar on a page and wish to control them separately via the URL vars
  2492. ; *navid*
  2493. : HTML ID to apply to the prev/next/month/year navigation form
  2494. ; *yearwidth*
  2495. : A comma-separated list that specifies how many years your calendar spans. Visitors will not be permitted to navigate (next/prev) the calendar outside this range. Options:
  2496. :: 0: use the earliest (posted) event as the earliest year, and the latest (modified or posted, whichever is greater) event as the latest year
  2497. :: any other single number expands the range equally in past & future directions
  2498. :: any pair of numbers subtracts the first from the earliest event and adds the second number of whole years to the latest event
  2499. :: adding @c@ to either value causes the current year to be used instead of the earliest or latest event
  2500. : Example: @yearwidth="2,4c"@ subtracts 2 years from the earliest event and adds 4 whole years to today's date
  2501. : Default: 0
  2502. ; *remap*
  2503. : When dealing with multiple calendars on a page it is often beneficial to use different names for @w=@, @m=@ or @y=@ in the URL so you can navigate calendars individually. This attribute enables you to rename the w, m, & y variable to any name specified after a colon (:)
  2504. : Example: @remap="w:wk, y:yr"@
  2505. ; *linkposted* (%(warning)only on small calendars%)
  2506. : Each cell that contains an event flag of the given type(s) will have its day number linked to the event's true start date instead of the cell's date. This allows you to always link to a valid article/event
  2507. : Note that if more than one event occurs in the cell, the link will only be to the first event the plugin finds
  2508. : Example: if you have a weekly event that starts on the 20th December 2008, setting @linkposted="recur"@ will cause the link to be @date=2008-12-20@ every week. Without linkposted, the dates would be 2008-12-20, 2008-12-27, 2009-01-03, and so on
  2509. : Default: @recur, multi, multiprev, multilast@ (i.e. any recurring or spanned event)
  2510. ; *maintain*
  2511. : Keep track of this comma-separated list of variables in the URL when navigating the calendar using the next/prev or month/year select lists. If you wish to maintain state yourself or do something exotic, empty this attribute first to avoid weirdness. Options:
  2512. :: calid
  2513. :: section
  2514. :: category
  2515. :: author
  2516. :: article
  2517. :: date
  2518. :: pg
  2519. :: any other URL variable of your choosing
  2520. : Example: use @maintain="section, article, calid"@ if you have an individual article page with a calendar in a sidebar, so the currently viewed article will remain in view when changing date
  2521. : Default: calid
  2522. ; *nameval*
  2523. : Add your own name/value pairs to the calendar's URL.
  2524. : Example: @nameval="tracker=mycal, keep=1"@ would add @?tracker=mycal&keep=1@ to the URL. Useful if you want to @maintain@ some values which you can't add to the URL on page load
  2525. h3. Style attributes
  2526. ; *classlevels*
  2527. : Each flagged event can be given a CSS class based on its flag name(s). Classes can be applied to events, cells, or both. You may also promote (i.e. copy) all event classes that occur in a day to the cell itself so you can style the cell based on the events it contains. Options:
  2528. :: event
  2529. :: cell
  2530. :: cellplus (for copying unique event classes to the containing cell)
  2531. : Note you should not use @cell@ and @cellplus@ together because the latter overrides the former
  2532. : Default: cell, event
  2533. ; *classprefixes*
  2534. : Comma-separated list of up to two prefixes to apply to your class names. The first prefix is applied to cell-level classes (and flags) and the second prefix is applied to event classes (see @eventclasses@)
  2535. : If you only specify one prefix, it will be used for both. If you use @classprefixes=""@ then no prefixes will be used at all
  2536. : Default: smd_cal_, smd_cal_ev_
  2537. ; *class*
  2538. : Class name of the calendar table itself
  2539. : Default: unset
  2540. ; *rowclass*
  2541. : Class name of each table row
  2542. : Default: unset
  2543. ; *cellclass*
  2544. : Class name of each table cell
  2545. : Default: unset
  2546. ; *emptyclass*
  2547. : Class name of any cells that don't contain a day number (i.e. the blank cells at the start & end of a month)
  2548. : Default: empty
  2549. ; *isoweekclass*
  2550. : Class name of each cell containing an ISO week
  2551. : Default: week
  2552. ; *navclass*
  2553. : Class name of the prev/next month nav arrows. If a comma-separated list is used, the first item will be the name of the class of the previous month, the 2nd item of the next month. If a single value is used, both class names will be the same
  2554. : Default: navprev, navnext
  2555. ; *myclass*
  2556. : Class name of both the month and year in the calendar header (either the @<span>@ or @<select>@ tags). If @mywraptag@ is used, the class is applied to the wraptag instead
  2557. : Default: unset
  2558. ; *eventclasses*
  2559. : Comma-separated list of items to add as classes to each event. Each are prefixed with the event prefix
  2560. : Example: @eventclasses="ID, AuthorID, custom_5"@ would add three classes to each event corresponding to the event's ID, its author (login) name and the contents of custom_5
  2561. : If you use @cellplus@, these classes will be copied to the cell level. Some special names exist: @category@ adds both Category1 and Category2 (if set); @gcat@ will add the current 'global' category (if filtering by category); @author@ adds the author ID (if filtering by author); @section@ adds the current section
  2562. : Default: category
  2563. ; *eventwraptag*
  2564. : HTML tag, without brackets, to wrap each event with
  2565. : Default: span
  2566. ; *mywraptag*
  2567. : HTML tag, without brackets, to wrap around _both_ month + year dropdown select lists and submit button
  2568. : Default: unset
  2569. h3(#cellform). Using a @cellform@ with replacement variables
  2570. If you don't like the layout of the default cell, you can do it yourself with the @cellform@ attribute. The cells are generated _last_, so by the time the plugin reaches this attribute, all events have already been processed by any of your forms/containers. Thus you can't use TXP article or plugin tags.
  2571. To build your own cells you often need information such as the events that fall on a particular day; or the week, month or day numbers, etc. So you may also insert any of the following replacements to have the relevant value inserted among your markup:
  2572. * {day} / {dayzeros} : day of the week (1-31 / 01-31)
  2573. * {weekday} : weekday in the local language, or from your @dayformat@ list
  2574. * {weekdayfull} or {weekdayabbr} : weekday in the local language
  2575. * {week} : ISO week number (01-53)
  2576. * {month} / {monthzeros} : month number (1-12 / 01-12)
  2577. * {monthname} : month name in the local language, or from your @monthformat@ list
  2578. * {monthnamefull} or {monthnameabbr} : month name in the local language
  2579. * {year} : 4-digit year
  2580. * {shortyear} : 2-digit year
  2581. * {isoyear} : 4 digit ISO year
  2582. * {shortisoyear} : 2 digit ISO year
  2583. * {evid} : event (article) ID
  2584. * {events} : all events for the day
  2585. * {standard} : only standard events
  2586. * {recurfirst} / {recur} : various recurring events
  2587. * {allrecur} : all recurring events for the day
  2588. * {multifirst} / {multi} / {multiprev} / {multilast} : various multi events
  2589. * {allmulti} : all multi events for the day
  2590. * {cancel} : only cancelled events
  2591. * {extra} : only extra events
  2592. h3(#hdrform). Using a @headerform@
  2593. You can create your own header if you wish and employ any of the following replacements in the markup:
  2594. * {firstday} : current weekday (as a number from 0 to 6)
  2595. * {daynames} : comma-separated day names
  2596. * {isoweekhead} : ISO week heading
  2597. * {week} : ISO week
  2598. * {month} : month
  2599. * {year} : year
  2600. * {isoyear} : ISO year
  2601. h3(#sizediff). Differences between large and small calendars
  2602. * @cellform@ cannot be used on a small calendar
  2603. * By default, no event descriptions are placed in the small calendar. You can add them yourself if you wish using a form/container
  2604. * The only thing rendered in a small calendar cell is the hyperlinked date and any flags so you can style the boxes
  2605. * @classlevels@ are ignored: everything is automatically assigned at the cell level (i.e. @cellplus@ is set)
  2606. * @eventclasses@ are still honoured if you wish to use a form to process them yourself
  2607. * The small calendar outputs year-month-day-title or messy permlinks only
  2608. h2(#ifcal). Tag: @<txp:smd_if_cal>@
  2609. This conditional tag allows you -- inside your container/forms -- to test certain conditions of the current event/cell. For enhanced conditional checking (perhaps in conjunction with "smd_cal_now":#calnow), consider the smd_if plugin. The default value is unset unless stated otherwise.
  2610. h3(#attsifcal). Attributes
  2611. ; *flag*
  2612. : The cell or event flag(s) you want to test, each separated by a comma. List one or more of:
  2613. :: event
  2614. :: standard
  2615. :: recurfirst
  2616. :: recur
  2617. :: multifirst
  2618. :: multi
  2619. :: multilast
  2620. :: multiprev
  2621. :: cancel
  2622. :: omit
  2623. :: extra
  2624. :: hols
  2625. :: today
  2626. :: SMD_ANY (will trigger if the cell or event contains any of the above)
  2627. ; *calid*
  2628. : The calendar ID you wish to check for a match
  2629. ; *year*
  2630. : The year the current cell falls in
  2631. ; *isoyear*
  2632. : The ISO year the current cell falls in
  2633. ; *month*
  2634. : The month number (1-12) that the current cell falls in
  2635. ; *week*
  2636. : The ISO week number that the current cell falls in
  2637. ; *day*
  2638. : The day number the current cell falls in
  2639. ; *logic*
  2640. : Method of combining the nominated tests. Options:
  2641. :: or: tag will trigger if at least one of the tests is true
  2642. :: and: tag will only trigger if all the tests are true
  2643. : Default: or
  2644. 'And' logic is useful for checking if the cell is of a certain type AND is later than the 15th of the month, for example.
  2645. Rudimentary comparators can be applied to the @(iso)year@, @month@, @week@ and @day@ attributes. Normally the value you supply will be tested for an exact match but if you prefix it with one of the following character sequences then the behaviour changes:
  2646. * @>@ tests if attribute is greater than the given value (e.g. @year=">2008"@)
  2647. * @>=@ tests if attribute is greater than or equal to the given value (e.g. @month=">=7"@)
  2648. * @<@ tests if attribute is less than the given value
  2649. * @<=@ tests if attribute is less than or equal to the given value
  2650. * @!@ tests if attribute is _not_ the given value (e.g. @day="!15"@)
  2651. h2(#calinfo). Tag: @<txp:smd_cal_info>@
  2652. Inside your smd_calendar container/forms, use this tag to output certain information about the current event.
  2653. h3(#attscalinfo). Attributes
  2654. ; *type*
  2655. : Comma-separated list of types of information you want to display. Options:
  2656. :: flag
  2657. :: calid
  2658. :: (iso)year
  2659. :: month
  2660. :: week
  2661. :: day
  2662. :: s (current section)
  2663. :: category
  2664. :: author
  2665. :: realname
  2666. :: article (id of the currently viewed article)
  2667. :: any other article variable such as @section@ (the current article's section), @authorid@, @article_image@, etc
  2668. : If using the @html@ attribute, you may optionally specify the name you want the variable to appear as in the URL string. The variables all take on sensible defaults (e.g. 'section' becomes @?s=<section name>@, 'category1' becomes @?c=<category1 name>@, etc).
  2669. : Example: @<txp:smd_cal_info type="catgeory:the_cat" html="1" />@ means you would see @?the_cat=<category1 name>@ in the URL
  2670. : Default: flag
  2671. ; *join*
  2672. : The characters you want to use to separate each item you asked for. Note it is the characters _between_ each item so the very first entry will *not* have the @join@ in front of it (see @join_prefix@)
  2673. : Default: a space
  2674. ; *join_prefix*
  2675. : The string you want to put in front of the first item in the returned list. If you do not specify this attribute it tries to be clever:
  2676. :: If using @type="flag"@ the join_prefix is set to the same as @join@. Thus with @join=" cal_"@ you might get @ cal_multi cal_today cal_hols@
  2677. :: If using @html="1"@ the join_prefix is set to a question mark, thus: @type="month,year,category" html="1"@ might render @?m=12&y=2008&c=gigs@, which can be put straight on the end of an anchor
  2678. : Default: @SMD_AUTO@
  2679. ; *html*
  2680. : Control in which format the information is returned. Options:
  2681. :: 0: return items verbatim
  2682. :: 1: return items as a URL parameter string. This is useful if you are building your own content inside each cell via a @form@ and wish to maintain the current search environment. If you allow people to filter events by category or author you can use this to return the 'current' state of certain variables so you can pass them to the next page and maintain state
  2683. : Note: Setting this attribute to 1 overrides the @join@ attribute and sets it to an ampersand
  2684. : Default: 0
  2685. ; *escape*
  2686. : Escape HTML entities such as @<@, @>@ and @&@ for page validation purposes. Use @escape=""@ to turn this off
  2687. : Default: @html@
  2688. h2(#calclass). Tag: @<txp:smd_cal_class>@
  2689. Inside your smd_calendat container/forms, use this tag to add a list of classes to the current cell/event. Very useful if building cells yourself because inside a "conditional":#ifcal tag you could add particular class names based on some value in a cell.
  2690. h3(#attscalclass). Attributes
  2691. ; *name*
  2692. : Comma-separated list of classnames to add to the current cell/event. These are *not* subject to any @classprefixes@ so will always appear exactly as you write them
  2693. : Default: unset
  2694. h2(#calnow). Tag: @<txp:smd_cal_now>@
  2695. Return the current date/time, formatted however you please. Useful for extracting parts of the current system timestamp to compare things via other conditional plugins or the @<txp:smd_if_cal>@ tag.
  2696. h3(#attscalnow). Attributes
  2697. ; *format*
  2698. : The way you want the date/time represented. Use any valid "strftime()":http://uk2.php.net/strftime string. Has full Windows support, even for those values where strftime() indicates otherwise
  2699. : Default: the date format set in Basic Preferences.
  2700. ; *now*
  2701. : If you don't want the time to be 'now' you can state what time 'now' is! Use any standard date/time format
  2702. : You may also use the codes @?day@, @?month@ or @?year@ in your time string which will do one of two things:
  2703. :: replace the codes with the URL parameters @d=@, @m=@ or @y=@, if they are being used
  2704. :: use the current day, month or year (i.e. the parts of today's date)
  2705. : Default: now! (the time at which you call the tag)
  2706. ; *offset*
  2707. : An offset into the future that you wish to apply to @now@
  2708. : Example: @2 months@. See "Example 6":#eg6 for a practical application of this attribute
  2709. ; *gmt*
  2710. : Return either:
  2711. :: 0: local time according to the time zone set in Basic Prefs
  2712. :: 1: GMT time
  2713. : Default: 0
  2714. ; *lang*
  2715. : An ISO language code that formats time strings suitable for the specified language (or locale) as defined by "ISO 639":http://en.wikipedia.org/wiki/ISO_639
  2716. : Default: unset (i.e. use the value as stated in TXP prefs)
  2717. h2(#artev). Tag: @<txp:smd_article_event>@
  2718. When you create recurring events, they really only exist once as a single article; the repetition is a trick. Thus the built-in article tags only show the single, real articles.
  2719. This tag -- similar in function to @<txp:article_custom />@ -- allows you to list recurring articles as if they were 'real' articles in the database. %(warning)They don't become real articles, they are just listed as such%.
  2720. Inside the tag's @form@ or container you can use all existing article tags to display any information you like about each 'virtual' article. The default value is unset unless stated otherwise.
  2721. h3. Identical attributes to "smd_calendar":#smdcal:
  2722. ; *stepfield*
  2723. ; *skipfield*
  2724. ; *omitfield*
  2725. ; *extrafield*
  2726. ; *datefields*
  2727. ; *section*
  2728. ; *category*
  2729. ; *author*
  2730. ; *realname*
  2731. ; *status*
  2732. ; *time*
  2733. ; *expired*
  2734. h3. Other attributes
  2735. ; *id*
  2736. : Restrict events to this list of article IDs
  2737. ; *custom*
  2738. : Specify your own comma-separated list of custom field clauses to the query. Separate each field from its (optional) operator and clause with a colon (alterable via @param_delim@).
  2739. : Example: @custom="custom_3:like:Lion%, custom_6:Chessington"@ would add @AND custom_3 like 'Lion%' AND custom_6='Chessington'@ to the database query.
  2740. ; *param_delim*
  2741. : Alter the separator character(s) between field-clause items in the @custom@ attribute.
  2742. : Default: colon (:)
  2743. ; *type*
  2744. : Comma-separated list of event types to display. Options:
  2745. :: standard
  2746. :: recur
  2747. :: multi
  2748. : Default: standard, recur, multi
  2749. ; *allspanned*
  2750. : Control display of spanned events:
  2751. : 0: any event that has a start date in the past will be omitted from the list
  2752. : 1: display remaining days from spanned events that began in the past
  2753. : Example: use @allspanned="1"@ if you are listing remaining performance dates from a Broadway show's schedule that started some months ago.
  2754. : Default: 0
  2755. ; *month*
  2756. : Only show events that occur in the given YYYY-mm
  2757. ; *from*
  2758. : Only show events with Posted dated beginning after this start date. Can be any valid date format
  2759. ; *to*
  2760. : Only show events with Posted dates up to this end date. Can be any valid date format
  2761. ; *sort*
  2762. : Order the events by this column (case-sensitive) and sort direction (asc or desc)
  2763. : Default: Posted asc
  2764. ; *form*
  2765. : Pass each matching event to the given TXP form. Note that using a container overrides this attribute, and if you specify neither a form nor container, you will see a list of article Posted dates
  2766. ; *paging*
  2767. : Unlike article_custom, events from the smd_article_event tag may be paged using @<txp:older />@ and @<txp:newer />@. But if you wish to show an event list on the same page as a standard article list, the older/newer tags will navigate both lists simultaneously. Under this circumstance you may need to turn paging off (0). Options:
  2768. :: 0 (off)
  2769. :: 1 (on)
  2770. : Default: 1
  2771. ; *offset*
  2772. : Begin displaying events from this numeric position, instead of from the start of the list of events
  2773. : Default: 0
  2774. ; *limit*
  2775. : Inly show this many events maximum *per page*, i.e. the number of events to display, whether they come from one 'real' article or many
  2776. : Default: 10
  2777. ; *eventlimit*
  2778. : Only show this many events maximum *per event*
  2779. : Example: if you have a weekly repeated event that lasts for four months and you set @eventlimit="6"@ you will only see a maximum of 6 events from every article containing repetition. The range (start and end date) is determined by other plugin attributes
  2780. : Default: 10
  2781. ; *pageby*
  2782. : Esoteric paging feature, identical to @<txp:article />@
  2783. : Default: same as @limit@
  2784. ; *pgonly*
  2785. : Set to 1 to perform the paging action without displaying anything. Probably useless
  2786. ; *wraptag*
  2787. : The HTML tag, without brackets, to wrap the list in
  2788. ; *break*
  2789. : The HTML tag to separate each item with
  2790. ; *class*
  2791. : The CSS class to apply to the @wraptag@
  2792. h3. The smd_article_event tag process
  2793. It is worth noting that this tag executes in 3-phases:
  2794. # Pre-filter: all events that match @type@, @category@, @section@, @author@, @status@, @id@, and @expired@ are extracted
  2795. # Time-filter: any "time-based" attributes are then applied to the above list. At this point, any @extrafield@, @stepfield@, @omitfield@, or @skipfield@ are calculated to find repeated dates (up to as many as @eventlimit@ allows or the calculation exceeds the event's expiry time). The attributes @time@, @month@, @from@, and @to@ are used to refine the filtration here
  2796. # Output: whatever the previous phases have left behind is subject to any @paging@, @offset@ and @limit@ you may have specified, then wrapped and displayed
  2797. h2(#eventinfo). Tag: @<txp:smd_event_info>@
  2798. Identical tag to "<txp:smd_calinfo />":#calinfo but for use inside @<txp:smd_article_event />@.
  2799. h2(examples). Examples
  2800. h3(#eg1). Example 1: basic calendar
  2801. You have the entire arsenal of TXP tags available to you in a calendar. Thus you can set the article's start and end dates to the same day and set the start and end times to indicate the start and end of the event. You can then use standard @<txp:posted />@ or @<txp:expires />@ tags with various @format@ strings to render the event's criteria.
  2802. Similarly you can use any other TXP tags to show as much or as little detail as you like in the calendar cell.
  2803. bc. <txp:smd_calendar section="events">
  2804. <div>
  2805. Event: <txp:permlink><txp:title /></txp:permlink>
  2806. <br /><txp:excerpt />
  2807. </div>
  2808. <div class="evtime">
  2809. Start: <txp:posted format="%H:%M" />
  2810. </div>
  2811. <div class="evtime">
  2812. End: <txp:expires format="%H:%M" />
  2813. </div>
  2814. </txp:smd_calendar>
  2815. h3(#eg2). Example 2: conditional calendar
  2816. Using the conditional tag you can take action if certain events contain particular flags. This example also shows a completely useless manner of employing @<txp:smd_cal_now />@.
  2817. bc. Time is: <txp:smd_cal_now format="%T" />
  2818. <txp:smd_calendar form="evform"
  2819. stepfield="custom_3" skipfield="custom_6"
  2820. spanform="multis" />
  2821. In form @evform@:
  2822. bc. <txp:smd_if_cal flag="recur">
  2823. <txp:permlink>(RECUR)</txp:permlink>
  2824. <txp:else />
  2825. <txp:permlink><txp:title /></txp:permlink>
  2826. <txp:smd_if_cal flag="multifirst">
  2827. <span class="right">&laquo;--</span>
  2828. </txp:smd_if_cal>
  2829. <txp:smd_if_cal flag="recurfirst">
  2830. <span>One of many...</span>
  2831. </txp:smd_if_cal>
  2832. </txp:smd_if_cal>
  2833. And in form @multis@:
  2834. bc. <txp:smd_if_cal flag="multi, multiprev">
  2835. <txp:permlink>--&raquo;--</txp:permlink>
  2836. </txp:smd_if_cal>
  2837. <txp:smd_if_cal flag="multilast">
  2838. <txp:permlink>
  2839. <span class="left">--&raquo;</span> END <txp:title />
  2840. </txp:permlink>
  2841. </txp:smd_if_cal>
  2842. Notice that @multifirst@ is tested inside the same form as standard events. This is because only _continuation spanned cells_ are passed to the @spanform@; the first event of a spanned group is just like any standard event. Similarly, if you had been using @recurform@ the first event of the recurring set would be processed in the usual form/container and every subsequent event would be passed to the dedicated form.
  2843. h3(#eg3). Example 3: classes
  2844. You could use the calendar tags to output various pieces of flag information to build your own class names. This example also demonstrates the @html@ attribute of @<txp:smd_cal_info />@ to build up a query string that is passed along with an event's @category1@ when a visitor clicks the anchor.
  2845. This allows your site visitors to filter events by category while retaining the ability to show the calendar for the current month/year and section they are viewing instead of dropping back to the current month/year like other calendar systems often do.
  2846. bc. <txp:smd_calendar isoweeks="WEEK#"
  2847. yearwidth="0,2" select="year, month"
  2848. stepfield="custom_1" skipfield="custom_2"
  2849. showskipped="1" expired="1">
  2850. <span class="<txp:smd_cal_info join=" cal_" />">
  2851. <txp:permlink><txp:title /></txp:permlink>
  2852. <a href="?c=<txp:category1
  2853. />&<txp:smd_cal_info type="s, year,
  2854. month, calid" html="1" join_prefix=""
  2855. />"><txp:category1 title="1" /></a>
  2856. </span>
  2857. </txp:smd_calendar>
  2858. What is also useful about the @<txp:smd_cal_info />@ tag is that if a particular value is not set it will _not_ be included in the output.
  2859. h3(#eg4). Example 4: upcoming events
  2860. List the next 5 upcoming -- recurring -- events, plus any standard and spanned events, formatting them as a definition list.
  2861. bc. <h2>Upcoming Events</h2>
  2862. <txp:smd_article_event stepfield="custom_1"
  2863. wraptag="dl" time="future" eventlimit="5">
  2864. <txp:if_different>
  2865. <dt><txp:posted format="%B %Y" /></dt>
  2866. </txp:if_different>
  2867. <dd>
  2868. <txp:permlink><txp:title/></txp:permlink>
  2869. <txp:posted />
  2870. </dd>
  2871. </txp:smd_article_event>
  2872. If you add pagination tags you can flip through all the events; they will be displayed 10 at a time on each page (if you want to keep track of which page you are on as you flip through a calendar that is in a sidebar, add @pg@ to the @maintain@ attribute in your calendar).
  2873. Note the hyperlinked title shown here will jump to the 'real' article (the first date in the recurring set) when clicked, not to an article with a date matching the recurrence.
  2874. If you wanted to allow people to book an event, try this in your hyperlinked individual article:
  2875. bc. <txp:if_individual_article>
  2876. <txp:article limit="1">
  2877. <h3><txp:title /></h3>
  2878. <txp:body />
  2879. <p>Please choose a date to book:</p>
  2880. <select>
  2881. <txp:smd_article_event stepfield="custom_1"
  2882. type="recur" id='<txp:article_id />'>
  2883. <option
  2884. value='<txp:posted format="%G-%m-%d"/>'>
  2885. <txp:posted format="%m %d, $G" />
  2886. </option>
  2887. </txp:smd_article_event>
  2888. </select>
  2889. </txp:article>
  2890. </txp:if_individual_article>
  2891. With some cunning you could even add the 'virtual' date that they chose in your original smd_article_event list and pass it as a URL variable to your individual article where you could read the value and pre-select the date in the select list for the vistor.
  2892. h3(#eg5). Example 5: iCal synchronisation
  2893. How about being able to output your events in iCal format so other people can sync their calendars to yours? Put this in a new Page template in its own Section:
  2894. bc. BEGIN:VCALENDAR
  2895. VERSION:2.0
  2896. X-WR-CALNAME:Gigs Calendar
  2897. PRODID:-//Apple Computer, Inc//iCal 1.5//EN
  2898. X-WR-TIMEZONE:Europe/London
  2899. <txp:smd_article_event form="icsitem" time="any"
  2900. section="gigs" limit="1000">
  2901. BEGIN:VEVENT
  2902. DTSTART:<txp:posted format="%Y%m%dT%H%i%s" />
  2903. DTEND:<txp:expires format="%Y%m%dT%H%i%s" />
  2904. SUMMARY:<txp:title />
  2905. END:VEVENT
  2906. </txp:smd_article_event>
  2907. END:VCALENDAR
  2908. That will output an iCal-formatted gig list (repeated or otherwise). If you got freaky with it and added some conditional logic inside the template you could even read in URL variables and plug them in. Thus you could link to it directly off the calendar itself, pass in the section, category or event info and have a customised iCal stream pumped out of Textpattern.
  2909. Thanks to woof for bringing the original "David Emery":http://de-online.co.uk/2006/05/05/textpattern-and-ical article to my attention.
  2910. h3(#eg6). Example 6: redefining now
  2911. Using the @now@ and @offset@ attributes of @<txp:smd_cal_now />@ you can effectively set 'now' to be any time you like and make calculations based on a particular date.
  2912. Plugging the @?month@ and @?year@ codes in allows you to make @<txp:smd_article_event />@ track the calendar. So you can automatically show only the events that occur in the month the visitor is browsing via the calendar:
  2913. bc. <txp:smd_calendar stepfield="custom_1" />
  2914. <h2>Events this month</h2>
  2915. <txp:smd_article_event stepfield="custom_1"
  2916. from='<txp:smd_cal_now now="01-?month-?year" />'
  2917. to='<txp:smd_cal_now now="?month-?year"
  2918. offset="1 month" />' time="any" wraptag="ul">
  2919. <li><txp:permlink><b><txp:title/></b></txp:permlink></li>
  2920. </txp:smd_article_event>
  2921. h2. Author / credits
  2922. "Stef Dawson":http://stefdawson.com/contact. Jointly funded by generous donators: mrdale, woof, jakob, renobird and joebaich. Originally based on mdp_calendar. All props to the original author, and of course the class upon which the calendar is based.
  2923. # --- END PLUGIN HELP ---
  2924. -->
  2925. <?php
  2926. }
  2927. ?>