' . SUPPLIER_REGEX . ')\/?(?' . EVENT_REGEX . ')?\/?$/'); define('ACTION_REGEX', '/^[a-z]{1,16}$/i'); $baseUrl = trim((isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] === 'on' ? "https" : "http") . "://" . $_SERVER['HTTP_HOST'] . $_SERVER['REQUEST_URI'], '/'); if (($pos = strpos($baseUrl, '?')) !== false) $baseUrl = substr($baseUrl, 0, $pos); $requestUrl = trim(array_key_exists('QUERY_STRING', $_SERVER) ? str_replace($_SERVER['QUERY_STRING'], '', $baseUrl) : $baseUrl, '?'); if (preg_match(REQUEST_REGEX, $requestUrl, $match)) { $requestSupplier = array_key_exists('supplier', $match) ? $match['supplier'] : null; $requestEvent = array_key_exists('event', $match) ? $match['event'] : null; if (!is_null($requestEvent)) $requestUrl = rtrim(str_replace($requestEvent, '', $requestUrl), '/'); if (!is_null($requestSupplier)) $requestUrl = rtrim(str_replace($requestSupplier, '', $requestUrl), '/'); } else { $requestSupplier = null; $requestEvent = null; } function ago($value) { $now = new \DateTimeImmutable('now 00:00:00'); $value = (clone $value)->setTime(0, 0, 0); $diff = $now->diff($value, false); if (abs($diff->y) > 0) $output = sprintf('%d an%s', $diff->y, $diff->y > 1 ? 's' : ''); elseif (abs($diff->m) > 0) $output = sprintf('%d mois', $diff->m); elseif (abs($diff->d) > 1) $output = sprintf('%d jours', $diff->d); if (isset($output)) $output = sprintf('%s %s', ($diff->invert === 1 ? 'il y a' : 'dans'), $output); elseif (abs($diff->d) > 0) $output = $diff->invert ? 'hier' : 'demain'; else $output = 'aujourd\'hui'; return $output; } function generatePassword($length = 20) { $chars = array_merge( range('A', 'Z'), range('a', 'z'), range('0', '9'), [ '!', '?', '~', '@', '#', '$', '%', '*', ';', ':', '-', '+', '=', ',', '.', '_' ] ); while ($length-- > 0) $value .= $chars[mt_rand(0, count($chars) - 1)]; return $value; } function generateUrl($supplier = null, $event = null) { global $requestUrl; if (is_null($supplier)) return $requestUrl; if (is_null($event)) return sprintf('%s/%s', $requestUrl, $supplier); return sprintf('%s/%s/%s', $requestUrl, $supplier, $event); } function findNext($start, $frequency, $excludes = [], $vsNow = true, $maxIterations = 1000, $direction = +1) { $now = new \DateTime('now'); $current = clone $start; $frequency = \DateInterval::createFromDateString($frequency); do { if ($direction === abs($direction)) { if (!$vsNow and ($maxIterations-- > 0)) { $current->add($frequency); } else { while ( ($current->getTimestamp() < $now->getTimestamp()) and ($maxIterations-- > 0) ) $current->add($frequency); } } else { if (!$vsNow and ($maxIterations-- > 0)) { $current->sub($frequency); } else { while ( ($current->getTimestamp() > $now->getTimestamp()) and ($maxIterations-- > 0) ) $current->sub($frequency); } } $nextEvent = $current->format('Y-m-d'); } while ( in_array($nextEvent, $excludes) and ($maxIterations > 0) ); return $current; } function findPrevious($start, $frequency, $excludes = [], $vsNow = true, $maxIterations = 1000) { return findNext($start, $frequency, $excludes, $vsNow, $maxIterations, -1); } define('CONFIG_FILE', __DIR__ . DIRECTORY_SEPARATOR . 'config.php'); define('DATA_FILE', __DIR__ . DIRECTORY_SEPARATOR . 'data.php'); if (file_exists(CONFIG_FILE)) require_once CONFIG_FILE; if (!isset($config)) $config = []; $action = (isset($_REQUEST['action']) and preg_match(ACTION_REGEX, $_REQUEST['action'])) ? $_REQUEST['action'] : null; $supplier = array_key_exists('supplier', $_REQUEST) ? $_REQUEST['supplier'] : $requestSupplier; $hasSupplier = is_string($supplier) and preg_match('/^' . SUPPLIER_REGEX . '$/', $supplier); $excludesFormatter = new \IntlDateFormatter('fr_FR.UTF8', \IntlDateFormatter::SHORT, \IntlDateFormatter::NONE, 'Europe/Paris'); $supplierIsNew = false; if ($hasSupplier) { if (!isset($config[$supplier])) { $config[$supplier] = []; $supplierIsNew = true; } $config[$supplier] = array_merge( [ 'title' => '', 'subtitle' => '%date% (%ago%)', 'description' => '', 'choices' => [], 'start' => 'now 00:00:00', 'end' => '+1 year 23:59:59', 'frequency' => '1 day', 'password' => '', 'excludes' => [], ], $config[$supplier] ); $hasPassword = !empty($config[$supplier]['password']); if ($action === 'config') { if ($hasPassword) { if (!isset($_SERVER['PHP_AUTH_USER'])) { header(sprintf('WWW-Authenticate: Basic realm="Configuration de mon panier bio pour %s"', $supplier)); header('HTTP/1.0 401 Unauthorized'); printf('Cette configuration est protégée par mot de passe !'); exit; } elseif ( ($_SERVER['PHP_AUTH_USER'] !== $supplier) or ($_SERVER['PHP_AUTH_PW'] !== $config[$supplier]['password']) ) { header('HTTP/1.0 403 Forbidden'); printf('Cette configuration est protégée par mot de passe !'); exit; } } foreach (array_keys($config[$supplier]) as $key) if (isset($_REQUEST[$key])) $config[$supplier][$key] = (!in_array($key, ['title', 'subtitle', 'description']) ? filter_var($_REQUEST[$key], FILTER_SANITIZE_STRING) : $_REQUEST[$key]); } if (empty($config[$supplier]['start'])) $config[$supplier]['start'] = 'now 00:00:00'; foreach (['choices', 'excludes'] as $key) { if (is_string($config[$supplier][$key])) $config[$supplier][$key] = explode(PHP_EOL, $config[$supplier][$key]); if (!is_array($config[$supplier][$key])) $config[$supplier][$key] = []; $config[$supplier][$key] = array_filter( $config[$supplier][$key], function ($choice) { return is_string($choice) and !empty(trim($choice)); } ); $config[$supplier][$key] = array_map('trim', $config[$supplier][$key]); } $config[$supplier]['excludes'] = array_filter( array_map( function ($value) use ($excludesFormatter) { if (preg_match('/^\d{4}-\d{2}-\d{2}$/', $value)) return $value; $timestamp = $excludesFormatter->parse($value, $offset); if ($timestamp !== false) return (new \DateTimeImmutable('@' . $timestamp, new \DateTimeZone('Europe/Paris')))->format('Y-m-d'); try { return (new \DateTimeImmutable($value, new \DateTimeZone('Europe/Paris')))->format('Y-m-d'); } catch (\Exception $exception) { return null; } }, $config[$supplier]['excludes'] ), function ($value) { return !is_null($value); } ); } $isConfig = false; if ($action === 'config') { $output = fopen(CONFIG_FILE, 'w+'); if ($output) { if (flock($output, LOCK_EX)) { fwrite($output, 'format('Y-m-d'); header('Location: ' . generateUrl($supplier, $nextEvent)); die(); } else { $current = new \DateTime($event); $previous = findPrevious($current, $config[$supplier]['frequency'], $config[$supplier]['excludes'], false); $previousEvent = $previous->format('Y-m-d'); if (false and !array_key_exists($previousEvent, $data[$supplier])) unset($previousEvent); $first = new \DateTime($config[$supplier]['start']); if (true and ($previous->getTimestamp() < $first->getTimestamp())) unset($previousEvent); $next = findNext($current, $config[$supplier]['frequency'], $config[$supplier]['excludes'], false); $nextEvent = $next->format('Y-m-d'); if (false and !array_key_exists($nextEvent, $data[$supplier])) unset($nextEvent); $last = new \DateTime($config[$supplier]['end']); if (true and ($next->getTimestamp() > $last->getTimestamp())) unset($nextEvent); } switch ($action) { case 'insert' : case 'delete' : $item = []; foreach (['name', 'choice', 'action'] as $field) $item[$field] = filter_var($_REQUEST[$field], FILTER_SANITIZE_STRING); $item['timestamp'] = time(); $hash = md5(implode([ $item['name'], $item['choice'], ])); $item['hash'] = $hash; $isBeginning = (!file_exists(DATA_FILE) or in_array(filesize(DATA_FILE), [ false, 0 ])); $output = fopen(DATA_FILE, 'a+'); if (!$output) break; if (!flock($output, LOCK_EX)) break; if ($isBeginning) fwrite($output, ' $prevItem) if ($prevItem['hash'] === $item['hash']) $alreadyInserted = true; if (!$alreadyInserted) $items[] = $item; } elseif ($item['action'] === 'delete') { foreach ($items as $index => $prevItem) if ($prevItem['hash'] === $item['hash']) unset($items[$index]); } } $date = (new \IntlDateFormatter('fr_FR.UTF8', \IntlDateFormatter::FULL, \IntlDateFormatter::NONE, 'Europe/Paris'))->format(new \DateTime($event)); $ago = ago(new \DateTimeImmutable($event)); foreach (['title', 'subtitle', 'description'] as $key) { while (preg_match('/%([^%]+)%/i', $config[$supplier][$key], $match)) $config[$supplier][$key] = str_replace( $match[0], ${$match[1]}, $config[$supplier][$key] ); } if (empty($config[$supplier]['title'])) $config[$supplier]['title'] = $supplier; $stats = []; foreach ($items as $item) if (!empty($item['choice'])) $stats[$item['choice']] += 1; } if ($supplierIsNew and !empty($suppliers)) { $closestSuppliers = array_filter( array_map( function ($other) use ($supplier) { return [ 'supplier' => $other, 'score' => levenshtein($supplier, $other), ]; }, $suppliers ), function ($item) { return $item['score'] > 0; } ); usort($closestSuppliers, function ($a, $b) { if ($a['score'] == $b['score']) { return 0; } return ($a['score'] < $b['score']) ? -1 : 1; }); } $linkUrl = !$hasSupplier ? generateUrl() : (!$hasEvent ? generateUrl($supplier) : generateUrl($supplier, $event)); ?> <?php if ($hasSupplier) : ?><?php echo strip_tags($config[$supplier]['title']); ?><?php if (!$isConfig) : ?> — <?php echo strip_tags($config[$supplier]['subtitle']); ?><?php endif; ?><?php else : ?><?php echo DEFAULT_TITLE; ?><?php endif; ?>
/
Documentation

