1<?php
2/**
3 * @license    http://www.cecill.info/licences/Licence_CeCILL-B_V1-fr.html
4 * @author     Francois Merciol <dokuplugin@merciol.fr>
5 *
6 * Plugin Schedule: manage events per wiki @groups
7 *
8 * $df_ : 31/12/2000
9 * $ds_ : 20001231
10 * $dn_ : 978217200
11 * $dt_ : array ("Y" => 2000, "m" => 12, "d" => "31)
12 */
13if (!defined ('DOKU_INC'))
14    define ('DOKU_INC', realpath (dirname (__FILE__).'/../../../').'/');
15if (!defined ('DOKU_PLUGIN'))
16    define ('DOKU_PLUGIN', DOKU_INC.'lib/plugins/');
17
18function cmpScheduleConfig ($a, $b) {
19    return ($a['nameSpace'] < $b['nameSpace']) ? -1 : 1;
20}
21
22// ============================================================
23// admin class
24class scheduleRoot {
25
26    // ============================================================
27    // Config attributs
28    // ============================================================
29    var $cacheRootDir;					// root cache directory
30    var $dataRootDir;					// root data directory
31
32    var $scheduleGroup;					// group who can display and fill form
33    var $groupsDir;						// pages groups directories
34    var $scheduleDir;					// schedule pages groups directories
35    var $sharedDir;						// schedule directory for shared events
36    var $scheduleWhat = array ();		// set of legal "what"
37    var $scheduleAudience = array ();	// set of legal "audience"
38    var $iconName;						// group icon name
39    var $iconWidth;						// group icon width
40    // lang
41    var $scheduleShared = array ();		// notshared/shared
42    var $currentDateFormat;
43
44    // ============================================================
45    // Constant attributs $this->scheduleRoot->
46    // ============================================================
47    //var $imagesUrlDir = '/lib/plugins/schedule/images/'; // DOKU_REL
48    var $wizardIconUrl = '/lib/plugins/schedule/images/wizard.png'; // $this->imagesUrlDir
49    var $editIconUrl = '/lib/plugins/schedule/images/edit.png';
50    var $repeatIconUrl = '/lib/plugins/schedule/images/repeat.png';
51    var $emptyIconUrl = '/lib/plugins/schedule/images/empty.png';
52
53    var $oldConfigFile = "config.xml";	// old file config name
54    var $configFile = "cfg.xml";		// file config name
55    var $mbrPrefix = "mbr-";			// member prefix file name
56    var $propFile = "prop.xml";			// member prefix file name
57    var $configAttributsName = array ("nameSpace", "lastNotification", "lastNotificationReset");
58    // scheduleDir == NS ?
59    var $dateFormat = array ("dmY" => array ("shortScan" => "(?<d>[0-9]{1,2})[ /-](?<m>[0-9]{1,2})[ /-](?<Y>[0-9]{1,2})",
60                                             "longScan" => "(?<d>[0-9]{1,2})[ /-](?<m>[0-9]{1,2})[ /-](?<Y>[0-9]{4})",
61                                             "fieldName" => array ("d", "m", "Y"),
62                                             "prettyPrint" => "d/m/Y",
63                                             "dayMonth" => "d/m"),
64
65                             "mdY" => array ("shortScan" => "(?<m>[0-9]{1,2})[ /-](?<d>[0-9]{1,2})[ /-](?<Y>[0-9]{1,2})",
66                                             "longScan" => "(?<m>[0-9]{1,2})[ /-](?<d>[0-9]{1,2})[ /-](?<Y>[0-9]{4})",
67                                             "fieldName" => array ("m", "d", "Y"),
68                                             "prettyPrint" => "m/d/Y",
69                                             "dayMonth" => "m/d"));
70    var $oddEvenValues = array ("odd", "even");
71    var $scheduleRequestAttributsName = array ("id", "audience", "shared",
72                                               "what", "title", "lead", "posterURL", "paperURL", "remark", "rate",
73                                               "where", "lon", "lat", "addr",
74                                               "from", "to", "at",
75                                               "repeat", "repeatType", "weekRank", "dayInWeek", "dayRank",
76                                               "user");
77    var $scheduleSaveAttributsName;
78    var $filterNames = array ("noMember" => "member",
79                              "noWhat" => "what",
80                              "NoAudience" => "audience");
81
82
83    // ============================================================
84    // Transcient attributs
85    // ============================================================
86    var $message = array ();		// messages to display : "notify" =>, "info" =>, "success" =>, "error" =>
87    var $plugin;				// link to wiki plugin inforation (conf, lang, ...)
88    var $isAdmin;				// if user member of schedule adminGroup
89    var $oddEven = 0;			// background color for table lines
90
91    // ============================================================
92    // Util
93    // ============================================================
94    /* messages container to be display before plugin */
95    function message ($type, $text) {
96        if (isset ($this->message[$type]))
97            $this->message[$type] .= '<br/>'.NL;
98        $this->message[$type] .= $text;
99    }
100    function startMessage ($type, $text) {
101        if (isset ($this->message[$type]))
102            $this->message[$type] = '<br/>'.NL.$this->message[$type];
103        $this->message[$type] = $text.$this->message[$type];
104    }
105    /* debug messages for admin only */
106    function debug ($text) {
107        global $INFO;
108        // XXX
109        if (isset ($INFO['userinfo'] ['grps']) && in_array ('admin', $INFO ['userinfo'] ['grps']))
110            $this->message ('notify', '<pre>'.$text.'</pre>');
111    }
112    /* Change date format from array to number  */
113    function dt2dn ($dt_date) {
114        $dn_date = mktime (0, 0, 0, $dt_date["m"], $dt_date["d"], $dt_date["Y"]);
115        if (!$dn_date)
116            $this->message ('error', $dt_date["Y"]."-".$dt_date["d"]."-".$dt_date["m"]." : ".$this->plugin->getLang ('mktimeError').'<br/>'.NL);
117        return $dn_date;
118    }
119    /* Check date format 31/12/2000 */
120    function checkDS ($ds_date) {
121        return
122            preg_match ("#([0-9]{8})#", $ds_date,  $dumy);
123    }
124    /* Change date format from MM/DD/AAAA to number  */
125    function df2ds ($df_date) {
126        $df_date = trim ($df_date);
127        if (!$df_date)
128            return;
129        if ("!" == $df_date)
130            $ds_date = date ("Ymd");
131        elseif (preg_match ("#\+(?<delta>[0-9]+)#", $df_date, $matches))
132            $ds_date = sprintf ("%08d", date ("Ymd", mktime (0, 0, 0, date ("n"), date ("j")+$matches["delta"], date ("Y"))));
133        elseif (preg_match ("#\-(?<delta>[0-9]+)#", $df_date, $matches))
134            $ds_date = sprintf ("%08d",  date ("Ymd", mktime (0, 0, 0, date ("n"), date ("j")-$matches["delta"], date ("Y"))));
135        elseif (preg_match ("#".$this->currentDateFormat ["longScan"]."#", $df_date, $dt_date))
136            $ds_date = sprintf ("%04d%02d%02d", $dt_date["Y"], $dt_date["m"], $dt_date["d"]);
137        elseif (preg_match ("#".$this->currentDateFormat ["shortScan"]."#", $df_date, $dt_date))
138            $ds_date = sprintf ("20%02d%02d%02d", $dt_date["Y"], $dt_date["m"], $dt_date["d"]);
139        else
140            $ds_date = "{$df_date}";
141        return $ds_date;
142    }
143    /* Change date format from number to MM/DD/AAAA  */
144    function ds2df ($ds_date) {
145        $ds_date = trim ($ds_date);
146        if (preg_match ("#(?<Y>[0-9][0-9][0-9][0-9])(?<m>[0-9][0-9])(?<d>[0-9][0-9])#", $ds_date,  $dt_date))
147            return sprintf ("%02d/%02d/%04d",
148                            $dt_date[$this->currentDateFormat["fieldName"][0]],
149                            $dt_date[$this->currentDateFormat["fieldName"][1]],
150                            $dt_date[$this->currentDateFormat["fieldName"][2]]);
151        else
152            return "{$ds_date}";
153    }
154    function ds2ln ($ds_date, $bold = false) {
155        $ds_date = trim ($ds_date);
156        if (preg_match ("#(?<Y>[0-9][0-9][0-9][0-9])(?<m>[0-9][0-9])(?<d>[0-9][0-9])#", $ds_date,  $dt_date)) {
157            // XXX prévoir le format anglais
158            setlocale (LC_TIME, 'fr_FR.utf8','fra');
159            return strftime (($bold ? "**%A %e %B** %Y" : "%A %e %B %Y"),
160                             mktime (0, 0, 0,
161                                     $dt_date[$this->currentDateFormat["fieldName"][1]],
162                                     $dt_date[$this->currentDateFormat["fieldName"][0]],
163                                     $dt_date[$this->currentDateFormat["fieldName"][2]]));
164        } else
165            return "{$ds_date}";
166    }
167
168    /* Convert array on string (words seperated bay '|')  */
169    function array2string ($array) {
170        if (!$array)
171            return "";
172        $array = array_unique ($array);
173        if (in_array ("", $array))
174            $array = array_diff ("", $array);
175        if (count ($array) < 1)
176            return "";
177        return implode ("|", $array);
178    }
179    /* Convert array on string (words seperated bay '|')  */
180    function assocImplode ($array, $vSep = '|', $kSep = ':') {
181        $result = "";
182        foreach ($array as $k => $v) {
183            $result .= $vSepNeeded.$k.$kSep.$v;
184            $vSepNeeded = $vSep;
185        }
186        return $result;
187    }
188    /* Convert array on string (words seperated bay '|')  */
189    function assocExplode ($string, $vSep = '|', $kSep = ':') {
190        $result = array ();
191        foreach (explode ($vSep, $string) as $kv) {
192            $data = explode ($kSep, $kv);
193            $result [$data[0]] = $data[1];
194        }
195        return $result;
196    }
197    /* Convert array on string (words seperated bay '|') */
198    // $ find data/[mp]*/membres/*/agenda/*/* data/[mp]*/commun/agenda/*/* -type f -print > dumy
199    // $ emacs dumy
200    //   C-x ( mv ' ' C-k C-y ' ' C-y C-f C-x )
201    //   M-1000 C-x e
202    //   C-M-%
203    //     ^\([^ ]* [^ ]* [^ ]*agenda/[^/ ]*_\)/ => \1
204    //   C-M-%
205    //     ^\([^ ]* [^ ]* [^ ]*agenda/[^/ ]*_\)/ => \1
206    //   C-M-%
207    //     ^\([^ ]* [^ ]* [^ ]*agenda/[^/ ]*\)/ => \1_
208    // $ find data/[mp]*/membres/*/agenda/* -type d -exec rmdir {} \; -print
209    function removeSep ($titleId) {
210        return strtr ($titleId, ':/;', '   ');
211    }
212
213    function getPageId ($schedule) {
214        return ":".
215                  $this->scheduleDir.":".
216                  ($schedule->shared ? $this->sharedDir : $schedule->member).":".
217                  $this->removeSep ($schedule->title);
218    }
219
220    function resetOddEven () {
221        $this->oddEven = 0;
222    }
223    function switchOddEven () {
224        $this->oddEven = 1 - $this->oddEven;
225    }
226
227    function getOddEven () {
228        return $this->oddEvenValues [$this->oddEven];
229    }
230
231    function createDirIsNeeded ($dir) {
232        if (is_dir ($dir))
233            return;
234        @mkdir ($dir);
235        @chmod ($dir, 0775);
236    }
237
238    // ============================================================
239    function __construct ($plugin) {
240        $this->plugin = $plugin;
241        $this->scheduleSaveAttributsName = array_merge ($this->scheduleRequestAttributsName, array ("weekDays"));
242        global $conf;
243        $savedir = ((!$conf['savedir'] || strpos ($conf['savedir'], '.') === 0) ? DOKU_INC : "").$conf['savedir']."/";
244        $this->cacheRootDir = $savedir."cache/schedule/";
245        $this->dataRootDir = $savedir.trim ($this->plugin->getConf ('dataDir').'/');
246        scheduleRoot::createDirIsNeeded ($this->cacheRootDir);
247        scheduleRoot::createDirIsNeeded ($this->dataRootDir);
248        $this->scheduleGroup = trim ($this->plugin->getConf ('scheduleGroup'));
249        $this->groupsDir = trim ($this->plugin->getConf ('groupsDir'));
250        $this->scheduleDir = trim ($this->plugin->getConf ('scheduleDir'));
251        $this->sharedDir = trim ($this->plugin->getConf ('sharedDir'));
252
253        foreach (explode ('|', $this->plugin->getConf ('scheduleWhat')) as $tmpCatDef) {
254            $tmpCatExp = explode (':', trim ($tmpCatDef));
255            $tmpCat = trim ($tmpCatExp[0]);
256            foreach (explode (',', $tmpCatExp[1]) as $tmpWhat) {
257                $tmpWhat = trim ($tmpWhat);
258                $this->scheduleWhat[$tmpWhat] = $tmpCat;
259            }
260        }
261        $this->scheduleAudience [""] = $this->plugin->getLang ('audienceChoice');
262        foreach (explode (',', $this->plugin->getConf ('scheduleAudience')) as $tmpAudience)
263            $this->scheduleAudience[trim ($tmpAudience)] = trim ($tmpAudience);
264        $this->iconName = $this->plugin->getConf ('iconName');
265        $this->iconWidth = $this->plugin->getConf ('iconWidth');
266        foreach (array (false, true) as $sharedVal) {
267            $tmp = $this->plugin->getLang ('shared');
268            $this->scheduleShared[] = trim ($tmp[$sharedVal]);
269        }
270        $this->currentDateFormat = $this->dateFormat [$this->plugin->getLang ('dateFormat')];
271
272        global $INFO;
273        $this->isAdmin =
274                       isset ($INFO ['userinfo']) &&
275                       isset ($INFO ['userinfo']['grps']) &&
276                       in_array (trim ($this->plugin->getConf ('adminGroup')), $INFO ['userinfo']['grps']);
277    }
278
279    // ============================================================
280    // Manage XML file
281    // ============================================================
282    /* read lodging config */
283    function readConfig ($dir) {
284        $oldFileName = $dir.$this->oldConfigFile;
285        $fileName = $dir.$this->configFile;
286
287        // rename old fashion membre file name
288        if (file_exists ($oldFileName)) {
289            // XXX migration
290            rename ($oldFileName, $fileName);
291            $exclude = ".|..|".$this->scheduleRoot->configFile;
292            $exclude_array = explode("|", $exclude);
293            $pathDir = rtrim ($dir, "/") . "/";
294            if (is_dir($pathDir)) {
295                $pathDirObj = opendir ($pathDir);
296                while (false !== ($file = readdir ($pathDirObj))) {
297                    if (in_array (strtolower ($file), $exclude_array))
298                        continue;
299                    $pathFile = $pathDir.$file;
300                    if (is_file ($pathFile) && preg_match ('#.*\.xml$#i', $file, $b))
301                        rename ($pathFile, $pathDir.$this->mbrPrefix.$file);
302                }
303            }
304        }
305        if (!file_exists ($fileName))
306            return false;
307        $result = array ();
308        // if (! class_exists ('DOMDocument'))
309        //     return $result;
310        $xml = new DOMDocument ("1.0", "utf8");
311        $xml->load ($fileName);
312        $root = $xml->documentElement;
313        foreach ($this->configAttributsName as $field) {
314            $element = $root->getElementsByTagName ($field);
315            if ($element)
316                $result [$field] = $element->item (0)->nodeValue;
317        }
318        return $result;
319    }
320    /* write lodging config */
321    function writeConfig ($schedules) {
322        $fileName = $schedules->dataDir.$this->configFile;
323        scheduleRoot::createDirIsNeeded ($schedules->dataDir);
324        // if (! class_exists ('DOMDocument'))
325        //     return;
326        $xml = new DOMDocument ("1.0", "utf8");
327        $root = $xml->createElement ("schedule");
328        $xml->appendChild ($root);
329        foreach ($this->configAttributsName as $field)
330            $root->appendChild ($xml->createElement ($field, htmlspecialchars ($schedules->$field)));
331        $xml->formatOutput = true;
332        $xml->save ($fileName);
333        chmod ($fileName, 0664);
334    }
335
336    // ============================================================
337    function manageAction ($request) {
338        if (!$this->isAdmin)
339            return;
340        $md5 = $request['md5'];
341        if (!$md5)
342            return;
343        $schedules = new schedules ($this, $request[$md5]['ns']);
344        switch ($request["action"]) {
345
346        case 'moveSchedules':
347            $md5 = $request['md5'];
348            $date = $this->df2ds ($request[$md5]['date']);
349            $md5bis = $request[$md5]['toNs'];
350            if (!$date)
351                return;
352            $src = new schedules ($this, $request[$md5]['ns']);
353            $src->load ();
354            $dst = new schedules ($this, $request[$md5bis]['ns']);
355            $dst->load ();
356            $filter = array ();
357            foreach ($src->allSchedules as $id => $schedule)
358                if (($schedule->to ? $schedule->to : $schedule->from) < $date)
359                    $filter [$id] = $schedule;
360            foreach ($filter as $id => $schedule) {
361                $dst->addSchedule ($schedule);
362                $src->removeSchedule ($id);
363            }
364            $dst->writeSchedules ();
365            $src->writeSchedules ();
366
367            unset ($_REQUEST['schd']);
368            break;
369        default:
370            return;
371        }
372    }
373
374    // ============================================================
375    function printForm () {
376        if (!$this->isAdmin)
377            return;
378        $list = $this->readAllSchedules ();
379        echo
380            '<form method="POST" action="" onSubmit="return false;" >'.NL.
381            ' <table class="admin" >'.NL.
382            '  <thead>'.NL.
383            // XXX getLang ()
384            '   <tr><th>Description</th><th>Valeurs</th></tr>'.NL.
385            '  </thead>'.NL.
386            '  <tbody>'.NL;
387        $this->resetOddEven ();
388        foreach ($list as $md5 => $item) {
389            $nameSpace = $item ["nameSpace"];
390            $schedules = $item ["schedules"];
391            echo
392                '   <tr class="'.$this->getOddEven ().'">'.NL.
393                '    <td class="name">NameSpace</td>'.NL.
394                '    <td class="value"><input type="hidden" name="schd['.$md5.'][ns]" value="'.$nameSpace.'"/>'.$nameSpace.'</td>'.NL.
395                '   </tr>'.NL.
396                '   <tr class="'.$this->getOddEven ().'">'.NL.
397                '    <td class="name">MD5</td>'.NL.
398                '    <td class="value">'.$md5.'</td>'.NL.
399                '   </tr>'.NL.
400                '   <tr class="'.$this->getOddEven ().'">'.NL.
401                '    <td class="name">Members</td>'.NL.
402                '    <td class="value">'.count ($schedules->memberSchedules).'</td>'.NL.
403                '   </tr>'.NL.
404                '   <tr class="'.$this->getOddEven ().'">'.NL.
405                '    <td class="name">Events</td>'.NL.
406                '    <td class="value">'.count ($schedules->datedSchedules).'</td>'.NL.
407                '   </tr>'.NL.
408                '   <tr class="'.$this->getOddEven ().'">'.NL.
409                '    <td class="name">Repeated</td>'.NL.
410                '    <td class="value">'.count ($schedules->repeatedSchedules).'</td>'.NL.
411                '   </tr>'.NL.
412                '   <tr class="'.$this->getOddEven ().'">'.NL.
413                '    <td class="name">Date</td>'.NL.
414                '    <td><input type="text" name="schd['.$md5.'][date]" value="'.'"  placeholder="'.$this->plugin->getLang ('rootMoveDatePH').'"/></td>'.NL.
415                '   </tr>'.NL.
416                '   <tr class="'.$this->getOddEven ().'">'.NL.
417                '    <td class="name">Vers</td>'.NL.
418                '    <td><select name="schd['.$md5.'][toNs]"/>'.NL;
419            foreach ($list as $md5bis => $itemBis)
420                if ($md5bis != $md5)
421                    echo
422                        '      <option value="'.$md5bis.'">'.$itemBis["nameSpace"].'</option>'.NL;
423            echo
424                '    </select></td>'.NL.
425                '   </tr>'.NL.
426                '   <tr class="'.$this->getOddEven ().'">'.NL.
427                '    <td class="name">Action</td>'.NL.
428                '    <td><input type="submit" name="action" value="moveSchedules" onClick="scheduleAjax(this.form,\'moveSchedules\',\''.$md5.'\')" /></td>'.NL.
429                '   </tr>'.NL;
430            $this->switchOddEven ();
431        }
432        echo
433            '  </tbody>'.NL.
434            ' </table>'.NL.
435            '</form>'.NL;
436    }
437
438    // ============================================================
439    function readAllSchedules () {
440        $exclude_array = explode ("|", ".|..");
441        if (!is_dir($this->dataRootDir))
442            return;
443        $pathDirObj = opendir ($this->dataRootDir);
444        while (false !== ($file = readdir ($pathDirObj))) {
445            $subDir = $this->dataRootDir.$file.'/';
446            if (in_array (strtolower ($file), $exclude_array) || !is_dir ($subDir))
447                continue;
448            $list [$file] = $this->readConfig ($subDir);
449            $list [$file]["schedules"] = new schedules ($this,  $list [$file]["nameSpace"]);
450            $list [$file]["schedules"]->load ();
451        }
452        uasort ($list, "cmpScheduleConfig");
453        return $list;
454    }
455
456    function clearCache () {
457        $list = $this->readAllSchedules ();
458        foreach ($list as $md5 => $item) {
459            $schedules = $item ["schedules"];
460            $schedules->clearCache ();
461        }
462    }
463
464    // ============================================================
465}
466