generate.php (22101B)
1 <?php 2 // Generate extension, https://github.com/annaesvensson/yellow-generate 3 4 class YellowGenerate { 5 const VERSION = "0.9.8"; 6 public $yellow; // access to API 7 public $files; // number of files 8 public $errors; // number of errors 9 public $locationsWithArguments; // locations with arguments detected 10 public $locationsWithPagination; // locations with pagination detected 11 12 // Handle initialisation 13 public function onLoad($yellow) { 14 $this->yellow = $yellow; 15 $this->yellow->system->setDefault("generateStaticUrl", "auto"); 16 $this->yellow->system->setDefault("generateStaticDirectory", "public/"); 17 $this->yellow->system->setDefault("generateStaticDefaultFile", "index.html"); 18 $this->yellow->system->setDefault("generateStaticErrorFile", "404.html"); 19 } 20 21 // Handle request 22 public function onRequest($scheme, $address, $base, $location, $fileName) { 23 return $this->processRequestCache($scheme, $address, $base, $location, $fileName); 24 } 25 26 // Handle command 27 public function onCommand($command, $text) { 28 switch ($command) { 29 case "generate": $statusCode = $this->processCommandGenerate($command, $text); break; 30 case "clean": $statusCode = $this->processCommandClean($command, $text); break; 31 default: $statusCode = 0; 32 } 33 return $statusCode; 34 } 35 36 // Handle command help 37 public function onCommandHelp() { 38 return array("generate [directory location]", "clean [directory location]"); 39 } 40 41 // Process command to generate static website 42 public function processCommandGenerate($command, $text) { 43 $statusCode = 0; 44 list($path, $location) = $this->yellow->toolbox->getTextArguments($text); 45 if (is_string_empty($location) || substru($location, 0, 1)=="/") { 46 if ($this->checkStaticSettings()) { 47 $statusCode = $this->generateStatic($path, $location); 48 } else { 49 $statusCode = 500; 50 $this->files = 0; 51 $this->errors = 1; 52 $fileName = $this->yellow->system->get("coreExtensionDirectory").$this->yellow->system->get("coreSystemFile"); 53 echo "ERROR generating files: Please configure GenerateStaticUrl in file '$fileName'!\n"; 54 } 55 echo "Yellow $command: $this->files file".($this->files!=1 ? "s" : ""); 56 echo ", $this->errors error".($this->errors!=1 ? "s" : "")."\n"; 57 } else { 58 $statusCode = 400; 59 echo "Yellow $command: Invalid arguments\n"; 60 } 61 return $statusCode; 62 } 63 64 // Generate static website 65 public function generateStatic($path, $location) { 66 $statusCode = 200; 67 $this->files = $this->errors = 0; 68 $path = rtrim(is_string_empty($path) ? $this->yellow->system->get("generateStaticDirectory") : $path, "/"); 69 if (is_string_empty($location)) { 70 $statusCode = $this->cleanStatic($path, $location); 71 foreach ($this->yellow->extension->data as $key=>$value) { 72 if (method_exists($value["object"], "onUpdate")) $value["object"]->onUpdate("clean"); 73 } 74 } 75 $statusCode = max($statusCode, $this->generateStaticContent($path, $location, "\rGenerating static website", 5, 95)); 76 $statusCode = max($statusCode, $this->generateStaticMedia($path, $location)); 77 $statusCode = max($statusCode, $this->generateStaticSystem($path, $location)); 78 echo "\rGenerating static website 100%... done\n"; 79 return $statusCode; 80 } 81 82 // Generate static content 83 public function generateStaticContent($path, $locationFilter, $progressText, $increments, $max) { 84 $statusCode = 200; 85 $this->locationsWithArguments = $this->locationsWithPagination = array(); 86 $staticUrl = $this->yellow->system->get("generateStaticUrl"); 87 list($scheme, $address, $base) = $this->yellow->lookup->getUrlInformation($staticUrl); 88 $locations = $this->getContentLocations(); 89 $filesEstimated = count($locations); 90 foreach ($locations as $location) { 91 echo "$progressText ".$this->getProgressPercent($this->files, $filesEstimated, $increments, $max/1.5)."%... "; 92 if (!preg_match("#^$base$locationFilter#", "$base$location")) continue; 93 $statusCode = max($statusCode, $this->generateStaticFile($path, $location, true)); 94 } 95 foreach ($this->locationsWithArguments as $location) { 96 echo "$progressText ".$this->getProgressPercent($this->files, $filesEstimated, $increments, $max/1.5)."%... "; 97 if (!preg_match("#^$base$locationFilter#", "$base$location")) continue; 98 $statusCode = max($statusCode, $this->generateStaticFile($path, $location, true)); 99 } 100 $filesEstimated = $this->files + count($this->locationsWithArguments) + count($this->locationsWithPagination); 101 foreach ($this->locationsWithPagination as $location) { 102 echo "$progressText ".$this->getProgressPercent($this->files, $filesEstimated, $increments, $max)."%... "; 103 if (!preg_match("#^$base$locationFilter#", "$base$location")) continue; 104 for ($pageNumber=2; $pageNumber<=999; ++$pageNumber) { 105 $statusCodeLocation = $this->generateStaticFile($path, $location.$pageNumber, false, true); 106 $statusCode = max($statusCode, $statusCodeLocation); 107 if ($statusCodeLocation==100) break; 108 } 109 } 110 echo "$progressText ".$this->getProgressPercent(100, 100, $increments, $max)."%... "; 111 return $statusCode; 112 } 113 114 // Generate static media 115 public function generateStaticMedia($path, $locationFilter) { 116 $statusCode = 200; 117 if (is_string_empty($locationFilter)) { 118 foreach ($this->getMediaLocations() as $location) { 119 $statusCode = max($statusCode, $this->generateStaticFile($path, $location)); 120 } 121 } 122 return $statusCode; 123 } 124 125 // Generate static system 126 public function generateStaticSystem($path, $locationFilter) { 127 $statusCode = 200; 128 if (is_string_empty($locationFilter)) { 129 foreach ($this->getSystemLocations($path) as $location) { 130 $statusCode = max($statusCode, $this->generateStaticFile($path, $location)); 131 } 132 $statusCode = max($statusCode, $this->generateStaticFile($path, "/error/", false, false, true)); 133 } 134 return $statusCode; 135 } 136 137 // Generate static file 138 public function generateStaticFile($path, $location, $analyse = false, $probe = false, $error = false) { 139 $this->yellow->page = new YellowPage($this->yellow); 140 $this->yellow->page->fileName = substru($location, 1); 141 if (!is_readable($this->yellow->page->fileName)) { 142 ob_start(); 143 $staticUrl = $this->yellow->system->get("generateStaticUrl"); 144 list($scheme, $address, $base) = $this->yellow->lookup->getUrlInformation($staticUrl); 145 $statusCode = $this->requestStaticFile($scheme, $address, $base, $location); 146 if ($statusCode<400 || $error) { 147 $fileData = ob_get_contents(); 148 $statusCode = $this->saveStaticFile($path, $location, $fileData, $statusCode); 149 } 150 ob_end_clean(); 151 } else { 152 $statusCode = $this->copyStaticFile($path, $location); 153 } 154 if ($statusCode==200 && $analyse) $this->analyseStaticLocations($scheme, $address, $base, $fileData); 155 if ($statusCode==404 && $probe) $statusCode = 100; 156 if ($statusCode==404 && $error) $statusCode = 200; 157 if ($statusCode>=200) ++$this->files; 158 if ($statusCode>=400) { 159 ++$this->errors; 160 echo "\rERROR generating location '$location', ".$this->yellow->page->getStatusCode(true)."\n"; 161 } 162 if ($this->yellow->system->get("coreDebugMode")>=2) { 163 echo "YellowGenerate::generateStaticFile status:$statusCode location:$location<br />\n"; 164 } 165 return $statusCode; 166 } 167 168 // Request static file 169 public function requestStaticFile($scheme, $address, $base, $location) { 170 list($serverName, $serverPort) = $this->yellow->toolbox->getTextList($address, ":", 2); 171 if (is_string_empty($serverPort)) $serverPort = $scheme=="https" ? 443 : 80; 172 $_SERVER["SERVER_PROTOCOL"] = "HTTP/1.1"; 173 $_SERVER["SERVER_NAME"] = $serverName; 174 $_SERVER["SERVER_PORT"] = $serverPort; 175 $_SERVER["REQUEST_METHOD"] = "GET"; 176 $_SERVER["REQUEST_SCHEME"] = $scheme; 177 $_SERVER["REQUEST_URI"] = $base.$location; 178 $_SERVER["SCRIPT_NAME"] = $base."/yellow.php"; 179 $_SERVER["REMOTE_ADDR"] = "127.0.0.1"; 180 $_REQUEST = array(); 181 return $this->yellow->request(); 182 } 183 184 // Save static file 185 public function saveStaticFile($path, $location, $fileData, $statusCode) { 186 $modified = strtotime($this->yellow->page->getHeader("Last-Modified")); 187 if ($modified==0) $modified = $this->yellow->toolbox->getFileModified($this->yellow->page->fileName); 188 if ($statusCode>=301 && $statusCode<=303) { 189 $fileData = $this->getStaticRedirect($this->yellow->page->getHeader("Location")); 190 $modified = time(); 191 } 192 $fileName = $this->getStaticFile($path, $location, $statusCode); 193 if (is_file($fileName)) $this->yellow->toolbox->deleteFile($fileName); 194 if (!$this->yellow->toolbox->writeFile($fileName, $fileData, true) || 195 !$this->yellow->toolbox->modifyFile($fileName, $modified)) { 196 $statusCode = 500; 197 $this->yellow->page->statusCode = $statusCode; 198 $this->yellow->page->errorMessage = "Can't write file '$fileName'!"; 199 } 200 return $statusCode; 201 } 202 203 // Copy static file 204 public function copyStaticFile($path, $location) { 205 $statusCode = 200; 206 $modified = $this->yellow->toolbox->getFileModified($this->yellow->page->fileName); 207 $fileName = $this->getStaticFile($path, $location, $statusCode); 208 if (is_file($fileName)) $this->yellow->toolbox->deleteFile($fileName); 209 if (!$this->yellow->toolbox->copyFile($this->yellow->page->fileName, $fileName, true) || 210 !$this->yellow->toolbox->modifyFile($fileName, $modified)) { 211 $statusCode = 500; 212 $this->yellow->page->statusCode = $statusCode; 213 $this->yellow->page->errorMessage = "Can't write file '$fileName'!"; 214 } 215 return $statusCode; 216 } 217 218 // Analyse static locations with arguments 219 public function analyseStaticLocations($scheme, $address, $base, $rawData) { 220 preg_match_all("/<(.*?)href=\"([^\"]+)\"(.*?)>/i", $rawData, $matches); 221 foreach ($matches[2] as $match) { 222 $location = rawurldecode($match); 223 if (preg_match("/^(.*?)#(.*)$/", $location, $tokens)) $location = $tokens[1]; 224 if (preg_match("/^(\w+):\/\/([^\/]+)(.*)$/", $location, $tokens)) { 225 if ($tokens[1]!=$scheme) continue; 226 if ($tokens[2]!=$address) continue; 227 $location = $tokens[3]; 228 } 229 if (substru($location, 0, strlenu($base))!=$base) continue; 230 if (substru($location, strlenu($base), 1)!="/") continue; 231 $location = substru($location, strlenu($base)); 232 if (!$this->yellow->toolbox->isLocationArguments($location)) continue; 233 if (!$this->yellow->toolbox->isLocationArgumentsPagination($location)) { 234 if (!isset($this->locationsWithArguments[$location])) { 235 $this->locationsWithArguments[$location] = $location; 236 if ($this->yellow->system->get("coreDebugMode")>=2) { 237 echo "YellowGenerate::analyseStaticLocations detected location:$location<br />\n"; 238 } 239 } 240 } else { 241 $location = rtrim($location, "0..9"); 242 if (!isset($this->locationsWithPagination[$location])) { 243 $this->locationsWithPagination[$location] = $location; 244 if ($this->yellow->system->get("coreDebugMode")>=2) { 245 echo "YellowGenerate::analyseStaticLocations detected location:$location<br />\n"; 246 } 247 } 248 } 249 } 250 } 251 252 // Process command to clean static website 253 public function processCommandClean($command, $text) { 254 $statusCode = 0; 255 list($path, $location) = $this->yellow->toolbox->getTextArguments($text); 256 if (is_string_empty($location) || substru($location, 0, 1)=="/") { 257 $statusCode = $this->cleanStatic($path, $location); 258 echo "Yellow $command: Static website"; 259 echo " ".($statusCode!=200 ? "not " : "")."cleaned\n"; 260 } else { 261 $statusCode = 400; 262 echo "Yellow $command: Invalid arguments\n"; 263 } 264 return $statusCode; 265 } 266 267 // Clean static website 268 public function cleanStatic($path, $location) { 269 $statusCode = 200; 270 $path = rtrim(is_string_empty($path) ? $this->yellow->system->get("generateStaticDirectory") : $path, "/"); 271 if (is_string_empty($location)) { 272 $statusCode = max($statusCode, $this->cleanStaticDirectory($path)); 273 } else { 274 if ($this->yellow->lookup->isFileLocation($location)) { 275 $fileName = $this->getStaticFile($path, $location, $statusCode); 276 $statusCode = $this->cleanStaticFile($fileName); 277 } else { 278 $statusCode = $this->cleanStaticDirectory($path.$location); 279 } 280 } 281 return $statusCode; 282 } 283 284 // Clean static directory 285 public function cleanStaticDirectory($path) { 286 $statusCode = 200; 287 if (is_dir($path) && $this->checkStaticDirectory($path)) { 288 if (!$this->yellow->toolbox->deleteDirectory($path)) { 289 $statusCode = 500; 290 echo "ERROR cleaning files: Can't delete directory '$path'!\n"; 291 } 292 } 293 return $statusCode; 294 } 295 296 // Clean static file 297 public function cleanStaticFile($fileName) { 298 $statusCode = 200; 299 if (is_file($fileName)) { 300 if (!$this->yellow->toolbox->deleteFile($fileName)) { 301 $statusCode = 500; 302 echo "ERROR cleaning files: Can't delete file '$fileName'!\n"; 303 } 304 } 305 return $statusCode; 306 } 307 308 // Process request for cached files 309 public function processRequestCache($scheme, $address, $base, $location, $fileName) { 310 $statusCode = 0; 311 if (is_dir($this->yellow->system->get("coreCacheDirectory"))) { 312 $location .= $this->yellow->toolbox->getLocationArguments(); 313 $fileName = rtrim($this->yellow->system->get("coreCacheDirectory"), "/").$location; 314 if (!$this->yellow->lookup->isFileLocation($location)) $fileName .= $this->yellow->system->get("generateStaticDefaultFile"); 315 if (is_file($fileName) && is_readable($fileName) && !$this->yellow->lookup->isCommandLine()) { 316 $statusCode = $this->yellow->sendFile(200, $fileName, true); 317 } 318 } 319 return $statusCode; 320 } 321 322 // Check static settings 323 public function checkStaticSettings() { 324 return preg_match("/^(http|https):/", $this->yellow->system->get("generateStaticUrl")); 325 } 326 327 // Check static directory 328 public function checkStaticDirectory($path) { 329 $ok = false; 330 if (!is_string_empty($path)) { 331 if ($path==rtrim($this->yellow->system->get("generateStaticDirectory"), "/")) $ok = true; 332 if ($path==rtrim($this->yellow->system->get("coreCacheDirectory"), "/")) $ok = true; 333 if ($path==rtrim($this->yellow->system->get("coreTrashDirectory"), "/")) $ok = true; 334 if (is_file("$path/".$this->yellow->system->get("generateStaticDefaultFile"))) $ok = true; 335 if (is_file("$path/yellow.php")) $ok = false; 336 } 337 return $ok; 338 } 339 340 // Return progress in percent 341 public function getProgressPercent($now, $total, $increments, $max) { 342 $max = intval($max/$increments) * $increments; 343 $percent = intval(($max/$total) * $now); 344 if ($increments>1) $percent = intval($percent/$increments) * $increments; 345 return min($max, $percent); 346 } 347 348 // Return static file 349 public function getStaticFile($path, $location, $statusCode) { 350 if ($statusCode<400) { 351 $fileName = $path.$location; 352 if (!$this->yellow->lookup->isFileLocation($location)) $fileName .= $this->yellow->system->get("generateStaticDefaultFile"); 353 } elseif ($statusCode==404) { 354 $fileName = $path."/".$this->yellow->system->get("generateStaticErrorFile"); 355 } else { 356 $fileName = $path."/error.html"; 357 } 358 return $fileName; 359 } 360 361 // Return static redirect 362 public function getStaticRedirect($location) { 363 $output = "<!DOCTYPE html><html>\n<head>\n"; 364 $output .= "<meta http-equiv=\"content-type\" content=\"text/html; charset=utf-8\" />\n"; 365 $output .= "<meta http-equiv=\"refresh\" content=\"0;url=".htmlspecialchars($location)."\" />\n"; 366 $output .= "</head>\n</html>"; 367 return $output; 368 } 369 370 // Return content locations 371 public function getContentLocations() { 372 $locations = array(); 373 $staticUrl = $this->yellow->system->get("generateStaticUrl"); 374 list($scheme, $address, $base) = $this->yellow->lookup->getUrlInformation($staticUrl); 375 $this->yellow->page->setRequestInformation($scheme, $address, $base, "", "", false); 376 foreach ($this->yellow->content->getChildrenRecursive("", true) as $page) { 377 if (preg_match("/exclude/i", $page->get("generate"))) continue; 378 if ($page->get("status")=="private" || $page->get("status")=="draft") continue; 379 array_push($locations, $page->location); 380 } 381 if (!$this->yellow->content->find("/") && $this->yellow->system->get("coreMultiLanguageMode")) array_unshift($locations, "/"); 382 return $locations; 383 } 384 385 // Return media locations 386 public function getMediaLocations() { 387 $locations = array(); 388 $path = $this->yellow->system->get("coreMediaDirectory"); 389 $fileNames = $this->yellow->toolbox->getDirectoryEntriesRecursive($path, "/.*/", false, false); 390 foreach ($fileNames as $fileName) { 391 $location = $this->yellow->lookup->findMediaLocationFromFile($fileName); 392 if (!is_string_empty($location)) array_push($locations, $location); 393 } 394 return $locations; 395 } 396 397 // Return system locations 398 public function getSystemLocations($pathIgnore) { 399 $locations = array(); 400 $path = $this->yellow->system->get("coreWorkerDirectory"); 401 $fileNames = $this->yellow->toolbox->getDirectoryEntriesRecursive($path, "/.*/", false, false); 402 foreach ($fileNames as $fileName) { 403 $location = $this->yellow->lookup->findSystemLocationFromFile($fileName); 404 if (!is_string_empty($location)) array_push($locations, $location); 405 } 406 $path = $this->yellow->system->get("coreThemeDirectory"); 407 $fileNames = $this->yellow->toolbox->getDirectoryEntriesRecursive($path, "/.*/", false, false); 408 foreach ($fileNames as $fileName) { 409 $location = $this->yellow->lookup->findSystemLocationFromFile($fileName); 410 if (!is_string_empty($location)) array_push($locations, $location); 411 } 412 $path = $this->yellow->system->get("coreLayoutDirectory"); 413 $fileNames = $this->yellow->toolbox->getDirectoryEntriesRecursive($path, "/.*/", false, false); 414 foreach ($fileNames as $fileName) { 415 $location = $this->yellow->lookup->findSystemLocationFromFile($fileName); 416 if (!is_string_empty($location)) array_push($locations, $location); 417 } 418 $regexIgnore = "#^(/". 419 $this->yellow->system->get("coreContentDirectory")."|/". 420 $this->yellow->system->get("coreMediaDirectory")."|/". 421 $this->yellow->system->get("coreSystemDirectory")."|/". 422 $this->yellow->system->get("generateStaticDirectory")."|/$pathIgnore/|/yellow.php)#"; 423 $fileNames = $this->yellow->toolbox->getDirectoryEntriesRecursive(".", "/.*/", false, false); 424 foreach ($fileNames as $fileName) { 425 $location = substru($fileName, 1); 426 if (!preg_match($regexIgnore, $location)) array_push($locations, $location); 427 } 428 return array_diff($locations, $this->getSystemLocationsIgnore()); 429 } 430 431 // Return system locations to ignore 432 public function getSystemLocationsIgnore() { 433 $locations = array(); 434 $path = $this->yellow->system->get("coreWorkerDirectory"); 435 $workerDirectoryLength = strlenu($this->yellow->system->get("coreWorkerDirectory")); 436 if ($this->yellow->extension->isExisting("bundle")) { 437 foreach ($this->yellow->toolbox->getDirectoryEntries($path, "/^bundle-(.*)/", false, false) as $entry) { 438 list($locationsBundle) = $this->yellow->extension->get("bundle")->getBundleInformation($entry); 439 $locations = array_merge($locations, $locationsBundle); 440 } 441 } 442 if ($this->yellow->extension->isExisting("edit")) { 443 foreach ($this->yellow->toolbox->getDirectoryEntries($path, "/^edit(\-|\.)(.*)/", false, false) as $entry) { 444 $location = $this->yellow->system->get("coreAssetLocation").substru($entry, $workerDirectoryLength); 445 array_push($locations, $location); 446 } 447 } 448 return array_unique($locations); 449 } 450 }