Schedule.php 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267
  1. <?php
  2. class Schedule {
  3. /** @var string[] $abbreviations */
  4. private $abbreviations;
  5. /** @var DateTime[] $displaydates */
  6. private $displaydates;
  7. /** @var string[] $stations */
  8. private $stations;
  9. /** @var string[] $stations */
  10. private $optstations;
  11. /** @var array[] $stations */
  12. private $wantedstations;
  13. /** @var array $improps */
  14. private $improps;
  15. /** @var GDIMAGE $this->image */
  16. private $image;
  17. /** @var boolean $darkmode */
  18. private $darkmode;
  19. /** @var boolean $debug */
  20. private $debug;
  21. public function __construct(array $displaydates, array $stations, array $optstations = [], $wantedstations = [], $darkmode = false) {
  22. $this->abbreviations = [];
  23. $this->training = $this->event = false;
  24. $this->displaydates = $displaydates;
  25. $this->stations = $stations;
  26. $this->optstations = $optstations;
  27. $this->wantedstations = $wantedstations;
  28. $this->improps = [];
  29. $this->darkmode = $darkmode;
  30. //$this->debug = true;
  31. }
  32. public function createImage() {
  33. $rawData = $this->fetchData();
  34. $relevantBookings = [];
  35. foreach($this->stations as $station) {
  36. $relevantBookings[$station] = [];
  37. }
  38. foreach($rawData as $item) {
  39. if(in_array($item['station']['ident'], $this->stations)) {
  40. $relevantBookings[$item['station']['ident']][] = [
  41. 'starts_at' => new DateTime($item['starts_at']),
  42. 'ends_at' => new DateTime($item['ends_at']),
  43. 'lastname' => $item['controller']['lastname'],
  44. 'firstname' => $item['controller']['firstname'],
  45. 'training' => $item['training'],
  46. 'event' => $item['event']
  47. ];
  48. }
  49. }
  50. putenv('GDFONTPATH=' . realpath('.'));
  51. $this->improps['font'] = "UbuntuMono-Regular";
  52. // $this->improps['font'] = "EuroScope";
  53. $this->improps['width'] = 100 * count($this->displaydates) + 100;
  54. $this->improps['height'] = 25 * count($this->stations) + 600;
  55. if(!$this->darkmode) {
  56. $this->image = imagecreatetruecolor($this->improps['width'], $this->improps['height']);
  57. $this->improps['bg'] = imagecolorallocate($this->image, 255, 255, 255);
  58. $this->improps['black'] = imagecolorexact($this->image, 0, 0, 0);
  59. $this->improps['open'] = imagecolorexact($this->image, 155, 155, 155);
  60. $this->improps['wanted'] = imagecolorexact($this->image, 255, 0, 0);
  61. $this->improps['event'] = imagecolorexact($this->image, 0, 255, 0);
  62. $this->improps['training'] = imagecolorexact($this->image, 50, 100, 255);
  63. $this->improps['eventtraining'] = imagecolorexact($this->image, 0, 255, 255);
  64. } else {
  65. $this->image = imagecreatetruecolor($this->improps['width'], $this->improps['height']);
  66. $this->improps['bg'] = imagecolorallocate($this->image, 0, 0, 0);
  67. $this->improps['black'] = imagecolorexact($this->image, 255, 255, 255);
  68. $this->improps['open'] = imagecolorexact($this->image, 155, 155, 155);
  69. $this->improps['wanted'] = imagecolorexact($this->image, 255, 0, 0);
  70. $this->improps['event'] = imagecolorexact($this->image, 0, 255, 0);
  71. $this->improps['training'] = imagecolorexact($this->image, 50, 100, 255);
  72. $this->improps['eventtraining'] = imagecolorexact($this->image, 0, 255, 255);
  73. }
  74. imagefill($this->image, 0, 0, $this->improps['bg']);
  75. $this->improps['lineheight'] = 20;
  76. $sepheight = 25;
  77. for($i = 0; $i < count($this->displaydates); $i++) {
  78. imagefttext($this->image, 11, 0, ($i) * 100 + 100, 25, $this->improps['black'], $this->improps['font'], $this->displaydates[$i]->format("D d.m."));
  79. }
  80. $height = 40;
  81. foreach($this->stations as $station) {
  82. if($this->debug) echo "\n". $station . "\th$height";
  83. imageline($this->image, 0, $height + 5, $this->improps['width'], $height + 5, $this->improps['open']);
  84. if($station == '--') {
  85. $height += $sepheight;
  86. continue;
  87. }
  88. $height += $this->improps['lineheight'];
  89. $lines = 0;
  90. for($i = 0; $i < count($this->displaydates); $i++) {
  91. $l = $this->addBookingData($station, $this->displaydates[$i], $relevantBookings[$station], $i * 100 + 100, $height);
  92. $lines = $l > $lines ? $l : $lines;
  93. if($this->debug) echo "\t" . $this->displaydates[$i]->format("m-d") . ": " . $lines;
  94. }
  95. if(!in_array($station, $this->optstations) && $lines < 1) $lines = 1;
  96. if($this->debug) echo "\tl$lines";
  97. if($lines > 0) {
  98. imagefttext($this->image, 11, 0, 10, $height, $this->improps['black'], $this->improps['font'], $station);
  99. imageline($this->image, 0, $height + 5, $this->improps['width'], $height + 5, $this->improps['open']);
  100. }
  101. $height += ($lines - 1) * $this->improps['lineheight'];
  102. }
  103. for($i = 0; $i < count($this->displaydates); $i++) {
  104. imageline($this->image, 100 * $i + 92, 0, 100 * $i + 92, $height + 5, $this->improps['open']);
  105. }
  106. $height += $this->improps['lineheight'] + 5;
  107. //imagefttext($this->image, 11, 0, 10, $height, $this->improps['wanted'], $this->improps['font'], 'Wanted');
  108. imagefttext($this->image, 11, 0, 100, $height, $this->improps['training'], $this->improps['font'], 'Training');
  109. imagefttext($this->image, 11, 0, 200, $height, $this->improps['event'], $this->improps['font'], 'Event');
  110. imagefttext($this->image, 11, 0, 300, $height, $this->improps['eventtraining'], $this->improps['font'], 'Event + Training');
  111. $height += $this->improps['lineheight'] * 2;
  112. $namelength = 0;
  113. asort($this->abbreviations);
  114. foreach($this->abbreviations as $name) {
  115. $namelength = strlen($name) > $namelength ? strlen($name) : $namelength;
  116. }
  117. $collength = ($namelength + 10) * 8;
  118. $cols = floor( ($this->improps['width'] - 10) / $collength);
  119. $col = 0;
  120. $row = 0;
  121. foreach($this->abbreviations as $abbrv => $name) {
  122. imagefttext($this->image, 11, 0, $col * $collength + 10, $height + $row * $this->improps['lineheight'], $this->improps['black'], $this->improps['font'], $abbrv . ": " . $name);
  123. $row = ($col + 1) >= $cols ? $row + 1 : $row;
  124. $col = ($col + 1) >= $cols ? 0 : $col + 1;
  125. }
  126. $height += $this->improps['lineheight'] * ($row +2);
  127. imagefttext($this->image, 11, 0, 10, $height, $this->improps['open'], $this->improps['font'], "Generated " . (new DateTime("now", new DateTimeZone('UTC')))->format('d.m.Y H:i:s') . "z" );
  128. $this->image = imagecrop($this->image, ['x' => 0, 'y' => 0, 'width' => $this->improps['width'], 'height' => $height+5]);
  129. return $this->image;
  130. }
  131. private function addBookingData($station, $date, $bookings, $x, $height) {
  132. $count = 0;
  133. foreach($bookings as $booking) {
  134. if($booking['starts_at']->format('Y-m-d') == $date->format('Y-m-d')) {
  135. $abbrv = substr($booking['firstname'], 0, 3) . substr($booking['lastname'], 0, 3); //TODO
  136. $this->abbreviations[$abbrv] = $booking['firstname'] . " " . $booking['lastname'];
  137. if($booking['training'] && $booking['event']) $color = $this->improps['eventtraining'];
  138. elseif($booking['training']) $color = $this->improps['training'];
  139. elseif($booking['event']) $color = $this->improps['event'];
  140. else $color = $this->improps['black'];
  141. imagefttext($this->image, 11, 0, $x, $height + $count * $this->improps['lineheight'], $color,
  142. $this->improps['font'], $abbrv . " " . $booking['starts_at']->format('H') . "-" . $booking['ends_at']->format('H'));
  143. $count++;
  144. }
  145. }
  146. if($count == 0 && !in_array($station, $this->optstations)) {
  147. if(!$this->dayIsWanted($station, $date))
  148. imagefttext($this->image, 10, 0, $x, $height, $this->improps['open'], $this->improps['font'], "Open");
  149. else
  150. imagefttext($this->image, 10, 0, $x, $height, $this->improps['wanted'], $this->improps['font'], "WANTED!");
  151. }
  152. return $count;
  153. }
  154. private function fetchData() {
  155. $url = sprintf("https://vatsim-germany.org/api/booking/atc/daterange/%s/%s", $this->displaydates[0]->modify("-1 day")->format("d.m.Y"), $this->displaydates[count($this->displaydates) -1]->format("d.m.Y"));
  156. $this->displaydates[0]->modify("+1 day");
  157. if($this->debug) echo "\nUrl: $url";
  158. $curl = curl_init();
  159. curl_setopt($curl, CURLOPT_URL, $url ) ;
  160. curl_setopt($curl, CURLOPT_HTTPHEADER, ["X-Requested-With: XMLHttpRequest"]);
  161. curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
  162. // curl_setopt($curl, CURLOPT_VERBOSE, true);
  163. // $verbose = fopen('php://temp', 'w+');
  164. // curl_setopt($curl, CURLOPT_STDERR, $verbose);
  165. $result = curl_exec($curl);
  166. // if ($result === FALSE) {
  167. // printf("cUrl error (#%d): %s<br>\n", curl_errno($curl),
  168. // htmlspecialchars(curl_error($curl)));
  169. // }
  170. // rewind($verbose);
  171. // $verboseLog = stream_get_contents($verbose);
  172. // echo "Verbose information:\n<pre>", htmlspecialchars($verboseLog), "</pre>\n";
  173. curl_close($curl);
  174. return json_decode($result, true);
  175. }
  176. public static function EveryXDaysFromStartdate($startdate, $x = 1, $numberofdates = 1) {
  177. $startdate = new DateTime($startdate, new DateTimeZone('UTC'));
  178. $days = [];
  179. while(count($days) < $numberofdates) {
  180. if(new DateTime('today', new DateTimeZone('UTC')) <= $startdate) {
  181. $days[] = clone($startdate);
  182. }
  183. $startdate->add(new DateInterval(sprintf("P%dD", $x)));
  184. }
  185. return $days;
  186. }
  187. public static function EveryXWeeksFromStartdate($startdate, $x = 1, $numberofdates = 1) {
  188. return self::EveryXDaysFromStartdate($startdate, $x * 7, $numberofdates);
  189. }
  190. public static function EveryWeekday($dayoftheweek, $numberofdates = 1) {
  191. $startdate = new DateTime($dayoftheweek, new DateTimeZone('UTC'));
  192. $days = [];
  193. while(count($days) < $numberofdates) {
  194. $days[] = clone($startdate);
  195. $startdate->add(new DateInterval("P7D"));
  196. }
  197. return $days;
  198. }
  199. public static function WholeWeek($startdate = "today") {
  200. $days = [];
  201. $startdate = new DateTime($startdate, new DateTimeZone('UTC'));
  202. for($i = 0; $i < 7; $i++) {
  203. $days[] = clone($startdate);
  204. $startdate->add(new DateInterval("P1D"));
  205. }
  206. return $days;
  207. }
  208. public static function processWantedDays(...$args) {
  209. if(count($args) % 2 == 1) throw new Exception("Even number of arguments required.");
  210. $r = [];
  211. for($i = 0; $i < count($args); $i += 2) {
  212. foreach($args[$i] as $station) {
  213. if(!isset($r[$station])) $r[$station] = [];
  214. foreach($args[$i + 1] as $date) {
  215. $r[$station][] = $date;
  216. }
  217. }
  218. }
  219. return $r;
  220. }
  221. private function dayIsWanted($station, $date) {
  222. if(!isset($this->wantedstations[$station])) return false;
  223. foreach($this->wantedstations[$station] as $target) {
  224. if($target->format("Y-m-d") == $date->format("Y-m-d")) return true;
  225. }
  226. return false;
  227. }
  228. }