*/ // must be run within Dokuwiki if (!defined('DOKU_INC')) { die(); } class helper_plugin_booking extends DokuWiki_Plugin { const E_NOLENGTH = 1; const E_OVERLAP = 2; /** * Get the filename where the booking data is stored for this resource * * @param string $id * @return string */ public function getFile($id) { global $conf; $id = cleanID($id); $file = $conf['metadir'] . '/' . utf8_encodeFN(str_replace(':', '/', "$id.booking")); return $file; } /** * Get all existing bookings for a given resource * * @param string $id Page ID of the resource * @param int $from List bookings from this timestamp onwards (0 for all) * @param int $to List bookings up to this timestamp (0 for all) * @return array */ public function getBookings($id, $from = 0, $to = 0) { $file = $this->getFile($id); if (!file_exists($file)) return []; $fh = fopen($file, 'r'); if (!$fh) return []; $bookings = []; while (($line = fgets($fh, 4096)) !== false) { $line = trim($line); if ($line === '') continue; list($start, $end, $user) = explode("\t", $line, 3); if ($to) { // list all overlapping bookings if ($start > $to) continue; if ($end <= $from) continue; } else { // list all bookings that have not been ended at $from if ($end < $from) continue; } // we use the start time as index, for sorting $bookings[$start] = [ 'start' => $start, 'end' => $end, 'user' => $user]; } fclose($fh); ksort($bookings); return $bookings; } /** * Parses simple time length strings to seconds * * @param string $time * @return int Returns 0 when the time could not be parsed */ public function parseTime($time) { $time = trim($time); if (preg_match('/([\d\.,]+)([dhm])/i', $time, $m)) { $val = floatval(str_replace(',', '.', $m[1])); $unit = strtolower($m[2]); // convert to seconds if ($unit === 'd') { $val = $val * 60 * 60 * 8; } elseif ($unit === 'h') { $val = $val * 60 * 60; } else { $val = $val * 60; } return (int)$val; } return 0; } /** * Adds a booking * * @param string $id resource to book * @param string $begin strtotime compatible start datetime * @param string $length length of booking * @param string $user user doing the booking * @throws Exception when a booking can't be added */ public function addBooking($id, $begin, $length, $user) { $file = $this->getFile($id); io_makeFileDir($file); $start = strtotime($begin); $end = $start + $this->parseTime($length); if ($start == $end) throw new \Exception('No valid length specified', self::E_NOLENGTH); $conflicts = $this->getBookings($id, $start, $end); if ($conflicts) throw new \Exception('Existing booking overlaps', self::E_OVERLAP); $line = "$start\t$end\t$user\n"; file_put_contents($file, $line, FILE_APPEND); } /** * Cancel a booking * * The booking line is replaced by spaces in the file * * @param string $id The booking resource * @param string|int $at The start time of the booking to cancel. Use int for timestamp * @param string|null $user Only cancel if the user matches, null for no check * @return bool Was any booking canceled? */ public function cancelBooking($id, $at, $user=null) { $file = $this->getFile($id); if (!file_exists($file)) return false; $fh = fopen($file, 'r+'); if (!$fh) return false; // we support ints and time strings if (!is_int($at)) { $at = strtotime($at); } while (($line = fgets($fh, 4096)) !== false) { list($start, ,$booker) = explode("\t", $line, 3); if ($start != $at) continue; if ($user && $user != trim($booker)) continue; $len = strlen($line); // length of line (includes newline) fseek($fh, -1 * $len, SEEK_CUR); // jump back to beginning of line fwrite($fh, str_pad('', $len - 1)); // write spaces instead (keep new line) return true; } fclose($fh); return false; } }