1<?php
2# =================================================================================================
3# gCalendar "gcal_read.php" - responsible for reading the data into the big gCal_data-array
4# =================================================================================================
5
6/**
7 * read the pages given in the pages=(...) parameter into the "gCal_data"-array
8 *
9 * @author Frank Hinkel <frank@hi-sys.de>
10 *
11 * @param  array $options     contains a list of parameters passed to the plugin at the wiki-page
12 * @param  array pages        the wiki-pages to read the data from
13 * @param  default_date       if year ist omitted it is taken from this date.
14 *                            so you can write "31.07." for a birthdate which shows up every year
15 */
16function read_pages_into_calendar(&$options,&$pages,$default_date) {
17  global $gCal_data;    # this array receives all the date-entries
18
19  # reset data-array
20  $gCal_data = array();
21
22  # memory for wiki-pages allready read to avoid duplicates
23  $pages_allready_read = array();
24
25  foreach($pages as $page_key=>$page)
26  {
27    list($page_name,$page_list) = explode("(",$page,2);
28    $page_list = substr($page_list,0,-1);
29
30    if($page_list != ""){
31      $page_list = explode("|",$page_list);
32    }else{
33      $page_list = array($page_name);
34    }
35
36    # expand namespaces (i.e. ":wiki:*") expands to all files in that ns
37    # if option 'nested' is given, this is done recursive (all subnamespaces)
38    $page_list = expand_ns($page_list,isset($options['nested']));
39
40    foreach($page_list as $wikipage)
41    {
42        # split section from wikipage
43        list($wikipage,$section)=explode('#',$wikipage,2);
44
45        $wikipage=cleanID($wikipage);
46
47        # skip pages allready read, except if allowed with option: "showdup"
48        if(!isset($options["showdup"]) && in_array($wikipage,$pages_allready_read)) continue;
49        $pages_allready_read[] = $wikipage;
50
51        # check if user is allowed to see this page
52        $perm = auth_quickaclcheck(cleanID($wikipage));
53        if ($perm < AUTH_READ) continue; # to next page
54
55        # if we have more than one page to include or option pagelinks is set to 'show',
56        # generate a link to the wiki page, except option pagelinks is set to 'hide'.
57        if( ($options["pagelinks"]=="show") ||
58           (($options["pagelinks"]!="hide") && count($page_list)>1) ) {
59          $pagelink = " <span class='gCal_pagelink'><a href='".wl($wikipage)."'>".noNS($wikipage)."</a></span>";
60        }else{
61          $pagelink="";
62        }
63
64        # now read this page into the calendar-array
65        read_wikipage_into_calendar($options,$page_key,$wikipage,$section,$pagelink,$default_date);
66    }
67  }
68}
69
70
71/**
72 * read a page into the gCal_data-array
73 *
74 * @author Frank Hinkel <frank@hi-sys.de>
75 *
76 * @param  array $options     contains a list of parameters passed to the plugin at the wiki-page
77 * @param  array page_key     the column number where the data is stored into the gCal_data-array
78 * @param  string wikipage    the wiki-page to read the data from
79 * @param  string section     optionally given section inside the wikipage to read from
80 * @param  default_date       if year ist omitted it is taken from this date.
81 *                            so you can write "31.07." for a birthdate which shows every year
82 *
83 */
84function read_wikipage_into_calendar(&$options,$page_key,$wikipage,$section,$pagelink,$default_date) {
85  global $gCal_data; # this array contains all the date-entries
86  global $conf;
87
88  # read categorypattern from config
89  $match_category = $conf['gCal_match_category'];
90  if(!is_array($match_category)) return;
91
92  # read eventpattern from config.
93  $match_event = $conf['gCal_match_event'];
94  if(!is_array($match_event)) return;
95
96  # find path to actual wiki-page. skip if file not exists
97  $filepath = wikiFN($wikipage);
98  if(!file_exists($filepath)) return;
99
100  # read data from wiki-page
101  $handle = fopen ($filepath, "r");
102
103  while (!feof($handle)) {
104    $buffer = trim(fgets($handle, 4096));
105
106    # check line against all category-patterns. see user/conf.php
107    foreach($match_category as $pattern) {
108      if(preg_match($pattern, $buffer, $subpattern)) {
109        $category = strtoupper($subpattern[1]);
110        break;
111      }
112    }
113
114    if(is_string($section) && strlen($section)>0 && (strtoupper($section)!=$category)) continue;
115
116    # check line against all event-patterns. see user/conf.php
117    foreach($match_event as $pattern) {
118      if(preg_match($pattern, $buffer, $subpattern)) {
119        $buffer = $subpattern[1];
120
121        # grab date- and time-spans  from the beginning of each line
122        $start_date = $end_date = fetch_date($buffer,$default_date);
123        $end_time = "";
124        $start_time = fetch_time($buffer);
125
126        # check for time-spans indicated by a dash
127        if($buffer{0}=="-") {
128          $buffer = trim(substr($buffer,1)); # remove dash
129          $end_date   = fetch_date($buffer,$default_date);
130          # end_date equals start_date by default
131          if ( strlen($end_date)==0 ) $end_date = $start_date;
132          $end_time   = fetch_time($buffer);
133        }
134
135        $cat = strtoupper(trim($category." ".fetch_inline_category($buffer)));
136
137        # insert the event into the whole date-range
138        for($d=$start_date ; $d <= $end_date ; $d++) {
139          $entry = $buffer;
140
141          # initialize event with event-source
142          $event = array("source"=>$subpattern[1]);
143
144          # set the category, even when there is no event-text
145          $event["categories"] = $cat;
146
147          # generate an event, when event-text or start-time or end-time is given
148          if(($entry!="") || ($start_time!="") || ($end_time!="")) {
149            # special character ">" will be suppressed, when it is the first character
150            # of the event-text. can be used to force an event-icon without text
151            if($entry{0}==">") $buffer=substr($entry,1);
152
153            # apply basic rendering like bold, italic, links, etc.
154            $entry = fast_p_render($entry);
155
156            # add the backlink to the original wikipage
157            $entry .= $pagelink;
158
159            # attach start_time at start_date and end_time at end_date. I hope this is allways logical?
160            if(is_array($end_time) && ($d==$end_date)  ) {
161                $entry = "- ".$end_time[0]. " ".$entry;
162                $event["end_time"] = $end_time[1];
163            }
164            if(is_array($start_time) && ($d==$start_date)) {
165              $entry = $start_time[0]." ".$entry;
166              $event["start_time"] = $start_time[1];
167            }
168
169            # write the event-entry to global array
170            $cat_classes = 'gCal_cat_'.implode(' gCal_cat_',explode(' ',$cat))." ";
171
172            $event["content"] = "<span class='$cat_classes gCal_event'>".$entry."</span>";
173          }
174          $gCal_data[$page_key][$d][] = $event;
175        }
176        break;
177      }
178    }
179  }
180  fclose($handle);
181}
182
183
184# =================================================================================================
185# Utility-functions
186# =================================================================================================
187
188/*
189 * returns date at the beginning of the text. if date found it is removed from the text.
190 */
191function fetch_date(&$text,$default_date) {
192  global $conf;
193
194  if($text=="") return;
195
196  #  '#^'            --> # string has to start with the pattern.
197  #  '(?=($|\D))#'   --> # look-ahead => any non-digit or end-of-string terminates pattern
198
199  if(preg_match('#^'.$conf['gCal_date_dmy'].'(?=($|\D))#',$text,$match)) {
200    $d=$match[1];$m=$match[2];$y=$match[3];
201  }elseif(preg_match('#^'.$conf['gCal_date_mdy'].'(?=($|\D))#',$text,$match)) {
202    $d=$match[2];$m=$match[1];$y=$match[3];
203  }elseif(preg_match('#^'.$conf['gCal_date_ymd'].'(?=($|\D))#',$text,$match)) {
204    $d=$match[3];$m=$match[2];$y=$match[1];
205  }else{
206    return;
207  }
208
209  if(strlen($d)==1) $d='0'.$d;
210  if(strlen($m)==1) $m='0'.$m;
211  if(strlen($y)==0) $y=date('Y',$default_date);
212  if(strlen($y)==2) $y='20'.$y;
213
214  $text=trim(substr($text,strlen($match[0])));
215  return $y.$m.$d; // return format yyyymmdd
216}
217
218
219/*
220 * returns time at the beginning of the text
221 */
222function fetch_time(&$text) {
223global $conf;
224
225  if($text=="") return;
226
227  # allowed formats are: 1:23 , 01:23, 01:23am. 1:23 Am, etc.
228  $pattern = '([0-9]{1,2})\:([0-9]{2})\s*(am|pm|)';
229  $pattern  = '#^'.$pattern;   # string has to start with the pattern
230  $pattern .= '(?=($|\\D))#i'; # look-ahead => any non-digit or end-of-string terminates pattern
231
232  if(preg_match($pattern,$text, $match)) {
233    $text=trim(substr($text,strlen($match[0])));
234
235    $time = str_replace(array('##','#h','#m','#r'),$match,$conf['gCal_time']);
236    if(strlen($match[1])==1) $match[1] = '0'.$match[1];
237    if(strtolower($match[3])=='pm') $match[1] += 12;
238    $euro  = $match[1].":".$match[2];
239
240    return array($time,$euro);
241  }
242}
243
244/*
245 * returns the inline-category
246 */
247function fetch_inline_category(&$text) {
248global $conf;
249
250  if($text=="") return;
251
252  # get the preg-expr from $conf. sting has to start with this pattern
253
254  $pattern = '#'.$conf['gCal_inline_Category_visible'].'#i';
255  if(preg_match($pattern,$text, $match)) {
256    $text=trim($match[1].substr($text,strlen($match[0])));
257    return strtoupper($match[1]);
258  }
259
260  $pattern = '#'.$conf['gCal_inline_Category_hidden'].'#i';
261  if(preg_match($pattern,$text, $match)) {
262    $text=trim(substr($text,strlen($match[0])));
263    return strtoupper($match[1]);
264  }
265}
266
267/**
268 * Expand namespaces in the form :namespace:* to every wiki-page in this ns.
269 *
270 * @author Frank Hinkel <frank@hi-sys.de>
271 *
272 * @param  array page_list    on input  : array of pages and namespaces
273 * @param  array page_list    on output : array of pages (namespaces are expanded)
274 * @param  boolean nested  if true -> subnamespaces are also expanded into single-pages
275 *
276 */
277function expand_ns($page_list,$nested=false) {
278  global $conf;
279  $pl = array();
280
281  foreach($page_list as $page) {
282    if(substr($page,-1)=="*") {
283    # when page ends with *, expand namespace
284      $start = noNS($conf['start']);
285      $dir = wikiFN(substr($page,0,-1).$start);
286      $dir = substr($dir,0,-(strlen($start)+4)); # strip filename - only the dir is needed
287
288      if(!@$handle=opendir($dir)) continue;
289
290      while ($file = readdir ($handle)) {
291        if($file == "." || $file == ".." || trim($file)=="") continue;
292
293        if(is_file($dir.$file)) {
294          $pl[] = substr($page,0,-1).substr($file,0,-4);
295        }elseif($nested && is_dir($dir.$file)){
296          $subNS[] = substr($page,0,-2).':'.cleanID($file).':*';
297        }
298      }
299
300      closedir($handle);
301    }else{
302    # leave page as it is
303      $pl[] = $page;
304    }
305  }
306
307  # if $nested is true, add all files of all subnamespaces to the list
308  if(is_array($subNS)) {
309      $subNS = expand_ns($subNS,$nested,false);
310      $pl = array_merge($pl,$subNS);
311  }
312
313  return $pl;
314}
315
316
317/**
318 * quick rendering, where not all the power of p_render is needed
319 * do basic formatting and linking. returns the rendered string
320 *
321 * @author Frank Hinkel <frank@hi-sys.de>
322 * @param  string $text   text to be rendered
323 *
324 * @todo : add more rendering
325 */
326function fast_p_render($text) {
327  global $conf;
328
329  # do some fundamental-rendering: bold, italic, underline, ...
330  $text = preg_replace('#\*\*(.*)\*\*#sU', '<strong>\1</strong>', $text);
331  $text = preg_replace('#(?<!\:)\/\/(.*)(?<!\:)\/\/#sU', '<em>\1</em>', $text);
332  $text = preg_replace('#\_\_(.*)\_\_#sU', '<em class="u">\1</em>', $text);
333  $text = preg_replace('#\'\'(.*)\'\'#sU', '<code>\1</code>', $text);
334  $text = preg_replace('#\\\\\\\\(\s+|$)#m', '<br/>', $text);
335
336  $text = str_replace('=>','&rArr;',$text);
337  $text = str_replace('<=','&lArr;',$text);
338  $text = str_replace('->','&rarr;',$text);
339  $text = str_replace('<-','&larr;',$text);
340
341  # some methods of the class 'Doku_Renderer_xhtml' needed here
342  static $drx; if(!isset($drx)) $drx = new Doku_Renderer_xhtml;
343
344  # process all links enclosed in double square brackets
345  preg_match_all("=\[\[.+\]\]=sU",$text,$wiki_links);
346
347  foreach($wiki_links[0] as $wl) {
348    # reset rendered content
349    $drx->doc = '';
350
351    list($link,$name)=explode("|",substr($wl,2,strlen($wl)-4),2);
352
353    if ( preg_match('/^[a-zA-Z\.]+>{1}.*$/u',$link )) {
354      // Interwiki
355      if(count($drx->interwiki)==0) $drx->interwiki = getInterwiki();
356
357      list($wikiname,$wikiuri) = preg_split('/>/u',$link,2);
358      $drx->interwikilink('',$name,strtolower($wikiname),$wikiuri);
359    }elseif ( preg_match('/^\\\\\\\\[\w.:?\-;,]+?\\\\/u',$link) ) {
360      // Windows Share
361      $drx->windowssharelink($link,$name);
362    }elseif ( preg_match('#^([a-z0-9\-\.+]+?)://#i',$link) ) {
363      // external link (accepts all protocols)
364      $drx->externallink($link,$name);
365    }elseif ( preg_match('#([a-z0-9\-_.]+?)@([\w\-]+\.([\w\-\.]+\.)*[\w]+)#i',$link) ) {
366      // email-link
367      $drx->emaillink($link,$name);
368    }elseif ( preg_match('!^#.+!',$link) ){
369      // local link
370      $drx->locallink($link,$name);
371    }else {
372      $drx->internallink($link,$name);
373    }
374
375    $text=str_replace($wl,$drx->doc,$text);
376  }
377
378  # remove all html-tags which are not explicitly allowed
379  return strip_tags($text,$conf['gCal_allowed_tags']);
380}
381