<?php
/**
 * @license    http://www.cecill.info/licences/Licence_CeCILL-B_V1-fr.html
 * @author     Francois Merciol <dokuplugin@merciol.fr>
 *
 * Plugin Schedule: manage events per wiki @groups
 *
 * $df_ : 31/12/2000
 * $ds_ : 20001231
 * $dn_ : 978217200
 * $dt_ : array ("Y" => 2000, "m" => 12, "d" => "31)
 */
if (!defined ('DOKU_INC'))
    define ('DOKU_INC', realpath (dirname (__FILE__).'/../../../').'/');
if (!defined ('DOKU_PLUGIN'))
    define ('DOKU_PLUGIN', DOKU_INC.'lib/plugins/');

function cmpScheduleConfig ($a, $b) {
    return ($a['nameSpace'] < $b['nameSpace']) ? -1 : 1;
}

// ============================================================
// admin class
class scheduleRoot {

    // ============================================================
    // Config attributs
    // ============================================================
    var $cacheRootDir;					// root cache directory
    var $dataRootDir;					// root data directory

    var $scheduleGroup;					// group who can display and fill form
    var $groupsDir;						// pages groups directories
    var $scheduleDir;					// schedule pages groups directories
    var $sharedDir;						// schedule directory for shared events
    var $scheduleWhat = array ();		// set of legal "what" 
    var $scheduleAudience = array ();	// set of legal "audience"
    var $iconName;						// group icon name
    var $iconWidth;						// group icon width
    // lang
    var $scheduleShared = array ();		// notshared/shared
    var $currentDateFormat;

    // ============================================================
    // Constant attributs $this->scheduleRoot->
    // ============================================================
    //var $imagesUrlDir = '/lib/plugins/schedule/images/'; // DOKU_REL
    var $wizardIconUrl = '/lib/plugins/schedule/images/wizard.png'; // $this->imagesUrlDir
    var $editIconUrl = '/lib/plugins/schedule/images/edit.png';
    var $repeatIconUrl = '/lib/plugins/schedule/images/repeat.png';
    var $emptyIconUrl = '/lib/plugins/schedule/images/empty.png';

    var $oldConfigFile = "config.xml";	// old file config name
    var $configFile = "cfg.xml";		// file config name
    var $mbrPrefix = "mbr-";			// member prefix file name
    var $propFile = "prop.xml";			// member prefix file name
    var $configAttributsName = array ("nameSpace", "lastNotification", "lastNotificationReset");
    // scheduleDir == NS ?
    var $dateFormat = array ("dmY" => array ("shortScan" => "(?<d>[0-9]{1,2})[ /-](?<m>[0-9]{1,2})[ /-](?<Y>[0-9]{1,2})",
                                             "longScan" => "(?<d>[0-9]{1,2})[ /-](?<m>[0-9]{1,2})[ /-](?<Y>[0-9]{4})",
                                             "fieldName" => array ("d", "m", "Y"),
                                             "prettyPrint" => "d/m/Y",
                                             "dayMonth" => "d/m"),

                             "mdY" => array ("shortScan" => "(?<m>[0-9]{1,2})[ /-](?<d>[0-9]{1,2})[ /-](?<Y>[0-9]{1,2})",
                                             "longScan" => "(?<m>[0-9]{1,2})[ /-](?<d>[0-9]{1,2})[ /-](?<Y>[0-9]{4})",
                                             "fieldName" => array ("m", "d", "Y"),
                                             "prettyPrint" => "m/d/Y",
                                             "dayMonth" => "m/d"));
    var $oddEvenValues = array ("odd", "even");
    var $scheduleRequestAttributsName = array ("id", "audience", "shared",
                                               "what", "title", "lead", "posterURL", "paperURL", "remark", "rate",
                                               "where", "lon", "lat", "addr",
                                               "from", "to", "at",
                                               "repeat", "repeatType", "weekRank", "dayInWeek", "dayRank",
                                               "user");
    var $scheduleSaveAttributsName;
    var $filterNames = array ("noMember" => "member",
                              "noWhat" => "what",
                              "NoAudience" => "audience");


