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