1<?php 2/** 3 * DokuWiki Plugin booking (Helper Component) 4 * 5 * @license GPL 2 http://www.gnu.org/licenses/gpl-2.0.html 6 * @author Andreas Gohr <dokuwiki@cosmocode.de> 7 */ 8 9// must be run within Dokuwiki 10if (!defined('DOKU_INC')) { 11 die(); 12} 13 14class helper_plugin_booking extends DokuWiki_Plugin 15{ 16 const E_NOLENGTH = 1; 17 const E_OVERLAP = 2; 18 19 20 /** 21 * Get the filename where the booking data is stored for this resource 22 * 23 * @param string $id 24 * @return string 25 */ 26 public function getFile($id) 27 { 28 global $conf; 29 $id = cleanID($id); 30 $file = $conf['metadir'] . '/' . utf8_encodeFN(str_replace(':', '/', "$id.booking")); 31 return $file; 32 } 33 34 /** 35 * Get all existing bookings for a given resource 36 * 37 * @param string $id Page ID of the resource 38 * @param int $from List bookings from this timestamp onwards (0 for all) 39 * @param int $to List bookings up to this timestamp (0 for all) 40 * @return array 41 */ 42 public function getBookings($id, $from = 0, $to = 0) 43 { 44 $file = $this->getFile($id); 45 if (!file_exists($file)) return []; 46 47 $fh = fopen($file, 'r'); 48 if (!$fh) return []; 49 50 $bookings = []; 51 52 while (($line = fgets($fh, 4096)) !== false) { 53 $line = trim($line); 54 if ($line === '') continue; 55 list($start, $end, $user) = explode("\t", $line, 3); 56 if ($to) { 57 // list all overlapping bookings 58 if ($start > $to) continue; 59 if ($end <= $from) continue; 60 } else { 61 // list all bookings that have not been ended at $from 62 if ($end < $from) continue; 63 } 64 65 // we use the start time as index, for sorting 66 $bookings[$start] = [ 67 'start' => $start, 68 'end' => $end, 69 'user' => $user]; 70 } 71 72 fclose($fh); 73 74 ksort($bookings); 75 return $bookings; 76 } 77 78 /** 79 * Parses simple time length strings to seconds 80 * 81 * @param string $time 82 * @return int Returns 0 when the time could not be parsed 83 */ 84 public function parseTime($time) 85 { 86 $time = trim($time); 87 88 if (preg_match('/([\d\.,]+)([dhm])/i', $time, $m)) { 89 $val = floatval(str_replace(',', '.', $m[1])); 90 $unit = strtolower($m[2]); 91 92 // convert to seconds 93 if ($unit === 'd') { 94 $val = $val * 60 * 60 * 8; 95 } elseif ($unit === 'h') { 96 $val = $val * 60 * 60; 97 } else { 98 $val = $val * 60; 99 } 100 101 return (int)$val; 102 } 103 104 return 0; 105 } 106 107 /** 108 * Adds a booking 109 * 110 * @param string $id resource to book 111 * @param string $begin strtotime compatible start datetime 112 * @param string $length length of booking 113 * @param string $user user doing the booking 114 * @throws Exception when a booking can't be added 115 */ 116 public function addBooking($id, $begin, $length, $user) 117 { 118 $file = $this->getFile($id); 119 io_makeFileDir($file); 120 121 $start = strtotime($begin); 122 $end = $start + $this->parseTime($length); 123 if ($start == $end) throw new \Exception('No valid length specified', self::E_NOLENGTH); 124 125 $conflicts = $this->getBookings($id, $start, $end); 126 if ($conflicts) throw new \Exception('Existing booking overlaps', self::E_OVERLAP); 127 128 $line = "$start\t$end\t$user\n"; 129 file_put_contents($file, $line, FILE_APPEND); 130 } 131 132 /** 133 * Cancel a booking 134 * 135 * The booking line is replaced by spaces in the file 136 * 137 * @param string $id The booking resource 138 * @param string|int $at The start time of the booking to cancel. Use int for timestamp 139 * @param string|null $user Only cancel if the user matches, null for no check 140 * @return bool Was any booking canceled? 141 */ 142 public function cancelBooking($id, $at, $user=null) 143 { 144 $file = $this->getFile($id); 145 if (!file_exists($file)) return false; 146 147 $fh = fopen($file, 'r+'); 148 if (!$fh) return false; 149 150 // we support ints and time strings 151 if (!is_int($at)) { 152 $at = strtotime($at); 153 } 154 155 while (($line = fgets($fh, 4096)) !== false) { 156 list($start, ,$booker) = explode("\t", $line, 3); 157 if ($start != $at) continue; 158 if ($user && $user != trim($booker)) continue; 159 160 $len = strlen($line); // length of line (includes newline) 161 fseek($fh, -1 * $len, SEEK_CUR); // jump back to beginning of line 162 fwrite($fh, str_pad('', $len - 1)); // write spaces instead (keep new line) 163 return true; 164 } 165 fclose($fh); 166 167 return false; 168 } 169} 170 171