1<?php
2/**
3 * Meta Plugin: Sets metadata for the current page
4 *
5 * @license    GPL 2 (http://www.gnu.org/licenses/gpl.html)
6 * @author     Esther Brunner <wikidesign@gmail.com>
7 */
8
9/**
10 * All DokuWiki plugins to extend the parser/rendering mechanism
11 * need to inherit from this class
12 */
13class syntax_plugin_meta extends DokuWiki_Syntax_Plugin
14{
15    function getType()
16    {
17        return 'substition';
18    }
19
20    function getSort()
21    {
22        return 99;
23    }
24
25    function connectTo($mode)
26    {
27        $this->Lexer->addSpecialPattern('~~META:.*?~~',$mode,'plugin_meta');
28    }
29
30    /**
31     * Handle the match
32     */
33    public function handle($match, $state, $pos, Doku_Handler $handler)
34    {
35        // strip ~~META: from start and ~~ from end
36        $match = substr($match,7,-2);
37
38        $data = array();
39        $pairs = explode('&', $match);
40        foreach ($pairs as $pair) {
41            list($key, $value) = explode('=', $pair, 2);
42            list($key, $subkey) = explode(' ', $key, 2);
43            if (trim($subkey)) {
44                $data[trim($key)][trim($subkey)] = trim($value);
45            } else {
46                $data[trim($key)] = trim($value);
47            }
48        }
49        $data = array_change_key_case($data, CASE_LOWER);
50
51        return $data;
52    }
53
54    /**
55     * Create output
56     */
57    public function render($mode, Doku_Renderer $renderer, $data)
58    {
59        if ($mode == 'xhtml') {
60            // don't output anything
61            return true;
62        } elseif ($mode == 'metadata') {
63            /** @var Doku_Renderer_metadata $renderer */
64
65            // do some validation / conversion for date metadata
66            if (isset($data['date'])) {
67                if (is_array($data['date'])) {
68                    foreach ($data['date'] as $key => $date) {
69                        $date = $this->convertDate(trim($date));
70                        if (!$date) {
71                            unset($data['date'][$key]);
72                        } else {
73                            $data['date'][$key] = $date;
74                        }
75                    }
76                } else {
77                    unset($data['date']);
78                }
79            }
80
81            // now merge the arrays
82            $protected = array('description', 'date', 'contributor');
83            foreach ($data as $key => $value) {
84
85                // be careful with sub-arrays of $meta['relation']
86                if ($key == 'relation') {
87                    foreach ($value as $subkey => $subvalue) {
88                        if ($subkey == 'media') {
89                            $renderer->meta[$key][$subkey][cleanID($subvalue)] = @file_exists(mediaFN($subvalue));
90                        } elseif ($subkey == 'firstimage') {
91                            /* The metadata renderer overrides the first image value with its internal value at the end.
92                            Therefore the only thing we can do is setting this internal value by calling _firstimage.
93                            This fails if there has already been a first image saved. */
94                            $renderer->_firstimage($subvalue);
95                        } else {
96                            // for everything else assume that we have a page id
97                            $renderer->meta[$key][$subkey][cleanID($subvalue)] = page_exists($subvalue);
98                        }
99                    }
100                } elseif (in_array($key, $protected)) {
101                    // be careful with some sensitive arrays of $meta
102                    if (is_array($renderer->meta) && array_key_exists($key, $renderer->meta)) {
103                        $renderer->meta[$key] = array_merge($renderer->meta[$key], array($value));
104                    } else {
105                        $renderer->meta[$key] = $value;
106                    }
107                } else {
108                    // no special treatment for the rest
109                    $renderer->meta[$key] = $value;
110                }
111            }
112        }
113    }
114
115    /**
116     * converts YYYY-MM-DD[ hh:mm:ss][ -> [YYYY-MM-DD ]hh:mm:ss] to PHP timestamps
117     */
118    private function convertDate($date)
119    {
120        list($start, $end) = explode('->', $date, 2);
121
122        if (!$end) {
123            // single date
124            list($date, $time) = explode(' ', trim($start), 2);
125            if (!preg_match('/\d{4}\-\d{2}\-\d{2}/', $date)) {
126                return false;
127            }
128            $time = $this->autocompleteTime($time);
129            return strtotime($date.' '.$time);
130        } else {
131            // duration
132
133            // start
134            list($startdate, $starttime) = explode(' ', trim($start), 2);
135            $startdate = $this->autocompleteDate($startdate);
136            if (!$startdate) {
137                return false;
138            }
139            $starttime = $this->autocompleteTime($starttime);
140
141            // end
142            list($enddate, $endtime) = explode(' ', trim($end), 2);
143            if (!trim($endtime)) {
144                // only time given
145                $end_date = $this->autocompleteDate($enddate, true);
146                if (!$end_date) {
147                    $endtime = $this->autocompleteTime($enddate, true);
148                    $enddate = $startdate;
149                } else {
150                    // only date given
151                    $enddate = $end_date;
152                    $endtime = '23:59:59';
153                }
154            } else {
155                $enddate = $this->autocompleteDate($enddate, true);
156                if (!$enddate) {
157                    $enddate = $startdate;
158                }
159                $endtime = $this->autocompleteTime($endtime, true);
160            }
161
162            $start = strtotime($startdate.' '.$starttime);
163            $end   = strtotime($enddate.' '.$endtime);
164            if (!$start || !$end) {
165                return false;
166            }
167            return array('start' => $start, 'end' => $end);
168        }
169    }
170
171    private function autocompleteDate($date, $end=false)
172    {
173        if (!preg_match('/^\d{4}\-\d{2}\-\d{2}$/', $date)) {
174            if (preg_match('/^\d{4}\-\d{2}$/', $date)) {
175                // we don't know which month
176                return ($end) ? $date.'-28' : $date.'-01';
177            } elseif (preg_match('/^\d{4}$/', $date)) {
178                return ($end) ? $date.'-12-31' : $date.'-01-01';
179            } else {
180                return false;
181            }
182        } else {
183            return $date;
184        }
185    }
186
187    private function autocompleteTime($time, $end=false)
188    {
189        if (!preg_match('/^\d{2}:\d{2}:\d{2}$/', $time)) {
190            if (preg_match('/^\d{2}:\d{2}$/', $time)) {
191                return ($end) ? $time.':59' : $time.':00';
192            } elseif (preg_match('/^\d{2}$/', $time)) {
193                return ($end) ? $time.':59:59': $time.':00:00';
194            } else {
195                return ($end) ? '23:59:59' : '00:00:00';
196            }
197        } else {
198            return $time;
199        }
200    }
201}
202// vim:ts=4:sw=4:et:enc=utf-8:
203