<?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

 // TODO :
 // passer des actions suivant langue en actions neutres
 // selection automatique de cantons suivant lieu si existe le canton existe
 // radius dans conf puis select

 */
if (!defined ('DOKU_INC'))
    define ('DOKU_INC', realpath (dirname (__FILE__).'/../../../').'/');
if (!defined ('DOKU_PLUGIN'))
    define ('DOKU_PLUGIN', DOKU_INC.'lib/plugins/');

require_once (realpath (dirname (__FILE__)).'/scheduleInseeCities.php');

// ============================================================
// management event class
class schedules {

    // ============================================================
    // Config attributs
    // ============================================================
    var $scheduleRoot;					// commun data for all schedules

    var $nameSpace;						// namespace where definition could be found
    var $lastNotification;				// time of last proposal notification
    var $lastNotificationReset;			// time of last administrators acknowledgement
    var $md5ns;							// the ns directory
    var $cacheDir;						// cache directory
    var $dataDir;						// schedule database

    var $migrate;						// performe data migration
    var $showOldValues;					// highlight non migrate data

    // ============================================================
    // Transcient attributs
    // ============================================================
    var $allGroups = array ();			// dynamique set of groups name
    var $allSchedules = array ();		// set of all events
    var $memberSchedules = array ();	// set of events indexed by member
    var $repeatedSchedules = array ();	// set of repeated events indexed by date
    var $fixedSchedules = array ();		// set of non repeated events indexed by date
    var $datedSchedules = array ();		// set of repeated or not events indexed by date
    var $membersToSave = array ();		// set of modified members
    var $allProposals = array ();		// set of all proposals
    var $proposalsToSave = false;		// modified proposals

    var $printProp = false;				// display proposal event form
    var $printForm = false;				// display add event form
    var $printCtrl = false;				// display action event form
    var $isShow = false;				// date focus flag mode
    var $userGroups = array ();			// set of user group wich are schedule groupe
    var $defaultSchedule;				// empty schedule model

    var $before;						// before filter date
    var $date;							// exact filter date
    var $after;							// after filter date
    var $max;							// max events lines
    var $filters;

    // ============================================================
    // Util
    // ============================================================
    function getConf ($name) {
        return $this->scheduleRoot->plugin->getConf ($name);
    }
    function getLang ($name) {
        return $this->scheduleRoot->plugin->getLang ($name);
    }
    function isAdmin () {
        return $this->scheduleRoot->isAdmin;
    }
    function message ($type, $text) {
        return $this->scheduleRoot->message ($type, $text);
    }
    function startMessage ($type, $text) {
        return $this->scheduleRoot->startMessage ($type, $text);
    }
    function debug ($text) {
        return $this->scheduleRoot->debug ($text);
    }
    function isMessage ($type) {
        return isset ($this->scheduleRoot->message[$type]);
    }

    // ============================================================
    function __construct ($scheduleRoot, $ns) {
        $this->scheduleRoot = $scheduleRoot;
        $this->defaultSchedule = new schedule ();
        $this->nameSpace = cleanId (trim ($ns));
        $this->md5ns = md5 ($this->nameSpace);
        $this->cacheDir = $this->scheduleRoot->cacheRootDir.$this->md5ns.'/';
        $this->dataDir = $this->scheduleRoot->dataRootDir.$this->md5ns.'/';
        $this->migrate = $this->getConf ('migrate');
        $this->showOldValues = $this->getConf ('showOldValues');
        if ($this->migrate) {
            global $scheduleInseeCities, $scheduleCitiesInsee;
            $scheduleCitiesInsee = array ();
            foreach ($scheduleInseeCities as $insee => $cityDef)
                $scheduleCitiesInsee [strtolower ($cityDef[0])] = $insee;
        }

        $config = $this->scheduleRoot->readConfig ($this->dataDir);
        if (!$config)
            $this->scheduleRoot->writeConfig ($this);
        else
            foreach ($this->scheduleRoot->configAttributsName as $field)
                if (isset ($config [$field]))
                    $this->$field = $config [$field];
    }
    /** XXX utilisé par scheduleRoot ?! */
    function load () {
        $exclude_array = explode ('|', '.|..');
        global $conf;
        $pathDir = $conf['savedir'].'/pages/'.trim ($this->scheduleRoot->groupsDir, '/') . '/';
        if (is_dir($pathDir)) {
            $pathDirObj = opendir ($pathDir);
            while (false !== ($file = readdir ($pathDirObj))) {
                if (in_array (strtolower ($file), $exclude_array))
                    continue;
                $pathFile = $pathDir . $file;
                // XXX precedent se trouve dans les membres ???
                if (is_dir ($pathFile) && !in_array ($file, explode (',', trim ($this->getConf ('noSchedule')))))
                    $this->allGroups [] = $file;
            }
        }
        sort ($this->allGroups);

        global $INFO;
        if (isset ($INFO ['userinfo']) && isset ($INFO ['userinfo']['grps'])) {
            $userGroups = array ();
            $scheduleGroup = false;
            foreach ($INFO['userinfo']['grps'] as $tmpMember) {
                $tmpMember = trim ($tmpMember);
                if ($this->scheduleRoot->scheduleGroup == $tmpMember)
                    $scheduleGroup = true;
                if (in_array ($tmpMember, $this->allGroups))
                    $userGroups [] = $tmpMember;
            }
            if ($scheduleGroup)
                $this->userGroups = $userGroups;
        }
        $this->readSchedules ($this->dataDir);
        $this->readProp ();
    }

    function isMember ($member) {
        return $this->isAdmin () || in_array ($member, $this->userGroups);
    }

    function isAMemberOf ($members) {
        if ($this->isAdmin ())
            return true;
        foreach ($members as $member)
            if (in_array ($member, $this->userGroups))
                return true;
        return false;
    }

    function isMemberOfAll ($members) {
        if ($this->isAdmin ())
            return true;
        foreach ($members as $member)
            if (!in_array ($member, $this->userGroups))
                return false;
        return true;
    }

    // ============================================================
    function readCache ($key) {
        if (!is_dir ($this->cacheDir))
            return false;
        $filename = $this->cacheDir.md5 ($key).'.cache';
        if (!file_exists ($filename))
            return false;
        // YYY date du fichier (le calendrier change tous les jours)
        if (filemtime ($filename) < strtotime ('today'))
            return false;
        // XXX ??? Warning: file_get_contents() [function.file-get-contents]: failed to open stream: No such file or directory in  on line 153
        return file_get_contents ($filename);
    }

    function writeCache ($key, $text) {
        scheduleRoot::createDirIsNeeded ($this->cacheDir);
        $filename = $this->cacheDir.md5 ($key).'.cache';
        file_put_contents ($filename, $text);
    }