    // ============================================================
    // Transcient attributs
    // ============================================================
    var $message = array ();		// messages to display : "notify" =>, "info" =>, "success" =>, "error" =>
    var $plugin;				// link to wiki plugin inforation (conf, lang, ...)
    var $isAdmin;				// if user member of schedule adminGroup
    var $oddEven = 0;			// background color for table lines

    // ============================================================
    // Util
    // ============================================================
    /* messages container to be display before plugin */
    function message ($type, $text) {
        if (isset ($this->message[$type]))
            $this->message[$type] .= '<br/>'.NL;
        $this->message[$type] .= $text;
    }
    function startMessage ($type, $text) {
        if (isset ($this->message[$type]))
            $this->message[$type] = '<br/>'.NL.$this->message[$type];
        $this->message[$type] = $text.$this->message[$type];
    }
    /* debug messages for admin only */
    function debug ($text) {
        global $INFO;
        // XXX
        if (isset ($INFO['userinfo'] ['grps']) && in_array ('admin', $INFO ['userinfo'] ['grps']))
            $this->message ('notify', '<pre>'.$text.'</pre>');
    }
    /* Change date format from array to number  */
    function dt2dn ($dt_date) {
        $dn_date = mktime (0, 0, 0, $dt_date["m"], $dt_date["d"], $dt_date["Y"]);
        if (!$dn_date)
            $this->message ('error', $dt_date["Y"]."-".$dt_date["d"]."-".$dt_date["m"]." : ".$this->plugin->getLang ('mktimeError').'<br/>'.NL);
        return $dn_date;
    }
    /* Check date format 31/12/2000 */
    function checkDS ($ds_date) {
        return
            preg_match ("#([0-9]{8})#", $ds_date,  $dumy);
    }
    /* Change date format from MM/DD/AAAA to number  */
    function df2ds ($df_date) {
        $df_date = trim ($df_date);
        if (!$df_date)
            return;
        if ("!" == $df_date)
            $ds_date = date ("Ymd");
        elseif (preg_match ("#\+(?<delta>[0-9]+)#", $df_date, $matches))
            $ds_date = sprintf ("%08d", date ("Ymd", mktime (0, 0, 0, date ("n"), date ("j")+$matches["delta"], date ("Y"))));
        elseif (preg_match ("#\-(?<delta>[0-9]+)#", $df_date, $matches))
            $ds_date = sprintf ("%08d",  date ("Ymd", mktime (0, 0, 0, date ("n"), date ("j")-$matches["delta"], date ("Y"))));
        elseif (preg_match ("#".$this->currentDateFormat ["longScan"]."#", $df_date, $dt_date))
            $ds_date = sprintf ("%04d%02d%02d", $dt_date["Y"], $dt_date["m"], $dt_date["d"]);
        elseif (preg_match ("#".$this->currentDateFormat ["shortScan"]."#", $df_date, $dt_date))
            $ds_date = sprintf ("20%02d%02d%02d", $dt_date["Y"], $dt_date["m"], $dt_date["d"]);
        else
            $ds_date = "{$df_date}";
        return $ds_date;
    }
    /* Change date format from number to MM/DD/AAAA  */
    function ds2df ($ds_date) {
        $ds_date = trim ($ds_date);
        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))
            return sprintf ("%02d/%02d/%04d",
                            $dt_date[$this->currentDateFormat["fieldName"][0]],
                            $dt_date[$this->currentDateFormat["fieldName"][1]],
                            $dt_date[$this->currentDateFormat["fieldName"][2]]);
        else
            return "{$ds_date}";
    }
    function ds2ln ($ds_date, $bold = false) {
        $ds_date = trim ($ds_date);
        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)) {
            // XXX prévoir le format anglais
            setlocale (LC_TIME, 'fr_FR.utf8','fra'); 
            return strftime (($bold ? "**%A %e %B** %Y" : "%A %e %B %Y"),
                             mktime (0, 0, 0,
                                     $dt_date[$this->currentDateFormat["fieldName"][1]],
                                     $dt_date[$this->currentDateFormat["fieldName"][0]],
                                     $dt_date[$this->currentDateFormat["fieldName"][2]]));
        } else
            return "{$ds_date}";
    }

    /* Convert array on string (words seperated bay '|')  */
    static function array2string ($array) {
        if (!$array)
            return "";
        $array = array_unique ($array);
        if (in_array ("", $array))
            $array = array_diff ("", $array);
        if (count ($array) < 1)
            return "";
        return implode ("|", $array);
    }
    /* Convert array on string (words seperated bay '|')  */
    function assocImplode ($array, $vSep = '|', $kSep = ':') {
        $result = "";
        foreach ($array as $k => $v) {
            $result .= $vSepNeeded.$k.$kSep.$v;
            $vSepNeeded = $vSep;
        }
        return $result;
    }
    /* Convert array on string (words seperated bay '|')  */
    function assocExplode ($string, $vSep = '|', $kSep = ':') {
        $result = array ();
        foreach (explode ($vSep, $string) as $kv) {
            $data = explode ($kSep, $kv);
            $result [$data[0]] = $data[1];
        }
        return $result;
    }
    /* Convert array on string (words seperated bay '|') */
    // $ find data/[mp]*/membres/*/agenda/*/* data/[mp]*/commun/agenda/*/* -type f -print > dumy
    // $ emacs dumy
    //   C-x ( mv ' ' C-k C-y ' ' C-y C-f C-x )
    //   M-1000 C-x e
    //   C-M-%
    //     ^\([^ ]* [^ ]* [^ ]*agenda/[^/ ]*_\)/ => \1
    //   C-M-%
    //     ^\([^ ]* [^ ]* [^ ]*agenda/[^/ ]*_\)/ => \1
    //   C-M-%
    //     ^\([^ ]* [^ ]* [^ ]*agenda/[^/ ]*\)/ => \1_
    // $ find data/[mp]*/membres/*/agenda/* -type d -exec rmdir {} \; -print
    function removeSep ($titleId) {
        return strtr ($titleId, ':/;', '   ');
    }

    function getPageId ($schedule) {
        return ":".
                  $this->scheduleDir.":".
                  ($schedule->shared ? $this->sharedDir : $schedule->member).":".
                  $this->removeSep ($schedule->title);
    }

    function resetOddEven () {
        $this->oddEven = 0;
    }
    function switchOddEven () {
        $this->oddEven = 1 - $this->oddEven;
    }

    function getOddEven () {
        return $this->oddEvenValues [$this->oddEven];
    }

     static function createDirIsNeeded ($dir) {
        if (is_dir ($dir))
            return;
        @mkdir ($dir);
        @chmod ($dir, 0775);
    }

    // ============================================================
    function __construct ($plugin) {
        $this->plugin = $plugin;
        $this->scheduleSaveAttributsName = array_merge ($this->scheduleRequestAttributsName, array ("weekDays"));
        global $conf;
        $savedir = ((!$conf['savedir'] || strpos ($conf['savedir'], '.') === 0) ? DOKU_INC : "").$conf['savedir']."/";
        $this->cacheRootDir = $savedir."cache/schedule/";
        $this->dataRootDir = $savedir.trim ($this->plugin->getConf ('dataDir').'/');
        scheduleRoot::createDirIsNeeded ($this->cacheRootDir);
        scheduleRoot::createDirIsNeeded ($this->dataRootDir);
        $this->scheduleGroup = trim ($this->plugin->getConf ('scheduleGroup'));
        $this->groupsDir = trim ($this->plugin->getConf ('groupsDir'));
        $this->scheduleDir = trim ($this->plugin->getConf ('scheduleDir'));
        $this->sharedDir = trim ($this->plugin->getConf ('sharedDir'));

        foreach (explode ('|', $this->plugin->getConf ('scheduleWhat')) as $tmpCatDef) {
            $tmpCatExp = explode (':', trim ($tmpCatDef));
            $tmpCat = trim ($tmpCatExp[0]);
            foreach (explode (',', $tmpCatExp[1]) as $tmpWhat) {
                $tmpWhat = trim ($tmpWhat);
                $this->scheduleWhat[$tmpWhat] = $tmpCat;
            }
        }
        $this->scheduleAudience [""] = $this->plugin->getLang ('audienceChoice');
        foreach (explode (',', $this->plugin->getConf ('scheduleAudience')) as $tmpAudience)
            $this->scheduleAudience[trim ($tmpAudience)] = trim ($tmpAudience);
        $this->iconName = $this->plugin->getConf ('iconName');
        $this->iconWidth = $this->plugin->getConf ('iconWidth');
        foreach (array (false, true) as $sharedVal) {
            $tmp = $this->plugin->getLang ('shared');
            $this->scheduleShared[] = trim ($tmp[$sharedVal]);
        }
        $this->currentDateFormat = $this->dateFormat [$this->plugin->getLang ('dateFormat')];

        global $INFO;
        $this->isAdmin =
                       isset ($INFO ['userinfo']) &&
                       isset ($INFO ['userinfo']['grps']) &&
                       in_array (trim ($this->plugin->getConf ('adminGroup')), $INFO ['userinfo']['grps']);
    }

    // ============================================================
    // Manage XML file
    // ============================================================
    /* read lodging config */
    function readConfig ($dir) {
        $oldFileName = $dir.$this->oldConfigFile;
        $fileName = $dir.$this->configFile;

        // rename old fashion membre file name
        if (file_exists ($oldFileName)) {
            // XXX migration
            rename ($oldFileName, $fileName);
            $exclude = ".|..|".$this->scheduleRoot->configFile;
            $exclude_array = explode("|", $exclude);
            $pathDir = rtrim ($dir, "/") . "/";
            if (is_dir($pathDir)) {
                $pathDirObj = opendir ($pathDir);
                while (false !== ($file = readdir ($pathDirObj))) {
                    if (in_array (strtolower ($file), $exclude_array))
                        continue;
                    $pathFile = $pathDir.$file;
                    if (is_file ($pathFile) && preg_match ('#.*\.xml$#i', $file, $b))
                        rename ($pathFile, $pathDir.$this->mbrPrefix.$file);
                }
            }
        }
        if (!file_exists ($fileName))
            return false;
        $result = array ();
        // if (! class_exists ('DOMDocument'))
        //     return $result;
        $xml = new DOMDocument ("1.0", "utf8");
        $xml->load ($fileName);
        $root = $xml->documentElement;
        foreach ($this->configAttributsName as $field) {
            $element = $root->getElementsByTagName ($field);
            if ($element)
                $result [$field] = $element->item (0)->nodeValue;
        }
        return $result;
    }
    /* write lodging config */
    function writeConfig ($schedules) {
        $fileName = $schedules->dataDir.$this->configFile;
        scheduleRoot::createDirIsNeeded ($schedules->dataDir);
        // if (! class_exists ('DOMDocument'))
        //     return;
        $xml = new DOMDocument ("1.0", "utf8");
        $root = $xml->createElement ("schedule");
        $xml->appendChild ($root);
        foreach ($this->configAttributsName as $field)
            $root->appendChild ($xml->createElement ($field, htmlspecialchars ($schedules->$field)));
        $xml->formatOutput = true;
        $xml->save ($fileName);
        chmod ($fileName, 0664);
    }

    // ============================================================
    function manageAction ($request) {
        if (!$this->isAdmin)
            return;
        $md5 = $request['md5'];
        if (!$md5)
            return;
        $schedules = new schedules ($this, $request[$md5]['ns']);
        switch ($request["action"]) {

        case 'moveSchedules':
            $md5 = $request['md5'];
            $date = $this->df2ds ($request[$md5]['date']);
            $md5bis = $request[$md5]['toNs'];
            if (!$date)
                return;
            $src = new schedules ($this, $request[$md5]['ns']);
            $src->load ();
            $dst = new schedules ($this, $request[$md5bis]['ns']);
            $dst->load ();
            $filter = array ();
            foreach ($src->allSchedules as $id => $schedule)
                if (($schedule->to ? $schedule->to : $schedule->from) < $date)
                    $filter [$id] = $schedule;
            foreach ($filter as $id => $schedule) {
                $dst->addSchedule ($schedule);
                $src->removeSchedule ($id);
            }
            $dst->writeSchedules ();
            $src->writeSchedules ();

            unset ($_REQUEST['schd']);
            break;
        default:
            return;
        }
    }

    // ============================================================
    function printForm () {
        if (!$this->isAdmin)
            return;
        $list = $this->readAllSchedules ();
        echo
            '<form method="POST" action="" onSubmit="return false;" >'.NL.
            ' <table class="admin" >'.NL.
            '  <thead>'.NL.
            // XXX getLang ()
            '   <tr><th>Description</th><th>Valeurs</th></tr>'.NL.
            '  </thead>'.NL.
            '  <tbody>'.NL;
        $this->resetOddEven ();
        foreach ($list as $md5 => $item) {
            $nameSpace = $item ["nameSpace"];
            $schedules = $item ["schedules"];
            echo
                '   <tr class="'.$this->getOddEven ().'">'.NL.
                '    <td class="name">NameSpace</td>'.NL.
                '    <td class="value"><input type="hidden" name="schd['.$md5.'][ns]" value="'.$nameSpace.'"/>'.$nameSpace.'</td>'.NL.
                '   </tr>'.NL.
                '   <tr class="'.$this->getOddEven ().'">'.NL.
                '    <td class="name">MD5</td>'.NL.
                '    <td class="value">'.$md5.'</td>'.NL.
                '   </tr>'.NL.
                '   <tr class="'.$this->getOddEven ().'">'.NL.
                '    <td class="name">Members</td>'.NL.
                '    <td class="value">'.count ($schedules->memberSchedules).'</td>'.NL.
                '   </tr>'.NL.
                '   <tr class="'.$this->getOddEven ().'">'.NL.
                '    <td class="name">Events</td>'.NL.
                '    <td class="value">'.count ($schedules->datedSchedules).'</td>'.NL.
                '   </tr>'.NL.
                '   <tr class="'.$this->getOddEven ().'">'.NL.
                '    <td class="name">Repeated</td>'.NL.
                '    <td class="value">'.count ($schedules->repeatedSchedules).'</td>'.NL.
                '   </tr>'.NL.
                '   <tr class="'.$this->getOddEven ().'">'.NL.
                '    <td class="name">Date</td>'.NL.
                '    <td><input type="text" name="schd['.$md5.'][date]" value="'.'"  placeholder="'.$this->plugin->getLang ('rootMoveDatePH').'"/></td>'.NL.
                '   </tr>'.NL.
                '   <tr class="'.$this->getOddEven ().'">'.NL.
                '    <td class="name">Vers</td>'.NL.
                '    <td><select name="schd['.$md5.'][toNs]"/>'.NL;
            foreach ($list as $md5bis => $itemBis)
                if ($md5bis != $md5)
                    echo
                        '      <option value="'.$md5bis.'">'.$itemBis["nameSpace"].'</option>'.NL;
            echo
                '    </select></td>'.NL.
                '   </tr>'.NL.
                '   <tr class="'.$this->getOddEven ().'">'.NL.
                '    <td class="name">Action</td>'.NL.
                '    <td><input type="submit" name="action" value="moveSchedules" onClick="scheduleAjax(this.form,\'moveSchedules\',\''.$md5.'\')" /></td>'.NL.
                '   </tr>'.NL;
            $this->switchOddEven ();
        }
        echo
            '  </tbody>'.NL.
            ' </table>'.NL.
            '</form>'.NL;
    }

    // ============================================================
    function readAllSchedules () {
        $exclude_array = explode ("|", ".|..");
        if (!is_dir($this->dataRootDir))
            return;
        $pathDirObj = opendir ($this->dataRootDir);
        while (false !== ($file = readdir ($pathDirObj))) {
            $subDir = $this->dataRootDir.$file.'/';
            if (in_array (strtolower ($file), $exclude_array) || !is_dir ($subDir))
                continue;
            $list [$file] = $this->readConfig ($subDir);
            $list [$file]["schedules"] = new schedules ($this,  $list [$file]["nameSpace"]);
            $list [$file]["schedules"]->load ();
        }
        uasort ($list, "cmpScheduleConfig");
        return $list;
    }

    function clearCache () {
        $list = $this->readAllSchedules ();
        foreach ($list as $md5 => $item) {
            $schedules = $item ["schedules"];
            $schedules->clearCache ();
        }
    }

    // ============================================================
}