Configuration

Le titre de la page. Par défaut ce sera le nom du fournisseur
La description affichée sous le titre.
Les différents choix possibles. Un par ligne. Ou pas.
La date du premier événement, si nécessaire de le préciser.
La fréquence des événements dans le format décrit sur cette page.
Les dates à exclure. Une par ligne. Ou pas. En tous cas le format c'est celui de l'ICU : getPattern(); ?>. Par exemple format(new \DateTimeImmutable('first day of january this year', new \DateTimeZone('Europe/Paris'))); ?>, format(new \DateTimeImmutable('now', new \DateTimeZone('Europe/Paris'))); ?> ou format(new \DateTimeImmutable('last day of december this year', new \DateTimeZone('Europe/Paris'))); ?>.
Ce mot de passe sera demandé pour accéder à la configuration la prochaine fois. Le nom d'utilisateur est le fournisseur courant (en l'occurrence ). Par exemple . Et pas de mot de passe, pas de protection.

Oops !

Le nom du fournisseur «  » est probablement mal orthographié, c'est pour ça qu'il n'existe pas.

Peut-être sagissait-il de $item) : ?> 0) : ?> ou , «  » ?

Recommencer

C'est normal !

On souhaite le créer.

Une fois configuré il sera prêt à être utilisé.

Configurer
$choice) : ?>
Nom Choix  
    $count) : ?>