    function clearCache () {
        if (!$this->isAdmin ())
            return;
        $exclude = '.|..|'.$this->scheduleRoot->configFile;
        $exclude_array = explode('|', $exclude);
        $pathDir = $this->cacheDir;
        if (!is_dir($pathDir))
            return;
        $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 ('#.*\.cache#i', $file, $b))
                unlink ($pathFile);
        }
        @rmdir ($pathDir);
    }

    // ============================================================
    function setDateFilters ($before, $date, $after) {
        $this->before = $this->scheduleRoot->df2ds ($before);
        $this->after = $this->scheduleRoot->df2ds ($after);
        $this->date = $this->scheduleRoot->df2ds ($date);
        if ($this->date)
            $this->before = $this->after = $this->date;
    }

    // ============================================================
    function setFormLevel ($formLevel) {
        global $INFO;
        if ($formLevel > 2)
            $this->printProp = true;
        if (($formLevel > 0) &&
            isset ($INFO ['userinfo']) &&
            isset ($INFO ['userinfo']['grps']))
            foreach ($INFO['userinfo']['grps'] as $tmpMember) {
                $tmpMember = trim ($tmpMember);
                if ($this->scheduleRoot->scheduleGroup == $tmpMember) {
                    if ($formLevel > 1)
                        $this->printForm = true;
                    $this->printCtrl = true;
                    $this->printProp = false;
                    break;
                }
            }
    }

    // ============================================================
    function setOtherFilters ($max, $filters, $grouped) {
        $this->max = $max;
        foreach ($filters as $name => $values) {
            $newValues = array ();
            foreach (explode (',', $values) as $value) {
                $value = trim ($value);
                if (! empty ($value))
                    $newValues [] = $value;
            }
            if (!empty ($newValues))
                $this->filters[$name] =  $newValues;
        }
        $this->repeatGrouped = trim ($this->getConf ('repeatPosition')) == 'grouped';
        $grouped = trim ($grouped);
        if (in_array ($grouped, array ('grouped', 'isolated')))
            $this->repeatGrouped = $grouped == 'grouped';
    }

    // ============================================================
    // Manage XML file
    // ============================================================
    /* Save a member data base */
    function writeSchedule ($member) {
        $xml = new DOMDocument ('1.0', 'utf8');
        $root = $xml->createElement ('schedule');
        $root->setAttribute ('member', $member);
        $xml->appendChild ($root);
        foreach ($this->memberSchedules [$member] as $id => $schedule) {
            $event = $xml->createElement ('event');
            foreach ($this->scheduleRoot->scheduleSaveAttributsName as $field)
                $event->appendChild ($xml->createElement ($field, htmlspecialchars ($schedule->$field)));
            $root->appendChild ($event);
        }
        $xml->formatOutput = true;
        $fileName = $this->dataDir.$this->scheduleRoot->mbrPrefix.$member.'.xml';
        $xml->save ($fileName);
        chmod ($fileName, 0664);
    }

    function readProp () {
        $this->readSchedule ($this->dataDir.$this->scheduleRoot->propFile, true);
    }

    function writeProp () {
        // if (! class_exists ('DOMDocument'))
        //     return;
        $xml = new DOMDocument ('1.0', 'utf8');
        $root = $xml->createElement ('schedule');
        $root->setAttribute ('member', 'proposal');
        $xml->appendChild ($root);
        foreach ($this->allProposals as $id => $schedule) {
            $event = $xml->createElement ('event');
            foreach ($this->scheduleRoot->scheduleSaveAttributsName as $field)
                $event->appendChild ($xml->createElement ($field, htmlspecialchars ($schedule->$field)));
            $event->appendChild ($xml->createElement ('member', htmlspecialchars ($schedule->member)));
            $root->appendChild ($event);
        }
        $xml->formatOutput = true;
        $fileName = $this->dataDir.$this->scheduleRoot->propFile;
        $xml->save ($fileName);
        chmod ($fileName, 0664);
    }

    /* Migrate where format */
    function whereMigrate ($schedule, $sep) {
        global $scheduleCitiesInsee;
        if (!$schedule)
            return false;
        if (preg_match ('/[0-9]/', $schedule->where)) {
            return false;
        }
        $newFields = array ();
        $updated = false;
        foreach (explode ($sep, $schedule->where) as $city) {
            $city = trim ($city);
            if (!$city)
                continue;
            $insee = $scheduleCitiesInsee [strtolower ($city)];
            $newFields [] = $insee ? $insee : $city;
            if ($insee)
                $updated = true;
        }
        $newWhere = implode (',', $newFields);
        if (!$newWhere || !$updated)
            return false;
        $schedule->where = $newWhere;
        return true;
    }

    /* Read a member data base */
    function readSchedule ($file, $prop = false) {
        if (!file_exists ($file))
            return;
        // if (! class_exists ('DOMDocument'))
        //     return;
        $xml = new DOMDocument ('1.0', 'utf8');
        $xml->load ($file);
        $root = $xml->documentElement;
        $member = $root->getAttribute ('member');
        $events = $root->getElementsByTagName ('event');
        for ($i = $events->length; --$i >= 0; ) {
            $event = $events->item ($i);
            $schedule = new schedule ();
            foreach ($this->scheduleRoot->scheduleSaveAttributsName as $field) {
                $element = $event->getElementsByTagName ($field);
                if ($element)
                    $schedule->$field = $element->item (0)->nodeValue;
            }
            if ($prop) {
                $element = $event->getElementsByTagName ('member');
                if ($element)
                    $schedule->member = $element->item (0)->nodeValue;
            } else
                $schedule->member = $member;
            if ($this->migrate && !$prop &&
                ($this->whereMigrate ($schedule, ',') ||
                 $this->whereMigrate ($schedule, '/') ||
                 $this->whereMigrate ($schedule, ' - ') ||
                 $this->whereMigrate ($schedule, ' ')))
                $this->membersToSave [$member] = true;
            // XXX compatibilité
            $schedule->shared = ($schedule->shared == true);
            $schedule->repeatFlag = $schedule->to != '' && $schedule->from != $schedule->to;
            if ($prop)
                $this->allProposals [$schedule->id] = $schedule;
            else
                $this->allSchedules [$schedule->id] = $schedule;
        }
    }

    // ============================================================
    // IO functions
    // ============================================================
    /* Read all member data base */
    function readSchedules ($pathDir) {
        $exclude = '.|..|'.$this->scheduleRoot->configFile;
        $exclude_array = explode('|', $exclude);
        $pathDir = rtrim ($pathDir, '/') . '/';
        if (!is_dir($pathDir))
            return;
        $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 ('#'.$this->scheduleRoot->mbrPrefix.'.*\.xml$#i', $file, $b))
                $this->readSchedule ($pathFile);
        }
        $this->updateSchedulesLists ();
    }

    // ============================================================
    /* Save all member data base marked as modified */
    function writeSchedules () {
        if (!count ($this->membersToSave))
            return;
        foreach (array_keys ($this->membersToSave) as $member)
            if (count ($this->memberSchedules [$member]) <= 0)
                @unlink ($this->dataDir.$this->scheduleRoot->mbrPrefix.$member.'.xml');
            else
                $this->writeSchedule ($member);
        $this->clearCache ();
    }

    // ============================================================
    // YYY
    // ============================================================
    /* get all siblings shared events but the pattern */
    function getOtherSharedEvents ($event) {
        $result = array ();
        if (!$event->from)
            return $result;
        foreach ($this->datedSchedules [$event->from] as $other)
            if ($event->shared == $other->shared &&
                $event->to == $other->to &&
                $event->at == $other->at &&
                $event->title == $other->title &&
                $event->id != $other->id)
                $result [] = $other;
        return $result;
    }

    // ============================================================
    /* XXX */
    function getMembersSharedEvents ($event) {
        if (!$event->shared || !$event->from || !$event->title)
            return $event->requestMembers ? $event->requestMembers : array ($event->member);
        $result = array ();
        foreach ($this->datedSchedules [$event->from] as $other)
            if ($event->shared == $other->shared &&
                $event->to == $other->to &&
                $event->at == $other->at &&
                $event->title == $other->title)
                $result [] = $other->member;
        return $result;
    }

    // ============================================================
    /* Performe action */
    function manageAction ($request) {

        if ($request['action'] == $this->getLang ('show')) {
            $this->isShow = true;
            if (isset ($request['date'])) {
                $this->before = $this->after = $this->date = $this->scheduleRoot->df2ds ($request['date']);
            }
        } elseif ($request['action'] == $this->getLang ('selected')) {
            if (isset ($request['id'])) {
                $id = $request['id'];
                if (isset ($this->allProposals [$id])) {
                    $this->defaultSchedule =  $this->allProposals [$id];
                    $this->defaultSchedule->prop = true;
                } else if (isset ($this->allSchedules [$id])) {
                    $this->defaultSchedule = $this->allSchedules [$id];
                }
            }

        } elseif (in_array ($request['action'],
                            array ($this->getLang ('modifyAll'),
                                   $this->getLang ('modify'),
                                   $this->getLang ('add'),
                                   $this->getLang ('valid'),
                                   $this->getLang ('prop')))) {
            if (isset ($request['id']))
                $id = $request['id'];
            $this->manageAddAction ($request);
            if ($request['action'] == $this->getLang ('valid'))
                $this->removeSchedule ($id);

        } elseif ($request['action'] == $this->getLang ('remove')) {
            if (isset ($request['idl']))
                foreach ($request['idl'] as $id)
                    $this->removeSchedule ($id);
            unset ($_REQUEST['schd']);

        } elseif ($request['action'] == $this->getLang ('created'))
              $this->manageCreateAction ($request);
    }

    // ============================================================
    function manageAddAction ($request) {
        if (isset ($request['id']))
            $id = $request['id'];
        $schedule = new schedule ();
        $others = array ();
        $oldMember = '';
        if (isset ($this->allSchedules [$id]) &&
            ($request['action'] == $this->getLang ('modifyAll') ||
             $request['action'] == $this->getLang ('modify'))) {
            $schedule = &$this->allSchedules [$id];
            $oldMember = $schedule->member;
            if ($request['action'] == $this->getLang ('modifyAll'))
                $others = $this->getOtherSharedEvents ($schedule);
        }
        foreach ($this->scheduleRoot->scheduleRequestAttributsName as $field)
            $schedule->$field = trim (str_replace (array ('"'), '', $request [$field]));
        if ($request ['member']) {
            $schedule->requestMembers = explode (',', trim (str_replace (',,', ',', $request ['member']), ','));
            $schedule->member = trim ($schedule->requestMembers [0]);
        }
        $schedule->title = strtr ($schedule->title, '"', "''");
        $schedule->lead = strtr ($schedule->lead, '"', "''");
        $schedule->remark = strtr ($schedule->remark, '"', "''");

        $schedule->weekDays = scheduleRoot::array2string ($request ['weekDays']);
        $schedule->from = $this->scheduleRoot->df2ds ($request ['from']);
        $schedule->to = $this->scheduleRoot->df2ds ($request ['to']);
        if ('' == $schedule->from) {
            $schedule->from = $schedule->to;
            $schedule->to = '';
        }
        if ($schedule->from == $schedule->to)
            $schedule->to = '';
        if ('' == $schedule->from && '' == $schedule->where && '' == $schedule->title) {
            $this->defaultSchedule = $schedule;
            unset ($_REQUEST['schd']);
            return;
        }
        if (!$this->printProp && $schedule->member && !$this->isMemberOfAll ($schedule->requestMembers)) {
            $this->message ('info', $this->getLang ('notMemberError'));
            $this->defaultSchedule = $schedule;
            unset ($_REQUEST['schd']);
            return;
        }
        if (!$request ['audience'])
            $this->message ('error', $this->getLang ('noAudienceError'));
        if (!$schedule->member)
            $this->message ('error', $this->getLang ('noMemberError'));
        if ('' == $schedule->what)
            $this->message ('error', $this->getLang ('noTypeError'));
        if ('' == $schedule->title)
            $this->message ('error', $this->getLang ('noTitleError'));
        if ('' == $schedule->where)
            $this->message ('error', $this->getLang ('noWhereError'));
        if (!$this->scheduleRoot->checkDS ($schedule->from))
            $this->message ('error', $request ['from'].' : '.$this->getLang ('badDateError'));
        if ($schedule->to && !$this->scheduleRoot->checkDS ($schedule->to))
            $this->message ('error', $request ['to'].' : '.$this->getLang ('badDateError'));
        else if ($this->after && ($schedule->to ? $schedule->to : $schedule->from) < $this->after)
            $this->message ('error', $request ['from'].' : '.$this->getLang ('pastError'));
        else if ($schedule->to != '' && $schedule->from > $schedule->to)
            $this->message ('error', $request ['to'].' : '.$this->getLang ('pastToError'));
        if ($this->isMessage ('error')) {
            $this->startMessage ('error', $this->getLang ('startError'));
            $this->defaultSchedule = $schedule;
            unset ($_REQUEST['schd']);
            return;
        }
        $schedule->repeatFlag = $schedule->to != '' && $schedule->from != $schedule->to;

        // force un nouvel id, member et file
        if (!$schedule->id || in_array ($request['action'],
                                        array ($this->getLang ('add'),
                                               $this->getLang ('valid'),
                                               $this->getLang ('prop'))))
            $schedule->id = date ('Ymdhis.0');

        if ($this->printProp) {
            unset ($_REQUEST['schd']);
            if (!$captcha =& plugin_load ('helper', 'captcha') ||
                !$captcha->check ()) {
                $this->defaultSchedule = $schedule;
                return;
            }
            $schedule->member = implode (',', $schedule->requestMembers);
            $this->allProposals [] = $schedule;
            $this->proposalsToSave = true;
            $this->adminNotification ();
            $this->message ('success', $this->getLang ('propSuccess'));
            return;
        }

        if ($oldMember)
            $this->membersToSave [$oldMember] = true;

        global $INFO;
        $schedule->user = $_SERVER['REMOTE_USER'];
        $this->addSchedule ($schedule);
        unset ($_REQUEST['schd']);

        foreach ($others as $other) {
            if (!$this->isMember ($others->member))
                continue;
            foreach ($this->scheduleRoot->scheduleSaveAttributsName as $field)
                if ($field != 'id')
                    $other->$field = $schedule->$field;
            $this->membersToSave [$other->member] = true;
        }

        $sharedMembers = $this->getMembersSharedEvents ($schedule);
        $i = 1;
        foreach ($schedule->requestMembers as $newMember) {
            $newMember = trim ($newMember);
            if (!$newMember || in_array ($newMember, $sharedMembers))
                continue;
            if (!$this->isMember ($newMember))
                continue;
            $newSchedule = new schedule ();
            foreach ($this->scheduleRoot->scheduleSaveAttributsName as $field)
                $newSchedule->$field = $schedule->$field;
            $newSchedule->id = date ('Ymdhis').$i;
            $i++;
            $newSchedule->member = $newMember;
            $this->addSchedule ($newSchedule);
        }

        $pageId = $this->scheduleRoot->getPageId ($schedule);
        resolve_pageid ($this->nameSpace, $pageId, $exists);
        if (!$exists)
            return;
        $content = io_readFile (wikiFN ($pageId));
        $start = "";
        $option = "";
        $end = $content;
        $place = trim ($this->makeListPlaceForMap ($schedule));
        if (preg_match ("#(?<start>(.|\n)*)<schedule(?<option>[^>]*)>(?<place>([^<]|\n)*)</schedule>(?<end>(.|\n)*)#", $content, $dumy)) {
            $start = $dumy ['start'];
            $option = $dumy ['option'];
            $place2 = trim ($dumy ['place']);
            $end = $dumy ['end'];
            if ($place == $place2)
                return;
        } else if (preg_match ("#(?<start>(.|\n)*=+ Lieu =+\n)(?<place>([^=]|=[^=]|\n)*)(?<end>\n=(.|\n)*)#", $content, $dumy)) {
            $start = $dumy ['start'].NL;
            $end = $dumy ['end'];
        } else {
            $start = '===== Lieu ====='.NL.NL;
            $end = $content;
        }
        $content = $start.'<schedule'.$option.'>'.NL.$place.NL.'</schedule>'.NL.$end;
        saveWikiText ($pageId, $content, 'update place');
    }

    // ============================================================
    function manageCreateAction ($request) {
        $id = $request['id'];
        $pageId = $request ['pageId'];
        resolve_pageid ($this->nameSpace, $pageId, $exists);
        if (!$exists && isset ($this->allSchedules [$id])) {
            $schedule = $this->allSchedules [$id];
            $sharedMembers = $this->getMembersSharedEvents ($schedule);
            // XXX mettre date, lieu, sujet, tarif dans langue
            $content = '';
            foreach ($sharedMembers as $member)
                $content .=
                         '[[:'.$this->scheduleRoot->groupsDir.':'.$member.':|'.
                         '{{ :'.$this->scheduleRoot->groupsDir.':'.$member.':'.$this->scheduleRoot->iconName.'?100}}]]'.NL;
            $content .=
                     '~~NOTOC~~'.NL.
                     '====== '.$schedule->title.' ======'.NL.NL;
            if ($schedule->lead)
                $content .=
                         str_replace ("''", '"', $schedule->lead).NL.NL;
            $content .=
                     '===== Date ====='.NL.NL.
                     ($schedule->repeatFlag ?
                      $this->makeRepeatString ($schedule, true) :
                      ('Le '. $this->scheduleRoot->ds2ln ($schedule->from, true))).
                     $this->makePrettyAt ($schedule->at).NL.NL.
                     '===== Lieu ====='.NL.NL.
                     '<schedule>'.NL.
                     $this->makeListPlaceForMap ($schedule).
                     '</schedule>'.NL.NL.
                     '===== Sujet ====='.NL.NL;
            if (!$schedule->remark)
                $content .=
                         '<wrap round todo>Sujet à préciser</wrap>'.NL.NL;
            if ($schedule->posterURL)
                $content .=
                         '{{ '.$schedule->posterURL.'?direct&200|}}'.NL;
            if ($schedule->paperURL)
                $content .=
                         '[['.$schedule->paperURL.' |]]'.NL;
            if ($schedule->remark)
                $content .=
                         str_replace ("''", '"', $schedule->remark).NL.NL;
            if ($schedule->rate)
                $content .=
                         '===== Tarif ====='.NL.NL.
                         $schedule->rate.NL;

            saveWikiText ($pageId, $content, 'wizard creation');
            $this->clearCache ();
        }
        unset ($_REQUEST['schd']);    
    }

    // ============================================================
    /* Add event */
    function addSchedule (&$schedule) {
        if (isset ($this->allSchedules [$schedule->id]))
            unset ($this->allSchedules [$schedule->id]);
        $this->membersToSave [$schedule->member] = true;
        // élimine les doublons
        if (isset ($this->memberSchedules [$schedule->member]))
            foreach ($this->memberSchedules [$schedule->member] as $event)
                if ($event->from == $schedule->from &&
                    $event->to == $schedule->to &&
                    $event->at == $schedule->at &&
                    $event->shared == $schedule->shared &&
                    $event->title == $schedule->title &&
                    $event->id != $schedule->id) {
                    unset ($this->allSchedules [$event->id]);
                }
        $this->allSchedules [$schedule->id] = $schedule;
        $this->updateSchedulesLists ();
    }

    // ============================================================
    /* Remove event */
    function removeSchedule ($id) {
        if (isset ($this->allProposals [$id])) {
            $this->proposalsToSave = true;
            unset ($this->allProposals [$id]);
            $this->resetAdminNotification ();
        } else if (isset ($this->allSchedules [$id])) {
            $this->membersToSave [$this->allSchedules [$id]->member] = true;
            unset ($this->allSchedules [$id]);
            $this->updateSchedulesLists ();
        }
    }

    // ============================================================
    /* Update data base memory indexes after modification */
    function updateSchedulesLists () {
        $this->memberSchedules = array ();
        $this->repeatedSchedules = array ();
        $this->fixedSchedules = array ();
        $this->datedSchedules = array ();
        foreach ($this->allSchedules as $id => $schedule) {
            $this->memberSchedules [$schedule->member][$id] = $schedule;
            $this->datedSchedules [$schedule->from][] = $schedule;
            if ($schedule->repeatFlag)
                $this->repeatedSchedules [$schedule->from][] = $schedule;
            else
                $this->fixedSchedules [$schedule->from][] = $schedule;
        }
        foreach (array_keys ($this->memberSchedules) as $member)
            uasort ($this->memberSchedules [$member], 'cmpSchedule');
        uasort ($this->allSchedules, 'cmpSchedule');
    }

    // ============================================================
    /* Print schedule as a matrix of days */
    function printScheduleCalendar ($mapId, $dn_showDay) {
        $nbDaysPerWeek = 7;
        $nbWeeks  = 5;
        $nbDays   = $nbDaysPerWeek * $nbWeeks;
        $dn_today = mktime (0, 0, 0, date ('n'), date ('j'), date ('Y'));
        $dn_start = mktime (0, 0, 0, date ('m', $dn_showDay), date ('d', $dn_showDay)-date ('w', $dn_showDay), date ('Y', $dn_showDay));
        $dn_end   = $dn_start + $nbDays * 86400;
        $ds_start = date ('Ymd', $dn_start);
        $ds_end   = date ('Ymd', $dn_end);
        $dn_prev  = mktime (0, 0, 0, date ('m', $dn_showDay)-1, date ('d', $dn_showDay), date ('Y', $dn_showDay));
        $dn_next  = mktime (0, 0, 0, date ('m', $dn_showDay)+1, date ('d', $dn_showDay), date ('Y', $dn_showDay));
        $df_prev  = date ($this->scheduleRoot->currentDateFormat['prettyPrint'], $dn_prev);
        $df_next  = date ($this->scheduleRoot->currentDateFormat['prettyPrint'], $dn_next);
        $dayNames = $this->getLang ('days');

        ptln ('<table class="short" summary="schedule">'.NL.
              ' <caption class="'.($dn_showDay == $dn_today ? 'scheduleToday' : 'scheduleShow').'">'.NL.
              '  <div class="showBubbleOnFocus" style="float:left;">'.NL.
              '   <a onClick="scheduleChangeDate(this,\''.$this->nameSpace.'\',\''.$mapId.'\',\''.$df_prev.'\');">&lt;</a>'.NL.
              '   <div class="bubble '.$this->getConf ('bubblePosition').'Bubble">'.$this->getLang ('tipPrevM').'</div>'.NL.
              '  </div>'.NL.
              '  '.date ($this->scheduleRoot->currentDateFormat['prettyPrint'], $dn_showDay).NL.
              '  <div class="showBubbleOnFocus" style="float:right;">'.NL.
              '   <a onClick="scheduleChangeDate(this,\''.$this->nameSpace.'\',\''.$mapId.'\',\''.$df_next.'\');">&gt;</a>'.
              '   <div class="bubble '.$this->getConf ('bubblePosition').'Bubble">'.$this->getLang ('tipNextM').'</div>'.NL.
              '  </div>'.
              ' </caption>'.NL.
              ' <thead><tr><th></th>');
        if ($dayNames)
            foreach ($dayNames as $dayName => $dayAbrv)
                echo '<th scope="col"><abbr title="'.$dayName.'">'.$dayAbrv.'</abbr></th>';
        ptln (' </tr></thead>'.NL.
              ' <tbody onMouseOut="javascript:scheduleHighlightPOI (null)">');

        $focusSchedules = array ();
        $dn_day = $dn_start;
        // copie des évènements non répété (à part quotidient)
        for ($i = 0; $i < $nbDays; $i++) {
            $ds_index = date ('Ymd', $dn_day);
            if (isset ($this->fixedSchedules [$ds_index]))
                $focusSchedules [$ds_index] = array_values ($this->fixedSchedules [$ds_index]);
            $dn_day = mktime(0, 0, 0, date ('m', $dn_day)  , date ('d', $dn_day)+1, date ('Y', $dn_day));
        }

        // extends repeated events
        foreach ($this->repeatedSchedules as $ds_index => $events) {
            foreach ($events as $event) {
                if ($event->from >= $ds_end)
                    continue;
                if ($event->to < $ds_start)
                    continue;

                preg_match ("#(?<Y>[0-9]{4})(?<m>[0-9]{2})(?<d>[0-9]{2})#", $event->from, $dt_from);
                $dn_from = $this->scheduleRoot->dt2dn ($dt_from);
                if ($event->to > $ds_end)
                    $dn_to = $dn_end;
                else {
                    preg_match ("#(?<Y>[0-9]{4})(?<m>[0-9]{2})(?<d>[0-9]{2})#", $event->to, $dt_to);
                    $dn_to = $this->scheduleRoot->dt2dn ($dt_to);
                }

                switch ($event->repeatType) {
                    //    chercher le premier dans la fenêtre
                    //    tant que dans la fenêtre répété la répétition 
                    //    ajouter l'évènement dans les autres

                case 'day' :
                    // répétition des évènements quotidients
                    $dn_day = $dn_from;
                    $step = 86400 * $event->repeat;
                    if ($dn_from < $dn_start)
                        $dn_day += ceil (($dn_start - $dn_from) / $step) * $step;
                    for ( ; $dn_day <= $dn_to; $dn_day += $step)
                        $focusSchedules [date ('Ymd', $dn_day)][] = $event;
                    break;
                case 'week' :
                    $dn_startWeek = mktime (0, 0, 0, date ('m', $dn_from), date ('d', $dn_from) - date ('w', $dn_from), date ('Y', $dn_from));
                    $step = 86400 * 7 * $event->repeat;
                    if ($dn_startWeek < $dn_start)
                        $dn_startWeek += floor (($dn_start - $dn_startWeek) / $step) * $step;
                    for ( ; $dn_startWeek <= $dn_to; $dn_startWeek += $step)
                        foreach (explode ('|', $event->weekDays) as $idd) {
                            $dn_day = $dn_startWeek + $idd * 86400;
                            if ($dn_day >= $dn_start && $dn_day >= $dn_from &&
                                $dn_day <= $dn_to)
                                $focusSchedules [date ('Ymd', $dn_day)][] = $event;
                        }
                    break;
                case 'dayMonth' :
                case 'dateMonth' :
                    $dn_startMonth = mktime (0, 0, 0, date ('m', $dn_from), 1, date ('Y', $dn_from));
                    if ($dn_startMonth < $dn_start) {
                        $nbMonth = floor ((date ('m', $dn_start) + date ('Y', $dn_start)*12 - (date ('m', $dn_from) + date ('Y', $dn_from)*12))/$event->repeat)*$event->repeat;
                        $dn_startMonth = mktime (0, 0, 0, date ('m', $dn_startMonth) + $nbMonth, 1, date ('Y', $dn_startMonth));
                    }
                    for ( ;
                          $dn_startMonth <= $dn_to;
                          $dn_startMonth = mktime (0, 0, 0, date ('m', $dn_startMonth) + $event->repeat, 1, date ('Y', $dn_startMonth))) {
                        $dn_day = $dn_startMonth;
                        if ($event->repeatType == 'dateMonth')
                            // XXX pb si moins de jour dans le mois
                            $dn_day = mktime (0, 0, 0, date ('m', $dn_startMonth), $event->dayRank, date ('Y', $dn_startMonth));
                        else {
                            $delta = $event->dayInWeek - date ('w', $dn_startMonth);
                            if ($delta < 0)
                                $delta += 7;
                            $delta += 7 * $event->weekRank;
                            // XXX pb si moins de jour dans le mois
                            $dn_day = mktime (0, 0, 0, date ('m', $dn_startMonth), $delta+1, date ('Y', $dn_startMonth));
                        }
                        if ($dn_day >= $dn_start && $dn_day >= $dn_from &&
                            $dn_day <= $dn_to)
                            $focusSchedules [date ('Ymd', $dn_day)][] = $event;
                    }
                    break;
                case 'year' :
                    $dayInmonth = date ('j', $dn_from);
                    $monthInYear = date ('n', $dn_from);
                    $startYear = date ('Y', $dn_start) - (date ('Y', $dn_start) - date ('Y', $dn_from)) % $event->repeat;
                    $endYear = date ('Y', $dn_end);
                    for ( ; $startYear <= $endYear; $startYear += $event->repeat) {
                        $dn_day = mktime (0, 0, 0, $monthInYear, $dayInmonth, $startYear);
                        if ($dn_day >= $dn_start && $dn_day >= $dn_from &&
                            $dn_day <= $dn_to)
                            $focusSchedules [date ('Ymd', $dn_day)][] = $event;
                    }
                    break;
                }
            }
        }

        global $conf;
        $dn_day = $dn_start;
        $allLocations = '';
        $LocationsEvents = array ();
        for ($s = 0; $s < 5; $s++) {
            $line = '';
            for ($d = 0; $d < 7; $d++) {
                $locations = '';
                $df_day = date ($this->scheduleRoot->currentDateFormat['prettyPrint'], $dn_day);
                $url = "javascript:scheduleSelectDate('".$df_day."');";
                $bubble = '';
                if ($d == 1)
                    $w = date ('W', $dn_day);
                $class = 'day showBubbleOnFocus';
                $dayRank = date ('d', $dn_day);

                $eventClass = '';
                if (isset ($focusSchedules [date ('Ymd', $dn_day)])) {
                    $eventClass = array ();
                    $nbEvent = 0;
                    $dayEvents = array ();
                    foreach ($focusSchedules [date ('Ymd', $dn_day)] as $event) {
                        $eventClass [preg_replace ("/\s/", '', htmlspecialchars (strtolower ($this->scheduleRoot->scheduleWhat [$event->what])))] = true;
                        $nbEvent++;
                        list ($LocationsEvents, $dayEvents, $locations)  = $this->addLocationsDayEvent ($LocationsEvents, $dayEvents, $locations, $dn_day, $event);
                    }
                    $bubble = $this->getDayBubble ($dn_day, $dayEvents);
                    ksort ($eventClass);
                    $eventClass = 'cat_'.implode ('', array_keys ($eventClass));
                    $url = '?id='.$this->nameSpace.':'.$conf['start'].'&schd[ns]='.$this->nameSpace.'&schd[action]='.$this->getLang ('show').'&schd[date]='.$df_day;
                    $class .= ' '.$eventClass;
                    $class .= ' op_'.($nbEvent < 2 ? $nbEvent : 3);
                }
                if (!$bubble && $dn_day != $dn_today && $dn_day != $dn_showDay)
                    $class .= ' empty';
                $locations = trim (implode (',', array_unique (explode (',', $locations))), ',');
                $allLocations = trim (implode (',', array_unique (explode (',', $locations.','.$allLocations))), ',');

                $over = '<div class="over mapSelect" style="display:none" day="'.$df_day.'" localtions="'.$locations.'" >'.$dayRank.'</div>';
                if ($dn_day < $dn_today)
                    $over .= '<div class="over past">'.$dayRank.'</div>';
                if ($d == 0 || $d == 6)
                    $class .= ' weekend';
                if ($dn_day == $dn_today)
                    $over .= '<div class="over scheduleToday">'.$dayRank.'</div>';
                elseif ($dn_day == $dn_showDay)
                    $over .= '<div class="over scheduleShow">'.$dayRank.'</div>';

                $js = ' onMouseOver="javascript:scheduleHighlightPOI (\''.$locations.'\')"';
                $line .= '<td class="'.$class.'"'.$js.'>'.$over.$bubble.'<a href="'.$url.'">'.$dayRank.'</a></td>';
                $dn_day = mktime(0, 0, 0, date ('m', $dn_day), date ('d', $dn_day)+1, date ('Y', $dn_day));
            }
            echo '<tr><td class="week">'.$w.'</td>'.$line.'</tr>'.NL;
        }
    
        ptln (' </tbody>'.NL.
              ' <tfoot>'.NL.
              '  <tr><td colspan="8" class="map">'.NL.
              $this->getCityBubble ($LocationsEvents).
              '   <div class="schedulePOI">'.NL.
              '    <div id="'.$mapId.'" class="scheduleMap scheduleMapCalendar"></div>'.NL.
              '    <span style="display:none;" class="poiLocations">'.$allLocations.'</span>'.NL.
              '   </div>'.NL.
              '  </td></tr>'.NL.
              ' </tfoot>'.NL.
              '</table>');
    }

    // ============================================================
    function getLocationsFromEvent ($event) {
        $locations = '';
        if ($event->lat && $event->lon) {
            foreach (array_combine (explode (',', $event->lat), explode (',', $event->lon)) as $lat => $lon) {
                $location = '('.$lat.'|'.$lon.')';
                $locations .= ','.$location;
            }
        } elseif ($event->where) {
            $location = $event->where;
            $locations .= ','.$location;
        }
        return $locations;
    }

    function addLocationsDayEvent ($LocationsEvents, $dayEvents, $locations, $dn_day, $event) {
        $pageId = $this->scheduleRoot->getPageId ($event);
        resolve_pageid ($this->nameSpace, $pageId, $exists);
        $logoId = $this->scheduleRoot->groupsDir.':'.$event->member.':'.$this->scheduleRoot->iconName;
        $pageIdAt = $pageId.'-'.$event->at;
        if ($dayEvents [$pageIdAt])
            $dayEvents [$pageIdAt]['logo'][] = $logoId;
        else
            $dayEvents [$pageIdAt] =
                                   array ('pageId'=> $pageId, 'logo' => array ($logoId),
                                          'what'=> $event->what, 'where'=> $event->where, 'at' =>$event->at, 'title' => $event->title,
                                          'exists' => $exists);
        if ($event->lat && $event->lon) {
            foreach (array_combine (explode (',', $event->lat), explode (',', $event->lon)) as $lat => $lon) {
                $location = '('.$lat.'|'.$lon.')';
                $LocationsEvents [$location][$dn_day][$pageIdAt] = $dayEvents [$pageIdAt];
                $locations .= ','.$location;
            }
        } elseif ($event->where) {
            $location = $event->where;
            $LocationsEvents [$location][$dn_day][$pageIdAt] = $dayEvents [$pageIdAt];
            $locations .= ','.$location;
        }
        // YYY sauf répétition de jours
        return array ($LocationsEvents, $dayEvents, $locations);
    }


    function formatDayEventsBubble ($dn_day, $dayEvents) {
        $content = '';
        $this->scheduleRoot->resetOddEven ();
        foreach ($dayEvents as $pageIdAt => $values) {
            $line = '';
            sort ($values['logo']);
            foreach ($values['logo'] as $logoId)
                $line .= '<img src="'.ml ($logoId, array ('cache'=>$_REQUEST['cache'], 'w'=>20)).'" width="20"/> ';
            if ($values['at'])
                $line .= $values['at']. ' ';
            $line .= $values['what'];
            if ($values['where'])
                $line .= ' - '.$this->makeWhereString ($values['where']);
            $line .= ' : '.$values['title'];
            if ($values['exists'])
                $line = '<a href="?id='.$values['pageId'].'">'.$line.'</a>';
            $content .= '<div class="'.$this->scheduleRoot->getOddEven ().'">'.$line.'</div>'.NL;
            $this->scheduleRoot->switchOddEven ();
        }
        if (!$content)
            return '';
        return '<div class="date">'.date ($this->scheduleRoot->currentDateFormat['prettyPrint'], $dn_day).'</div>'.$content;
    }

    function getDayBubble ($dn_day, $dayEvents) {
        uasort ($dayEvents, 'cmpScheduleInDay');
        $this->scheduleRoot->resetOddEven ();
        $content = $this->formatDayEventsBubble ($dn_day, $dayEvents);
        if (!$content)
            return '';
        return '<div class="bubble '.$this->getConf ('bubblePosition').'Bubble">'.$content.'</div>';
    }

    // ============================================================
    function getCityBubble ($LocationsEvents) {
        $content = '';
        // XXX uasort ($dayEvents, 'cmpScheduleInCities');
        foreach ($LocationsEvents as $location => $datedEvents) {
            $locationContent = '';
            foreach ($datedEvents as $dn_day => $dayEvents)
                $locationContent .= $this->formatDayEventsBubble ($dn_day, $dayEvents);
            if ($locationContent)
                $content .= '<div class="cityBubble" location="'.$location.'"><div class="bubble '.$this->getConf ('bubblePosition').'Bubble">'.$locationContent.'</div></div>';
        }
        return $content;
    }

    // ============================================================
    /* Print schedule as a matrix or list */
    function printScheduleList ($mapId) {
        if ($this->isAdmin ()) {
            echo '<div><form>';
            foreach (array ('clear', 'clearAll') as $action)
                echo '<input value="'.$this->getLang ($action).'" onclick="javascript:scheduleClearCache (\''.$action.'\', \''.$this->nameSpace.'\')" type="button">';
            echo '</form></div>';
        }
        ptln ('<div class="schedule">');
        if ($this->printProp || $this->printForm)
            $this->printScheduleInputForm ($mapId);
        if ($this->printCtrl)
            ptln ('</div>'.NL.
                  '<div class="schedule">'.NL.
                  ' <form method="POST" action="'.$_SERVER['PHP_SELF'].'">'.NL.
                  '  <input type="hidden" name="schd[ns]" value="'.$this->nameSpace.'"/>'.NL.
                  '  <input type="hidden" name="id" value="'.$_REQUEST['id'].'" />');
        ptln ('  <table class="long" onMouseOut="javascript:scheduleHighlightEvent (null, null)">');
        $this->printScheduleListHead ();
        if ($this->isAdmin ())
            $this->printScheduleListContent ($this->allProposals, true);
        $this->printScheduleListContent ($this->allSchedules);
        $this->printScheduleDeleteForm ();
        ptln ('  </table>');
        if ($this->printCtrl)
            ptln (' </form>');
        ptln ('</div>');
    }

    // ============================================================
    /* Print schedule head list */
    function printScheduleListHead () {
        global $conf;

        ptln ('   <thead>'.NL.
              '    <tr>'.NL.
              '     <th class="when" rowspan="2">'.
              ($this->isShow ?
               ('<a href="?id='.$this->nameSpace.':'.$conf ['start'].'" rel="nofollow" class="wikilink1">'.
                $this->getLang ('allDates').'</a>') :
               $this->getLang ('when')).'</th>'.NL.
              '     <th class="what">'.$this->getLang ('what').'</th>'.NL.
              '     <th class="where">'.$this->getLang ('where').'</th>'.NL.
              '     <th class="suject">'.$this->getLang ('title').'</th>'.NL.
              '    </tr>'.NL.
              '    <tr>'.NL.
              '     <th class="audience">'.$this->getLang ('audience').'</th>'.NL.
              '     <th class="by">'.$this->getLang ('proposedBy').'</th>'.NL.
              '     <th class="repeat">');
        if ($this->printCtrl)
            ptln ('<input class="button" type="button" onclick="scheduleSwitchSelection(this.form)" value="'.$this->getLang ('inverted').'"/>');
        ptln ('</th>'.NL.
              '    </tr>'.NL.
              '   </thead>');
    }

    // ============================================================
    /* Print schedule line list */
    function printScheduleLineEvent ($pageId, $lineVals, $even, $prop = false) {
        $ns = $this->nameSpace;

        $event = $lineVals['event'];
        $propClass = $prop ? ' proposal' : '';
    
        $day = $this->scheduleRoot->ds2df ($event->syncStart ? $event->syncStart : $event->from);
        $overEvent=' onMouseOver="javascript:scheduleHighlightEvent (\''.$day.'\', \''.$this->getLocationsFromEvent ($event).'\')"';

        ksort ($lineVals['member']);
        ptln ('    <tr class="'.$this->scheduleRoot->getOddEven ().' '.$lineVals ['class'].$propClass.'" '.$overEvent.'>'.NL.
              '     <td class="when" rowspan="2">');
        if ($prop)
            ptln ('<i>proposition</i><br/>');
        ptln ($day.(($event->syncStart || ('' == $event->to)) ? '' : '<br/>'.NL.$this->scheduleRoot->ds2df ($event->to)).('' == $event->at ? '' : '<br/>'.NL.$event->at).'</td>'.NL.
              '     <td class="what">'.$event->what.'</td>'.NL.
              '     <td class="where">'.$this->makeWhereString ($event->where).'</td>'.NL.
              '     <td class="title"><a class="'.$lineVals['css'].'" href="?id='.$pageId.'">'.$event->title.'</a></td>'.NL.
              '    </tr>'.NL.
              '    <tr class="'.$this->scheduleRoot->getOddEven ().' '.$lineVals ['class'].$propClass.'" '.$overEvent.'>'.NL.
              '     <td class="audience">'.$event->audience.'</td>'.NL.
              '     <td class="proposedBy">');
        if ($this->printCtrl) {
            ptln ($this->scheduleRoot->scheduleShared[$event->shared]);
            if (!$prop && !$lineVals['exists'] && $this->isAMemberOf (array_keys ($lineVals['member']))) {
                // petite baguette magique de création
                $id = reset (array_values($lineVals['member']));
                ptln ('<a href="?id='.$_REQUEST['id'].'&schd[id]='.$id.'&schd[ns]='.$ns.'&schd[pageId]='.$pageId.'&schd[action]='.$this->getLang ('created').'">'.
                      ' <img src="'.$this->scheduleRoot->wizardIconUrl.'"/></a>');
            }
        }
        foreach ($lineVals['member'] as $member => $id) {
            echo '<span style="white-space: nowrap;">';
            if ($this->printCtrl && $this->isMember ($member)) {
                // suppression ou édition pour un membre
                ptln ('<input type="checkbox" name="schd[idl][]" value="'.$id.'"/> '.
                      '<a href="?id='.$_REQUEST['id'].'&schd[id]='.$id.'&schd[ns]='.$ns.'&schd[action]='.$this->getLang ('selected').'">'.
                      '<img src="'.$this->scheduleRoot->editIconUrl.'"/></a>');
            }
            $logoId = $this->scheduleRoot->groupsDir.':'.$member.':'.$this->scheduleRoot->iconName;
            // YYY
            ptln ('
	  <a href="/'.$this->scheduleRoot->groupsDir.':'.$member.'/"><img src="'.ml ($logoId, array ('cache'=>$_REQUEST['cache'], 'w'=>$this->scheduleRoot->iconWidth)).'" width="'.$this->scheduleRoot->iconWidth.'"/></a></span> ');
        }
        ptln ('</td>'.NL.
              '     <td class="repeat">');
        if ($event->repeatFlag)
            ptln ('<img src="'.$this->scheduleRoot->repeatIconUrl.'" /> '.$this->makeRepeatString ($event, false));
        ptln ('</td>'.NL.
              '    </tr>');
    }

    // ============================================================
    function makePrettyAt ($at) {
        if (!$at)
            return '';
        if (preg_match ("#(?<from>[^-]*)-(?<to>[^-]*)#", $at, $dumy))
            return ' '.$this->getLang ('fromHour').' **'.$dumy ['from'].'** '.$this->getLang ('toHour').'  **'.$dumy ['to'].'**';
        return ' '.$this->getLang ('at').' **'.$at.'**';
    }

    function makeListPlaceForMap ($schedule) {
        global $scheduleInseeCities;
        $result= '';
        $cities = explode (',', $schedule->where);
        $lats = explode (',', $schedule->lat);
        $lons = explode (',', $schedule->lon);
        $addrs = explode ('|', $schedule->addr);
        $unknownAddr = false;
        for ($i = 0; $i < count ($cities); $i++) {
            if (!isset ($addrs [$i]))
                $unknownAddr = true;
            $result .=
                    $cities[$i].' | '.
                    (isset ($lats [$i]) ? $lats [$i] : '').' | '.
                    (isset ($lons [$i]) ? $lons [$i] : '').' | '.
                    (isset ($addrs [$i]) ? $addrs [$i] : '').
                    NL;
        }
        if ($unknownAddr)
            $result .= '<wrap todo>Les coordonnées par défaut correspondent aux centres-villes !</wrap>'.NL;
        return $result;
    }

    // ============================================================
    /* Create a natural langage string for cities zipcode */
    function makeWhereString ($where) {
        global $scheduleInseeCities;
        $result = array ();
        foreach (explode (',', $where) as $zip)
            $result[] = (isset ($scheduleInseeCities[$zip]) ?
                         $scheduleInseeCities[$zip][0] :
                         ($this->showOldValues ? '<b>'.$zip.'</b>' : $zip));
        return implode (',', array_unique ($result));
    }

    // ============================================================
    /* Create a natural langage string to formulate repeat event */
    function makeRepeatString ($event, $bold = false) {
        $orderedFormat = $this->getLang ('orderedFormat');
        $rt = $this->getLang ('repeatType');
        $result = '';
        switch ($event->repeatType) {
        case 'day' :
            return $this->getLang ('from').' '.$this->scheduleRoot->ds2ln ($event->from, $bold).' '.$this->getLang ('to').' '.$this->scheduleRoot->ds2ln ($event->to, $bold);
            break;
            // pour gagner du temps
        case 'week' :
            foreach (array_keys ($this->getLang ('days')) as $idd => $dayName)
                $result .= in_array ($idd, explode ('|', $event->weekDays)) ? $dayName.' ' : '';
                        break;
        case 'dayMonth' :
            $dayNames = array_keys ($this->getLang ('days'));
            // XXX pb OVH => utilisation de tableau au lieu de fonction
            // $result .= $f ($event->weekRank+1).' '.$d [$event->dayInWeek].' ';
            $result .= $orderedFormat [$event->weekRank+1].' '.$dayNames [$event->dayInWeek].' ';
            break;
        case 'dateMonth' :
            // XXX pb OVH => utilisation de tableau au lieu de fonction
            // $result .= $f ($event->dayRank).' ';
            $result .= $orderedFormat [$event->dayRank].' ';
            break;
        case 'year' :
            // XXX le Ie jour du Je mois
            preg_match ("#(?<Y>[0-9]{4})(?<m>[0-9]{2})(?<d>[0-9]{2})#", $event->from, $dt_from);
            $dn_from = $this->scheduleRoot->dt2dn ($dt_from);
            $result .= date ($this->scheduleRoot->currentDateFormat ['dayMonth'], $dn_from).' ';
        }
        $result .= $this->getLang ('all').' ';
        if ($event->repeat > 1)
            $result .= $event->repeat.' ';
        $result .= $rt[$event->repeatType];

        return $result;
    }

    // ============================================================
    /* Add a schedule line list in array for futur print */
    function addEventTab (&$tab, $event, $when, $pageId, $eventClass, $exists, $cssLink) {
        if (!$tab [$when])
            $tab [$when] = array ();
        if ($tab [$when][$pageId])
            $tab [$when][$pageId]['member'][$event->member] = $event->id;
        else
            $tab [$when][$pageId] =
                                  array ('member' => array ($event->member => $event->id), 'event' => $event, 'class' => $eventClass, 'exists' => $exists, 'css' => $cssLink);
    }

    // ============================================================
    /* Print all schedule line list */
    function printScheduleListContent ($allSchedules, $prop = false) {
        $linesTab = array ();
        $repeatTab = array ();
        if ($this->after) {
            preg_match ("#(?<Y>[0-9]{4})(?<m>[0-9]{2})(?<d>[0-9]{2})#", $this->after, $dt_start);
            $dn_start = $this->scheduleRoot->dt2dn ($dt_start);
        }
        foreach ($allSchedules as $event) {
            // XXX même filtre pour printScheduleCalendar ?
            if ($this->before && strcmp ($event->from, $this->before) > 0)
                continue;
            // XXX repeat le même jour => calcul
            if ($this->date && (strcmp ($event->from, $this->date) > 0 ||
                                strcmp (('' != $event->to ? $event->to : $event->from), $this->date) < 0))
                continue;
            if ($this->after &&
                strcmp (('' != $event->to ? $event->to : $event->from), $this->after) < 0)
                continue;
            if ($this->max && $line > $this->max)
                break;
            $continue = false;
            if ($this->filters)
                foreach ($this->filters as $name => $values) {
                    if (in_array ($name, $this->scheduleRoot->filterNames) && !in_array ($event->$name, $values)) {
                        $continue = true;
                        break;
                    } elseif (in_array ($name,  array_keys ($this->scheduleRoot->filterNames))) {
                        $trueName = $this->scheduleRoot->filterNames[$name];
                        if (in_array ($event->$trueName, $values)) {
                            $continue = true;
                            break;
                        }
                    }
                }
            if ($continue)
                continue;
            $pageId = $this->scheduleRoot->getPageId ($event);
            resolve_pageid ($this->nameSpace, $pageId, $exists);
            $cssLink = $exists ? 'wikilink1' : 'wikilink2';
            $eventClass = 'cat_'.preg_replace ("/\s/", '', htmlspecialchars (strtolower ($this->scheduleRoot->scheduleWhat [$event->what])));
            $when = $event->from.'-'.$event->at.'-'.$event->to;

            if ($event->repeatFlag) {
                if (!$this->after || strcmp ($event->from, $this->after) > 0)
                    $event->syncStart = $event->from;
                else {
                    // cas ou il faut déplacer le début de la répétition (on ne déplace si la 1re occurance est après le début de la fenêtre)
                    preg_match ("#(?<Y>[0-9]{4})(?<m>[0-9]{2})(?<d>[0-9]{2})#", $event->from, $dt_from);
                    $dn_day = $dn_from = $this->scheduleRoot->dt2dn ($dt_from);
                    // on déplace sur la 1re occurance
                    switch ($event->repeatType) {
                        // chercher le 1er évènement après 'after'
                    case 'day' :
                        $step = 86400 * $event->repeat;
                        $dn_day += ceil (($dn_start - $dn_from) / $step) * $step;
                        break;
                    case 'week' :
                        $dn_startWeek = mktime (0, 0, 0, date ('m', $dn_from), date ('d', $dn_from) - date ('w', $dn_from), date ('Y', $dn_from));
                        $step = 86400 * 7 * $event->repeat;
                        $dn_startWeek += floor (($dn_start - $dn_startWeek) / $step) * $step;
                        // au pire start commence après les jours répétés dans la semaine
                        for ($i = 0; $i < 2; $i++, $dn_startWeek += $step) {
                            foreach (explode ('|', $event->weekDays) as $idd) {
                                $dn_day = $dn_startWeek + $idd * 86400;
                                if ($dn_day >= $dn_start && $dn_day >= $dn_from) {
                                    break 2;
                                }
                            }
                        }
                        break;
                    case 'dayMonth' :
                    case 'dateMonth' : {
                        $dn_startMonth = mktime (0, 0, 0, date ('m', $dn_from), 1, date ('Y', $dn_from));
                        $nbMonth = floor ((date ('m', $dn_start) + date ('Y', $dn_start)*12 - (date ('m', $dn_from) + date ('Y', $dn_from)*12))/$event->repeat)*$event->repeat;
                        $dn_startMonth = mktime (0, 0, 0, date ('m', $dn_startMonth) + $nbMonth, 1, date ('Y', $dn_startMonth));
                        for ($i = 0; $i < 2; $i++, $dn_startMonth = mktime (0, 0, 0, date ('m', $dn_startMonth) + $event->repeat, 1, date ('Y', $dn_startMonth))) {
                            $dn_day = $dn_startMonth;
                            if ($event->repeatType == 'dateMonth')
                                // XXX pb si moins de jour dans le mois
                                $dn_day = mktime (0, 0, 0, date ('m', $dn_startMonth), $event->dayRank, date ('Y', $dn_startMonth));
                            else {
                                $delta = $event->dayInWeek - date ('w', $dn_startMonth);
                                if ($delta < 0)
                                    $delta += 7;
                                $delta += 7 * $event->weekRank;
                                // XXX pb si moins de jour dans le mois
                                $dn_day = mktime (0, 0, 0, date ('m', $dn_startMonth), $delta+1, date ('Y', $dn_startMonth));
                            }
                            if ($dn_day >= $dn_start && $dn_day >= $dn_from)
                                break;
                        }
                        break;
                    }
                    case 'year' :
                        $dayInmonth = date ('j', $dn_from);
                        $monthInYear = date ('n', $dn_from);
                        $startYear = date ('Y', $dn_start) - (date ('Y', $dn_start) - date ('Y', $dn_from)) % $event->repeat;
                        for ($i = 0; $i < 2; $i++, $startYear += $event->repeat) {
                            $dn_day = mktime (0, 0, 0, $monthInYear, $dayInmonth, $startYear);
                            if ($dn_day >= $dn_start && $dn_day >= $dn_from)
                                break;
                        }
                        break;
                    }
                    $ds_day = date ('Ymd', $dn_day);
                    if ($this->before && strcmp ($ds_day, $this->before) > 0) 
                        continue;
                    if ($this->date && strcmp ($ds_day, $this->date) != 0)
                        continue;
                    $event->syncStart = $ds_day;
                    $when = $ds_day.'-'.$event->at.'-'.$event->to;	
                }
            }
            if ($this->repeatGrouped && $event->repeatFlag)
                schedules::addEventTab ($repeatTab, $event, $when, $pageId, $eventClass, $exists, $cssLink);
            else
                schedules::addEventTab ($linesTab, $event, $when, $pageId, $eventClass, $exists, $cssLink);
        }
        ksort ($repeatTab);
        ksort ($linesTab);

        $line = 1;
        foreach ($repeatTab as $when) {
            foreach ($when as $pageId => $lineVals) {
                schedules::printScheduleLineEvent ($pageId, $lineVals, $even, $prop);
                $this->scheduleRoot->switchOddEven ();
                $line++;
            }
        }
        foreach ($linesTab as $when) {
            foreach ($when as $pageId => $lineVals) {
                schedules::printScheduleLineEvent ($pageId, $lineVals, $even, $prop);
                $this->scheduleRoot->switchOddEven ();
                $line++;
            }
        }
        if ($line < 1)
            echo '
            <tr class="'.$this->scheduleRoot->getOddEven ().'"><td colspan="6">'.$this->getLang ('noEvent').'</td></tr>';
    }

    // ============================================================
    /* Print delete button in form */
    function printScheduleDeleteForm () {
        if (!$this->printCtrl)
            return;
        echo '
      <tr>
	<th colspan="2"></th>
	<th><input type="submit" name="schd[action]" value="'.$this->getLang ('remove').'"/></th>
        <th></th>
      </tr>';
    }

    // ============================================================
    /* Print add event form */
    function printRepeatForm () {
        $repeat = ($this->defaultSchedule->repeat > 1) ? $this->defaultSchedule->repeat : 1;
        ptln('
<table>
 <tr>
  <td colspan="8">'.$this->getLang ('all').' <input class="repeat" name="schd[repeat]" type="text" value="'.$repeat.'"/>
   <select name="schd[repeatType]" onChange="scheduleUpdateRepeatType (this)">
');
        foreach ($this->getLang ('repeatType') as $idrt => $repeatType) {
            $selected = ($this->defaultSchedule->repeatType == $idrt) ? ' selected="true"' : '';
            ptln ('    <option value="'.$idrt.'"'.$selected.'>'.$repeatType.'</option>'.NL);
        }
        ptln ('
   </select>
  </td>
 </tr><tr>
  <td rowspan="2">'.$this->getLang ('each').'</td>
');
        $disabled = ($this->defaultSchedule->repeatType !== "week") ? ' disabled="true"' : '';
        $eventWeekDays = explode ("|", $this->defaultSchedule->weekDays);
        $checkbox = "";
        $dayNames = $this->getLang ('days');
        foreach (array_values ($dayNames) as $idd => $dayAbrv) {
            ptln ('
  <td>'.$dayAbrv.'</td>
');
            $selected = in_array ($idd, $eventWeekDays) ? ' checked="true"' : '';
            $checkbox .= '  <td><input type="checkbox" name="schd[weekDays][]" onclick="scheduleUpdateWeekDays (this)" value="'.$idd.'"'.$selected.$disabled.'/></td>'.NL;
        }
        $disabled = ($this->defaultSchedule->repeatType !== "dayMonth") ? ' disabled="true"' : '';
        ptln ('
 </tr><tr>
'.$checkbox.'
 </tr><tr>
  <td colspan="8">
   <select name="schd[weekRank]"'.$disabled.'>'
        );
        $orderedFormat = $this->getLang ('orderedFormat');
        for ($week = 0; $week < 5; $week++) {
            $selected = ($week == $this->defaultSchedule->weekRank) ? ' selected="true"' : '';
            // XXX pb OVH => utilisation de tableau au lieu de fonction
            // echo '<option value="'.$week.'"'.$selected.'>'.$f ($week+1).'</option>'.NL;
            ptln ('          <option value="'.$week.'"'.$selected.'>'.$orderedFormat [$week+1].'</option>'.NL);
        }
        ptln ('
   </select>
   <select name="schd[dayInWeek]"'.$disabled.'>
');
        foreach (array_keys ($dayNames) as $idw => $week) {
            $selected = ($idw == $this->defaultSchedule->dayInWeek) ? ' selected="true"' : '';
            ptln ('    <option value="'.$idw.'"'.$selected.'>'.$week.'</option>');
        }
        $disabled = ($this->defaultSchedule->repeatType !== "dateMonth") ? ' disabled="true"' : '';
        ptln ('
   </select>
   <select name="schd[dayRank]"'.$disabled.'>
');
        for ($dayRank = 1; $dayRank < 32; $dayRank++) {
            $selected = ($dayRank == $this->defaultSchedule->dayRank) ? ' selected="true"' : '';
            ptln ('    <option value="'.$dayRank.'"'.$selected.'>'.$orderedFormat [$dayRank].'</option>'.NL);
        }
        ptln ('
   </select>
  </td>
 </tr>
</table>');
    }

    // ============================================================
    function printScheduleInputForm ($mapId) {
        if (!($this->printForm || $this->printProp))
            return;

        $from = (!$this->defaultSchedule->from && isset ($_REQUEST["schd"]["date"])) ?
              $_REQUEST["schd"]["date"] :
              $this->scheduleRoot->ds2df ($this->defaultSchedule->from);

        ptln ('  <div class="scheduleTabForm">'.NL.
              '   <p>'.$this->getLang ($this->printProp ? 'proposedEvent' : 'addEvent').'</p>'.NL. // XXX getLang
              '   <ul>'.NL.
              '    <li><a class="warningPlace" href="#scheduleTabForm-0">1) '.$this->getLang ('who').'</a></li>'.NL.
              '    <li><a class="warningPlace" href="#scheduleTabForm-1">2) '.$this->getLang ('what').'</a></li>'.NL.
              '    <li><a class="warningPlace" href="#scheduleTabForm-2">3) '.$this->getLang ('where').'</a></li>'.NL.
              '    <li><a class="warningPlace" href="#scheduleTabForm-3">4) '.$this->getLang ('when').'</a></li>'.NL.
              '    <li><a class="warningPlace" href="#scheduleTabForm-4">5) '.$this->getLang ('validation').'</a></li>'.NL.
              '   </ul>'.NL.

              '   <form class="scheduleMiscForm">'.NL.
              '   <div id="scheduleTabForm-0">'.NL.
              '    <div>'.NL.
              '     <div class="warningPlace"></div><select name="schd[audience]" onChange="scheduleCheckInputs ()">'.NL);
        foreach ($this->scheduleRoot->scheduleAudience as $audience => $label) {
            $selected = ($audience == $this->defaultSchedule->audience) ? ' selected="true" ' : '';
            ptln ('      <option value="'.$audience.'"'.$selected.'>'.$label.'</option>'.NL);
        }
        ptln ('     </select>'.NL.
              '     <select name="schd[shared]" onChange="scheduleSharedEvent (this.form)">'.NL);
        foreach (array (false, true) as $sharedVal) {
            $selected = ($sharedVal == $this->defaultSchedule->shared) ? ' selected="true" ' : '';
            ptln ('       <option value="'.$sharedVal.'"'.$selected.'>'.$this->scheduleRoot->scheduleShared[$sharedVal].'</option>'.NL);
        }
        ptln ('     </select>'.NL.
              '     <div class="warningPlace"></div><select class="members" name="schd[member]"'.($this->defaultSchedule->shared ? ' multiple="true"' : "").' size="'.($this->defaultSchedule->shared == 1 ? 9 : 1).'" onChange="scheduleCheckInputs ()">');
        $availableGroup = ($this->isAdmin () || $this->printProp)? $this->allGroups : $this->userGroups;
        $sharedMembers = $this->defaultSchedule->prop ?
                       explode (",", $this->defaultSchedule->member) :
                       $this->getMembersSharedEvents ($this->defaultSchedule);
        ptln ('      <option value="">'.$this->getLang ('memberChoice').'</option>'.NL);
        foreach ($availableGroup as $member) {
            $selected = in_array ($member, $sharedMembers) ? ' selected="true" ' : '';
            ptln ('      <option value="'.$member.'"'.$selected.'>'.$member.'</option>'.NL);
        }
        ptln ('     </select>'.NL.
              '    </div>'.NL.
              '   </div>'.NL.

              '   <div id="scheduleTabForm-1">'.NL.
              '     <div class="warningPlace"></div><select name="schd[what]" onChange="scheduleCheckInputs ()" >'.NL.
              '      <option value="">'.$this->getLang ('eventTypeChoice').'</option>'.NL);
        foreach (array_keys ($this->scheduleRoot->scheduleWhat) as $what) {
            $selected = ($what == $this->defaultSchedule->what) ? ' selected="'.$what.' '.$this->defaultSchedule->what.'" ' : '';
            ptln ('      <option value="'.$what.'"'.$selected.'>'.$what.'</option>'.NL);
        }
        ptln ('     </select>'.NL.
              '     <input class="title" name="schd[title]" type="text" value="'.$this->defaultSchedule->title.'" placeholder="'.$this->getLang ('titlePH').'" onChange="scheduleCheckInputs ()"/>'.NL.
              '    <p>'.NL.
              '     '.$this->getLang ('lead').':<br/>'.NL.
              '     <textarea class="paper" name="schd[lead]" placeholder="'.$this->getLang ('leadPH').'">'.$this->defaultSchedule->lead.'</textarea>'.NL.
              '    </p><p>'.NL.
              '     '.$this->getLang ('posterURL').':<br/>'.NL.
              '     <input class="poster" name="schd[posterURL]" type="text" value="'.$this->defaultSchedule->posterURL.'" placeholder="'.$this->getLang ('posterPH').'"/>'.NL.
              '    </p><p>'.NL.
              '     '.$this->getLang ('paperURL').':<br/>'.NL.
              '     <input class="paper" name="schd[paperURL]" type="text" value="'.$this->defaultSchedule->paperURL.'" placeholder="'.$this->getLang ('paperPH').'"/>'.NL.
              '    </p><p>'.NL.
              '     '.$this->getLang ('remark').':<br/>'.NL.
              '     <textarea class="paper" name="schd[remark]" placeholder="'.$this->getLang ('remarkPH').'">'.$this->defaultSchedule->remark.'</textarea>'.NL.
              '    </p><p>'.NL.
              '     '.$this->getLang ('rate').':<br/>'.NL.
              '     <input class="rate" name="schd[rate]" type="text" value="'.$this->defaultSchedule->rate.'" placeholder="'.$this->getLang ('ratePH').'"/>'.NL.
              '    </p>'.NL.
              '   </div>'.NL.
              '   </form>'.NL.

              '   <div id="scheduleTabForm-2">'.NL.
              '   <form class="scheduleCitiesForm insee">'.NL.
              '    <div>'.NL.
              '     <div id="'.$mapId.'" class="scheduleMap scheduleMapForm"></div>'.NL.
              '     <div class="ui-widget">'.NL.
              '      <div><ul class="cities warningPlace">'.NL);

        if ($this->defaultSchedule->where) {
            global $scheduleInseeCities;
            $cities = explode (',', $this->defaultSchedule->where);
            $lats = explode (',', $this->defaultSchedule->lat);
            $lons = explode (',', $this->defaultSchedule->lon);
            $addrs = explode ('|', $this->defaultSchedule->addr);
            for ($i = 0; $i < count ($cities); $i++) {
                $insee = $cities[$i];
                $cityName =
                          isset ($scheduleInseeCities [$insee]) ?
                          $scheduleInseeCities [$insee][0] :
                          $insee;
                ptln ('        <li onclick="scheduleSelectCity (this)" insee="'.$insee.'" lat="'.
                      (isset ($lats [$i]) ? $lats [$i] : "").'" lon="'.
                      (isset ($lons [$i]) ? $lons [$i] : "").'"><img class="checked" src="'.
                      $this->scheduleRoot->emptyIconUrl.'" onclick="scheduleRemoveCity (this)"/>'.
                      '<span class="addresse">'.(isset ($addrs [$i]) ? str_replace ('\\\\', '<br/>', str_replace ('~br~', '<br/>', $addrs [$i])) : "").'</span> '.
                      '<span class="city">'.$cityName.'</span></li>');
            }
        }
        ptln ('      </ul></div>'.NL.
              '      <label>'.$this->getLang ('city').' :<br/></label><input name="city" type="text" placeholder="'.$this->getLang ('cityPH').'"/>'.NL.
              '      <span class="wrap_tip ">'.$this->getLang ('enterTip').'</span>'.NL.
              '      <label>'.$this->getLang ('addresse').' :<br/></label><input name="addr" type="text" placeholder="'.$this->getLang ('addrPH').'"/>'.NL.
              '      <span class="wrap_tip ">'.$this->getLang ('enterTip').'</span>'.NL.
              '     </div>'.NL.
              '    </div>'.NL.
              '   </form>'.NL);
        if ($this->isAdmin ())
            ptln ('    <div>'.NL.
                  '<input type="button" value="'.$this->getLang ('add').'" onClick="javascript:scheduleAddInsee ()" />'.NL.
                  '<input type="button" value="'.$this->getLang ('remove').'" onClick="javascript:scheduleRemoveInsee ()" />'.NL.
                  '<input type="button" value="'.$this->getLang ('test').'" onClick="javascript:scheduleTestInsee ()" />'.NL.
                  '</div>'.NL);
        ptln ('   </div>'.NL.

              '   <form class="scheduleFinalForm" method="POST" action="'.$_SERVER["PHP_SELF"].'">'.NL.
              '    <div id="scheduleTabForm-3">'.NL.
              '     <p>'.NL.
              $this->getLang ('from').' <input class="date" id="scheduleFrom" name="schd[from]" type="text" value="'.$from.'" placeholder="'.$this->getLang ('datePH').'" onChange="scheduleCheckInputs ()" /> '.
              $this->getLang ('to').' <input class="date" name="schd[to]" type="text" value="'.$this->scheduleRoot->ds2df ($this->defaultSchedule->to).'" placeholder="'.$this->getLang ('datePH').'"/></p><p>'.
              $this->getLang ('at').' <input class="hour" name="schd[at]" type="text" value="'.$this->defaultSchedule->at.'" placeholder="'.$this->getLang ('hourPH').'"/></p>');
        $this->printRepeatForm ();
        ptln ('    </div>'.NL.

              '    <div id="scheduleTabForm-4">'.NL.
              '     <p>'.NL.

              '      <input type="hidden" name="schd[ns]" value="'.$this->nameSpace.'"/>'.NL.
              '      <input type="hidden" name="id" value="'.$_REQUEST["id"].'" />'.NL);

        if ($this->printForm) {
            if ($this->defaultSchedule->id) {
                ptln ('<input type="hidden" name="schd[id]" value="'.$this->defaultSchedule->id.'"/>');
                if (!$this->defaultSchedule->prop) {
                    if (count ($this->getMembersSharedEvents ($this->defaultSchedule)) > 1)
                        ptln ('<input type="submit" name="schd[action]" value="'.$this->getLang ('modifyAll').'"/>');
                    ptln ('<input type="submit" name="schd[action]" value="'.$this->getLang ('modify').'"/> ');
                }
            }
            ptln ('<input type="submit" name="schd[action]" value="'.$this->getLang ($this->defaultSchedule->prop ? 'valid' : 'add').'"/>');
        }
        if ($this->printProp) {
            ptln ('<input type="submit" name="schd[action]" value="'.$this->getLang ('prop').'"/> ');
            if ($captcha =& plugin_load ('helper', 'captcha'))
                ptln ($captcha->getHTML ());
        }
        ptln ('     </p>'.NL.
              '    </div>'.NL.
              '   </form>'.NL.
              '   <div id="scheduleHelp">'.NL.
              '    <a href="#">Aide</a>'.NL.
              '    <div>'.NL.
              '     <div class="vshare__center">'.NL.
              '      <!--[if !IE]> --><object type="application/x-shockwave-flash" data="http://www.dailymotion.com/swf/x10xcw7_" height="350" width="623"><!-- <![endif]-->'.NL.
              '      <!--[if IE]><object width="623" height="350" classid="clsid:D27CDB6E-AE6D-11cf-96B8-444553540000"> <param name="movie" value="http://www.dailymotion.com/swf/x10xcw7_" /><!--><!-- -->'.NL.
              '       <param name="FlashVars" value="">'.NL.
              '       </object>'.NL.
              '      <!-- <![endif]-->'.NL.
              '     </div>'.NL.
              '    </div>'.NL.
              '   </div>'.NL.
              '  </div>'.NL);
    }

    // ============================================================
    function resetAdminNotification () {
        $this->lastNotificationReset = date ('YmdHis');
        $this->scheduleRoot->writeConfig ($this);
    }

    function adminNotification () {
        if ($this->lastNotification > $this->lastNotificationReset)
            return;

        global $auth;
        $users = $auth->retrieveUsers ();
        foreach ($users as $user => $userinfo) {
            $mailSubject = $this->getLang ('notifySubject');
            $mailContent = $this->getLang ('notifyContent');
            if (in_array (trim ($this->getConf ('adminGroup')), $userinfo ['grps'])) {
                mail_send ($userinfo['mail'], $mailSubject, $mailContent);
            }
        }

        $this->lastNotification = date ('YmdHis');
        $this->scheduleRoot->writeConfig ($this);
    }

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