update.php (48615B)
1 <?php 2 // Update extension, https://github.com/annaesvensson/yellow-update 3 4 class YellowUpdate { 5 const VERSION = "0.9.10"; 6 const PRIORITY = "2"; 7 public $yellow; // access to API 8 public $extensions; // number of extensions 9 10 // Handle initialisation 11 public function onLoad($yellow) { 12 $this->yellow = $yellow; 13 $this->yellow->system->setDefault("updateCurrentRelease", "none"); 14 $this->yellow->system->setDefault("updateAvailableUrl", "auto"); 15 $this->yellow->system->setDefault("updateAvailableFile", "update-available.ini"); 16 $this->yellow->system->setDefault("updateInstalledFile", "update-installed.ini"); 17 $this->yellow->system->setDefault("updateExtensionFile", "extension.ini"); 18 $this->yellow->system->setDefault("updateEventPending", "none"); 19 $this->yellow->system->setDefault("updateEventDaily", "0"); 20 $this->yellow->system->setDefault("updateTrashTimeout", "7776660"); 21 } 22 23 // Handle update 24 public function onUpdate($action) { 25 if ($action=="clean" || $action=="daily") { 26 $statusCode = 200; 27 $path = $this->yellow->system->get("coreExtensionDirectory"); 28 foreach ($this->yellow->toolbox->getDirectoryEntries($path, "/^.*\.download$/", false, false) as $entry) { 29 if (!$this->yellow->toolbox->deleteFile($entry)) $statusCode = 500; 30 } 31 if ($statusCode==500) $this->yellow->toolbox->log("error", "Can't delete files in directory '$path'!"); 32 $statusCode = 200; 33 $path = $this->yellow->system->get("coreTrashDirectory"); 34 foreach ($this->yellow->toolbox->getDirectoryEntries($path, "/.*/", false, false) as $entry) { 35 $expire = $this->yellow->toolbox->getFileDeleted($entry) + $this->yellow->system->get("updateTrashTimeout"); 36 if ($expire<=time() && !$this->yellow->toolbox->deleteFile($entry)) $statusCode = 500; 37 } 38 foreach ($this->yellow->toolbox->getDirectoryEntries($path, "/.*/", false, true) as $entry) { 39 $expire = $this->yellow->toolbox->getFileDeleted($entry) + $this->yellow->system->get("updateTrashTimeout"); 40 if ($expire<=time() && !$this->yellow->toolbox->deleteDirectory($entry)) $statusCode = 500; 41 } 42 if ($statusCode==500) $this->yellow->toolbox->log("error", "Can't delete files in directory '$path'!"); 43 } 44 } 45 46 // Handle request 47 public function onRequest($scheme, $address, $base, $location, $fileName) { 48 return $this->processRequestPending($scheme, $address, $base, $location, $fileName); 49 } 50 51 // Handle command 52 public function onCommand($command, $text) { 53 $statusCode = $this->processCommandPending(); 54 if ($statusCode==0) { 55 switch ($command) { 56 case "about": $statusCode = $this->processCommandAbout($command, $text); break; 57 case "install": $statusCode = $this->processCommandInstall($command, $text); break; 58 case "uninstall": $statusCode = $this->processCommandUninstall($command, $text); break; 59 case "update": $statusCode = $this->processCommandUpdate($command, $text); break; 60 default: $statusCode = 0; break; 61 } 62 } 63 return $statusCode; 64 } 65 66 // Handle command help 67 public function onCommandHelp() { 68 return array("about [extension]", "install [extension]", "uninstall [extension]", "update [extension]"); 69 } 70 71 // Handle page content element 72 public function onParseContentElement($page, $name, $text, $attributes, $type) { 73 $output = null; 74 if ($name=="about" && $type=="inline") { 75 list($dummy, $settingsCurrent) = $this->getExtensionSettings(true); 76 $output = "Datenstrom Yellow ".YellowCore::RELEASE."<br />\n"; 77 foreach ($settingsCurrent as $key=>$value) { 78 $output .= ucfirst($key)." ".$value->get("version")."<br />\n"; 79 } 80 } 81 return $output; 82 } 83 84 // Process command to show current version 85 public function processCommandAbout($command, $text) { 86 $statusCode = 200; 87 $extensions = $this->getExtensionsFromText($text); 88 if (!is_array_empty($extensions)) { 89 list($statusCode, $settings) = $this->getExtensionAboutInformation($extensions); 90 if ($statusCode==200) { 91 foreach ($settings as $key=>$value) { 92 echo ucfirst($key)." ".$value->get("version")." - ".$this->getExtensionDescription($key, $value)."\n"; 93 if ($value->isExisting("documentationUrl")) echo "Read more at ".$value->get("documentationUrl")."\n"; 94 } 95 } 96 if ($statusCode>=400) echo "ERROR checking extensions: ".$this->yellow->page->errorMessage."\n"; 97 } else { 98 echo "Datenstrom Yellow ".YellowCore::RELEASE."\n"; 99 list($statusCode, $settingsCurrent) = $this->getExtensionSettings(true); 100 foreach ($settingsCurrent as $key=>$value) { 101 echo ucfirst($key)." ".$value->get("version")."\n"; 102 } 103 } 104 return $statusCode; 105 } 106 107 // Process command to install extensions 108 public function processCommandInstall($command, $text) { 109 $extensions = $this->getExtensionsFromText($text); 110 if (!is_array_empty($extensions)) { 111 $this->extensions = 0; 112 list($statusCode, $settings) = $this->getExtensionInstallInformation($extensions); 113 if ($statusCode==200) $statusCode = $this->downloadExtensions($settings); 114 if ($statusCode==200) $statusCode = $this->updateExtensions("install"); 115 if ($statusCode>=400) echo "ERROR installing files: ".$this->yellow->page->errorMessage."\n"; 116 echo "Yellow $command: Website ".($statusCode!=200 ? "not " : "")."updated"; 117 echo ", $this->extensions extension".($this->extensions!=1 ? "s" : "")." installed\n"; 118 } else { 119 list($statusCode, $settingsAvailable) = $this->getExtensionSettings(false); 120 foreach ($settingsAvailable as $key=>$value) { 121 echo ucfirst($key)." - ".$this->getExtensionDescription($key, $value)."\n"; 122 } 123 if ($statusCode!=200) echo "ERROR checking extensions: ".$this->yellow->page->errorMessage."\n"; 124 } 125 return $statusCode; 126 } 127 128 // Process command to uninstall extensions 129 public function processCommandUninstall($command, $text) { 130 $extensions = $this->getExtensionsFromText($text); 131 if (!is_array_empty($extensions)) { 132 $this->extensions = 0; 133 list($statusCode, $settings) = $this->getExtensionUninstallInformation($extensions, "core, update"); 134 if ($statusCode==200) $statusCode = $this->removeExtensions($settings); 135 if ($statusCode>=400) echo "ERROR uninstalling files: ".$this->yellow->page->errorMessage."\n"; 136 echo "Yellow $command: Website ".($statusCode!=200 ? "not " : "")."updated"; 137 echo ", $this->extensions extension".($this->extensions!=1 ? "s" : "")." uninstalled\n"; 138 } else { 139 list($statusCode, $settingsCurrent) = $this->getExtensionSettings(true); 140 foreach ($settingsCurrent as $key=>$value) { 141 echo ucfirst($key)." - ".$this->getExtensionDescription($key, $value)."\n"; 142 } 143 if ($statusCode!=200) echo "ERROR checking extensions: ".$this->yellow->page->errorMessage."\n"; 144 } 145 return $statusCode; 146 } 147 148 // Process command to update website 149 public function processCommandUpdate($command, $text) { 150 $extensions = $this->getExtensionsFromText($text); 151 if (!is_array_empty($extensions)) { 152 list($statusCode, $settings) = $this->getExtensionUpdateInformation($extensions); 153 if ($statusCode!=200 || !is_array_empty($settings)) { 154 $this->extensions = 0; 155 if ($statusCode==200) $statusCode = $this->downloadExtensions($settings); 156 if ($statusCode==200) $statusCode = $this->updateExtensions("update"); 157 if ($statusCode>=400) echo "ERROR updating files: ".$this->yellow->page->errorMessage."\n"; 158 echo "Yellow $command: Website ".($statusCode!=200 ? "not " : "")."updated"; 159 echo ", $this->extensions extension".($this->extensions!=1 ? "s" : "")." updated\n"; 160 } else { 161 echo "Your website is up to date\n"; 162 } 163 } else { 164 list($statusCode, $settings) = $this->getExtensionUpdateInformation(array("all")); 165 if (!is_array_empty($settings)) { 166 foreach ($settings as $key=>$value) { 167 echo ucfirst($key)." ".$value->get("version")."\n"; 168 } 169 echo "Yellow $command: Updates are available. Please type 'php yellow.php update all'.\n"; 170 } elseif ($statusCode!=200) { 171 echo "ERROR updating files: ".$this->yellow->page->errorMessage."\n"; 172 } else { 173 echo "Your website is up to date\n"; 174 } 175 } 176 return $statusCode; 177 } 178 179 // Process command for pending events 180 public function processCommandPending() { 181 $statusCode = 0; 182 $this->extensions = 0; 183 $this->updatePatchPending(); 184 $this->updateEventPending(); 185 $statusCode = $this->updateExtensionPending(); 186 if ($statusCode==303) { 187 echo "Yellow detected ZIP file".($this->extensions!=1 ? "s" : ""); 188 echo ", $this->extensions extension".($this->extensions!=1 ? "s" : "")." installed. Please run command again.\n"; 189 } 190 return $statusCode; 191 } 192 193 // Process request for pending events 194 public function processRequestPending($scheme, $address, $base, $location, $fileName) { 195 $statusCode = 0; 196 if ($this->yellow->lookup->isContentFile($fileName)) { 197 $this->updatePatchPending(); 198 $this->updateEventPending(); 199 $statusCode = $this->updateExtensionPending(); 200 if ($statusCode==303) { 201 $location = $this->yellow->lookup->normaliseUrl($scheme, $address, $base, $location); 202 $statusCode = $this->yellow->sendStatus(303, $location); 203 } 204 } 205 return $statusCode; 206 } 207 208 // Download extensions 209 public function downloadExtensions($settings) { 210 $statusCode = 200; 211 $path = $this->yellow->system->get("coreExtensionDirectory"); 212 foreach ($settings as $key=>$value) { 213 $fileName = $path.$this->yellow->lookup->normaliseName($key, true, false, true).".zip"; 214 list($statusCode, $fileData) = $this->getExtensionFile($value->get("downloadUrl")); 215 if ($statusCode==200 && !$this->yellow->toolbox->writeFile($fileName.".download", $fileData)) { 216 $statusCode = 500; 217 $this->yellow->page->error($statusCode, "Can't write file '$fileName'!"); 218 } 219 if ($statusCode!=200) break; 220 } 221 if ($statusCode==200) { 222 foreach ($settings as $key=>$value) { 223 $fileName = $path.$this->yellow->lookup->normaliseName($key, true, false, true).".zip"; 224 if (!$this->yellow->toolbox->renameFile($fileName.".download", $fileName)) { 225 $statusCode = 500; 226 $this->yellow->page->error($statusCode, "Can't write file '$fileName'!"); 227 } 228 } 229 } 230 return $statusCode; 231 } 232 233 // Update extensions 234 public function updateExtensions($action) { 235 $statusCode = 200; 236 if (function_exists("opcache_reset")) opcache_reset(); 237 $this->yellow->page->setHeader("Clear-Site-Data", "cache"); 238 $path = $this->yellow->system->get("coreExtensionDirectory"); 239 foreach ($this->yellow->toolbox->getDirectoryEntries($path, "/^.*\.zip$/", true, false) as $entry) { 240 $statusCode = max($statusCode, $this->updateExtensionArchive($entry, $action)); 241 if (!$this->yellow->toolbox->deleteFile($entry)) { 242 $statusCode = 500; 243 $this->yellow->page->error($statusCode, "Can't delete file '$entry'!"); 244 } 245 } 246 return $statusCode; 247 } 248 249 // Update extension from archive 250 public function updateExtensionArchive($path, $action) { 251 $statusCode = 200; 252 $zip = new ZipArchive(); 253 if ($zip->open($path)===true) { 254 if ($this->yellow->system->get("coreDebugMode")>=2) echo "YellowUpdate::updateExtensionArchive file:$path<br />\n"; 255 $pathBase = ""; 256 if (preg_match("#^(.*\/).*?$#", $zip->getNameIndex(0), $matches)) $pathBase = $matches[1]; 257 $fileData = $zip->getFromName($pathBase.$this->yellow->system->get("updateExtensionFile")); 258 $settings = $this->yellow->toolbox->getTextSettings($fileData, ""); 259 list($extension, $version, $newModified, $oldModified) = $this->getExtensionInformation($settings); 260 if (!is_string_empty($extension) && !is_string_empty($version)) { 261 $statusCode = max($statusCode, $this->updateExtensionSettings($extension, $action, $fileData)); 262 $paths = $this->getExtensionDirectories($zip, $pathBase); 263 foreach ($this->getExtensionFileNames($settings) as $fileName) { 264 list($entry, $flags) = $this->yellow->toolbox->getTextList($settings[$fileName], ",", 2); 265 if (!$this->yellow->lookup->isContentFile($fileName)) { 266 $fileNameSource = $pathBase.$entry; 267 $fileData = $zip->getFromName($fileNameSource); 268 $lastModified = $this->yellow->toolbox->getFileModified($fileName); 269 $statusCode = max($statusCode, $this->updateExtensionFile($fileName, $fileData, 270 $newModified, $oldModified, $lastModified, $flags, $extension)); 271 } else { 272 foreach ($this->getExtensionContentRootPages() as $page) { 273 list($fileNameSource, $fileNameDestination) = $this->getExtensionContentFileNames( 274 $fileName, $pathBase, $entry, $flags, $paths, $page); 275 $fileData = $zip->getFromName($fileNameSource); 276 $lastModified = $this->yellow->toolbox->getFileModified($fileNameDestination); 277 $statusCode = max($statusCode, $this->updateExtensionFile($fileNameDestination, $fileData, 278 $newModified, $oldModified, $lastModified, $flags, $extension)); 279 } 280 } 281 } 282 $statusCode = max($statusCode, $this->updateExtensionNotification($extension, $action)); 283 $this->yellow->toolbox->log($statusCode==200 ? "info" : "error", ucfirst($action)." extension '".ucfirst($extension)." $version'"); 284 ++$this->extensions; 285 } else { 286 $statusCode = 500; 287 $this->yellow->page->error($statusCode, "Can't detect file '$path'!"); 288 } 289 $zip->close(); 290 } else { 291 $statusCode = 500; 292 $this->yellow->page->error($statusCode, "Can't open file '$path'!"); 293 } 294 return $statusCode; 295 } 296 297 // Update extension from file 298 public function updateExtensionFile($fileName, $fileData, $newModified, $oldModified, $lastModified, $flags, $extension) { 299 $statusCode = 200; 300 $fileName = $this->yellow->lookup->normalisePath($fileName); 301 if ($this->yellow->lookup->isValidFile($fileName)) { 302 $create = $update = $delete = false; 303 if (preg_match("/create/i", $flags) && !is_file($fileName) && !is_string_empty($fileData)) $create = true; 304 if (preg_match("/update/i", $flags) && is_file($fileName) && !is_string_empty($fileData)) $update = true; 305 if (preg_match("/delete/i", $flags) && is_file($fileName)) $delete = true; 306 if (preg_match("/optional/i", $flags) && $this->yellow->extension->isExisting($extension)) $create = $update = $delete = false; 307 if (preg_match("/careful/i", $flags) && is_file($fileName) && $lastModified!=$oldModified) $update = false; 308 if ($create) { 309 if (!$this->yellow->toolbox->writeFile($fileName, $fileData, true) || 310 !$this->yellow->toolbox->modifyFile($fileName, $newModified)) { 311 $statusCode = 500; 312 $this->yellow->page->error($statusCode, "Can't write file '$fileName'!"); 313 } 314 } 315 if ($update) { 316 if (!$this->yellow->toolbox->deleteFile($fileName, $this->yellow->system->get("coreTrashDirectory")) || 317 !$this->yellow->toolbox->writeFile($fileName, $fileData) || 318 !$this->yellow->toolbox->modifyFile($fileName, $newModified)) { 319 $statusCode = 500; 320 $this->yellow->page->error($statusCode, "Can't write file '$fileName'!"); 321 } 322 } 323 if ($delete) { 324 if (!$this->yellow->toolbox->deleteFile($fileName, $this->yellow->system->get("coreTrashDirectory"))) { 325 $statusCode = 500; 326 $this->yellow->page->error($statusCode, "Can't delete file '$fileName'!"); 327 } 328 } 329 if ($this->yellow->system->get("coreDebugMode")>=2) { 330 $debug = "action:".($create ? "create" : "").($update ? "update" : "").($delete ? "delete" : ""); 331 if (!$create && !$update && !$delete) $debug = "action:none"; 332 echo "YellowUpdate::updateExtensionFile file:$fileName $debug<br />\n"; 333 } 334 } 335 return $statusCode; 336 } 337 338 // Update pending patches 339 public function updatePatchPending() { 340 $fileName = $this->yellow->system->get("coreWorkerDirectory")."updatepatch.bin"; 341 if (is_file($fileName)) { 342 if ($this->yellow->system->get("coreDebugMode")>=2) echo "YellowUpdate::updatePatchPending file:$fileName<br />\n"; 343 if (!$this->yellow->extension->isExisting("updatepatch")) { 344 require_once($fileName); 345 $this->yellow->extension->register("updatepatch", "YellowUpdatePatch"); 346 } 347 if ($this->yellow->extension->isExisting("updatepatch")) { 348 $value = $this->yellow->extension->data["updatepatch"]; 349 if (method_exists($value["object"], "onLoad")) $value["object"]->onLoad($this->yellow); 350 if (method_exists($value["object"], "onUpdate")) $value["object"]->onUpdate("patch"); 351 } 352 unset($this->yellow->extension->data["updatepatch"]); 353 if (function_exists("opcache_reset")) opcache_reset(); 354 $this->yellow->page->setHeader("Clear-Site-Data", "cache"); 355 if (!$this->yellow->toolbox->deleteFile($fileName)) { 356 $this->yellow->toolbox->log("error", "Can't delete file '$fileName'!"); 357 } 358 } 359 } 360 361 // Update pending events 362 public function updateEventPending() { 363 if ($this->yellow->system->get("updateCurrentRelease")!="none") { 364 if ($this->yellow->system->get("updateCurrentRelease")!=YellowCore::RELEASE) { 365 $fileName = $this->yellow->system->get("coreExtensionDirectory").$this->yellow->system->get("coreSystemFile"); 366 if (!$this->yellow->system->save($fileName, array("updateCurrentRelease" => YellowCore::RELEASE))) { 367 $this->yellow->toolbox->log("error", "Can't write file '$fileName'!"); 368 } else { 369 list($name, $version, $os) = $this->yellow->toolbox->detectServerInformation(); 370 $product = "Datenstrom Yellow ".YellowCore::RELEASE; 371 $this->yellow->toolbox->log("info", "Update $product, PHP ".PHP_VERSION.", $name $version, $os"); 372 } 373 } 374 if ($this->yellow->system->get("updateEventPending")!="none") { 375 foreach (preg_split("/\s*,\s*/", $this->yellow->system->get("updateEventPending")) as $token) { 376 list($extension, $action) = $this->yellow->toolbox->getTextList($token, "/", 2); 377 if ($action!="uninstall") { 378 $this->updateSystemSettings($extension, $action); 379 $this->updateLanguageSettings($extension, $action); 380 if ($this->yellow->extension->isExisting($extension)) { 381 $value = $this->yellow->extension->data[$extension]; 382 if (method_exists($value["object"], "onUpdate")) $value["object"]->onUpdate($action); 383 } 384 } 385 } 386 $fileName = $this->yellow->system->get("coreExtensionDirectory").$this->yellow->system->get("coreSystemFile"); 387 if (!$this->yellow->system->save($fileName, array("updateEventPending" => "none"))) { 388 $this->yellow->toolbox->log("error", "Can't write file '$fileName'!"); 389 } 390 } 391 if ($this->yellow->system->get("updateEventDaily")<=time()) { 392 foreach ($this->yellow->extension->data as $key=>$value) { 393 if (method_exists($value["object"], "onUpdate")) $value["object"]->onUpdate("daily"); 394 } 395 $fileName = $this->yellow->system->get("coreExtensionDirectory").$this->yellow->system->get("coreSystemFile"); 396 if (!$this->yellow->system->save($fileName, array("updateEventDaily" => $this->getTimestampDaily()))) { 397 $this->yellow->toolbox->log("error", "Can't write file '$fileName'!"); 398 } 399 } 400 } 401 } 402 403 // Update pending extensions 404 public function updateExtensionPending() { 405 $statusCode = 0; 406 $path = $this->yellow->system->get("coreExtensionDirectory"); 407 if (!is_array_empty($this->yellow->toolbox->getDirectoryEntries($path, "/^.*\.zip$/", false, false))) { 408 $statusCode = $this->updateExtensions("install"); 409 if ($statusCode==200) $statusCode = 303; 410 if ($statusCode>=400) { 411 $this->yellow->toolbox->log("error", $this->yellow->page->errorMessage); 412 $this->yellow->page->statusCode = 0; 413 $this->yellow->page->errorMessage = ""; 414 $statusCode = 303; 415 } 416 } 417 return $statusCode; 418 } 419 420 // Update extension settings 421 public function updateExtensionSettings($extension, $action, $text = "") { 422 $statusCode = 200; 423 $fileName = $this->yellow->system->get("coreExtensionDirectory").$this->yellow->system->get("updateInstalledFile"); 424 $fileData = $fileDataNew = $this->yellow->toolbox->readFile($fileName); 425 if ($action=="install" || $action=="update") { 426 $settingsCurrent = $this->yellow->toolbox->getTextSettings($fileData, "extension"); 427 $settingsCurrent[$extension] = new YellowArray(); 428 $block = $this->yellow->toolbox->getTextSettings($text, ""); 429 foreach ($block as $key=>$value) $settingsCurrent[$extension][$key] = $value; 430 $settingsCurrent->uksort("strnatcasecmp"); 431 $fileDataNew = ""; 432 foreach ($this->yellow->toolbox->getTextLines($fileData) as $line) { 433 if (preg_match("/^\#/", $line)) $fileDataNew = $line; 434 break; 435 } 436 foreach ($settingsCurrent as $extension=>$block) { 437 if (!is_string_empty($fileDataNew)) $fileDataNew .= "\n"; 438 foreach ($block as $key=>$value) { 439 $fileDataNew .= (strposu($key, "/") ? $key : ucfirst($key)).": $value\n"; 440 } 441 } 442 } elseif ($action=="uninstall") { 443 $fileDataNew = $this->yellow->toolbox->unsetTextSettings($fileData, "extension", $extension); 444 } 445 if ($fileData!=$fileDataNew && !$this->yellow->toolbox->writeFile($fileName, $fileDataNew)) { 446 $statusCode = 500; 447 $this->yellow->page->error($statusCode, "Can't write file '$fileName'!"); 448 } 449 return $statusCode; 450 } 451 452 // Update system settings 453 public function updateSystemSettings($extension, $action) { 454 $statusCode = 200; 455 $fileName = $this->yellow->system->get("coreExtensionDirectory").$this->yellow->system->get("coreSystemFile"); 456 $fileData = $fileDataNew = $this->yellow->toolbox->readFile($fileName); 457 if (!is_string_empty($extension)) { 458 $regex = "/^".ucfirst($extension)."[A-Z]+/"; 459 if ($action=="install" || $action=="update") { 460 $fileDataStart = $fileDataSettings = ""; 461 $settings = new YellowArray(); 462 $settings->exchangeArray($this->yellow->system->settingsDefaults->getArrayCopy()); 463 foreach ($this->yellow->toolbox->getTextLines($fileData) as $line) { 464 if (preg_match("/^\#/", $line)) { 465 if (is_string_empty($fileDataStart)) $fileDataStart = $line."\n"; 466 continue; 467 } 468 if (preg_match("/^\s*(.*?)\s*:\s*(.*?)\s*$/", $line, $matches)) { 469 if (!is_string_empty($matches[1]) && !is_string_empty($matches[2])) { 470 if (!preg_match($regex, $matches[1]) || $settings->isExisting($matches[1])) { 471 $settings[$matches[1]] = $matches[2]; 472 } 473 } 474 } 475 } 476 foreach ($settings as $key=>$value) { 477 $fileDataSettings .= ucfirst($key).(is_string_empty($value) ? ":\n" : ": $value\n"); 478 } 479 $fileDataNew = $fileDataStart.$fileDataSettings; 480 } elseif ($action=="uninstall") { 481 $fileDataNew = ""; 482 foreach ($this->yellow->toolbox->getTextLines($fileData) as $line) { 483 if (preg_match("/^\s*(.*?)\s*:\s*(.*?)\s*$/", $line, $matches)) { 484 if (!is_string_empty($matches[1]) && preg_match($regex, $matches[1])) continue; 485 } 486 $fileDataNew .= $line; 487 } 488 } 489 } 490 if ($fileData!=$fileDataNew && !$this->yellow->toolbox->writeFile($fileName, $fileDataNew)) { 491 $statusCode = 500; 492 $this->yellow->page->error($statusCode, "Can't write file '$fileName'!"); 493 } 494 return $statusCode; 495 } 496 497 // Update language settings 498 public function updateLanguageSettings($extension, $action) { 499 $statusCode = 200; 500 $fileName = $this->yellow->system->get("coreExtensionDirectory").$this->yellow->system->get("coreLanguageFile"); 501 $fileData = $fileDataNew = $this->yellow->toolbox->readFile($fileName); 502 if (!is_string_empty($extension) && ucfirst($extension)!="Language") { 503 $regex = "/^".ucfirst($extension)."[A-Z]+/"; 504 if ($action=="install" || $action=="update") { 505 $fileDataStart = $fileDataSettings = $language = ""; 506 $settings = new YellowArray(); 507 foreach ($this->yellow->toolbox->getTextLines($fileData) as $line) { 508 if (preg_match("/^\#/", $line)) { 509 if (is_string_empty($fileDataStart)) $fileDataStart = $line."\n"; 510 continue; 511 } 512 if (preg_match("/^\s*(.*?)\s*:\s*(.*?)\s*$/", $line, $matches)) { 513 if (!is_string_empty($matches[1]) && !is_string_empty($matches[2])) { 514 if (lcfirst($matches[1])=="language") { 515 if (!is_array_empty($settings)) { 516 if (!is_string_empty($fileDataSettings)) $fileDataSettings .= "\n"; 517 foreach ($settings as $key=>$value) { 518 $fileDataSettings .= (strposu($key, "/") ? $key : ucfirst($key)).": $value\n"; 519 } 520 } 521 $language = $matches[2]; 522 $settings = new YellowArray(); 523 $settings["language"] = $language; 524 $settings["languageLocale"] = "n/a"; 525 $settings["languageDescription"] = "n/a"; 526 $settings["languageTranslator"] = "Unknown"; 527 foreach ($this->yellow->language->settingsDefaults as $key=>$value) { 528 $require = preg_match("/^([a-z]*)[A-Z]+/", $key, $tokens) ? $tokens[1] : "core"; 529 if ($require=="language") $require = "core"; 530 if ($this->yellow->extension->isExisting($require)) { 531 if ($this->yellow->language->isText($key, $language)) { 532 $settings[$key] = $this->yellow->language->getText($key, $language); 533 } else { 534 $settings[$key] = $this->yellow->language->getText($key, "en"); 535 } 536 } 537 } 538 } 539 if (!is_string_empty($language)) { 540 if (!preg_match($regex, $matches[1]) || $settings->isExisting($matches[1])) { 541 $settings[$matches[1]] = $matches[2]; 542 } 543 } 544 } 545 } 546 } 547 if (!is_array_empty($settings)) { 548 if (!is_string_empty($fileDataSettings)) $fileDataSettings .= "\n"; 549 foreach ($settings as $key=>$value) { 550 $fileDataSettings .= (strposu($key, "/") ? $key : ucfirst($key)).": $value\n"; 551 } 552 } 553 $fileDataNew = $fileDataStart.$fileDataSettings; 554 } elseif ($action=="uninstall") { 555 $fileDataNew = ""; 556 foreach ($this->yellow->toolbox->getTextLines($fileData) as $line) { 557 if (preg_match("/^\s*(.*?)\s*:\s*(.*?)\s*$/", $line, $matches)) { 558 if (!is_string_empty($matches[1]) && preg_match($regex, $matches[1])) continue; 559 } 560 $fileDataNew .= $line; 561 } 562 } 563 } 564 if ($fileData!=$fileDataNew && !$this->yellow->toolbox->writeFile($fileName, $fileDataNew)) { 565 $statusCode = 500; 566 $this->yellow->page->error($statusCode, "Can't write file '$fileName'!"); 567 } 568 return $statusCode; 569 } 570 571 // Update extension notification 572 public function updateExtensionNotification($extension, $action) { 573 $statusCode = 200; 574 if ($this->yellow->extension->isExisting($extension) && $action=="uninstall") { 575 $value = $this->yellow->extension->data[$extension]; 576 if (method_exists($value["object"], "onUpdate")) $value["object"]->onUpdate($action); 577 } 578 $updateEventPending = $this->yellow->system->get("updateEventPending"); 579 if ($updateEventPending=="none") $updateEventPending = ""; 580 if (!is_string_empty($updateEventPending)) $updateEventPending .= ","; 581 $updateEventPending .= "$extension/$action"; 582 $fileName = $this->yellow->system->get("coreExtensionDirectory").$this->yellow->system->get("coreSystemFile"); 583 if (!$this->yellow->system->save($fileName, array("updateEventPending" => $updateEventPending))) { 584 $statusCode = 500; 585 $this->yellow->page->error($statusCode, "Can't write file '$fileName'!"); 586 } 587 return $statusCode; 588 } 589 590 // Remove extensions 591 public function removeExtensions($settings) { 592 $statusCode = 200; 593 if (function_exists("opcache_reset")) opcache_reset(); 594 $this->yellow->page->setHeader("Clear-Site-Data", "cache"); 595 foreach ($settings as $extension=>$block) { 596 $statusCode = max($statusCode, $this->removeExtensionArchive($extension, "uninstall", $block)); 597 } 598 return $statusCode; 599 } 600 601 // Remove extension archive 602 public function removeExtensionArchive($extension, $action, $settings) { 603 $statusCode = 200; 604 $fileNames = $this->getExtensionFileNames($settings, true); 605 if (!is_array_empty($fileNames)) { 606 $statusCode = max($statusCode, $this->updateExtensionNotification($extension, $action)); 607 foreach ($fileNames as $fileName) { 608 $statusCode = max($statusCode, $this->removeExtensionFile($fileName)); 609 } 610 if ($statusCode==200) { 611 $statusCode = max($statusCode, $this->updateExtensionSettings($extension, $action)); 612 $statusCode = max($statusCode, $this->updateSystemSettings($extension, $action)); 613 $statusCode = max($statusCode, $this->updateLanguageSettings($extension, $action)); 614 } 615 $version = $settings->get("version"); 616 $this->yellow->toolbox->log($statusCode==200 ? "info" : "error", ucfirst($action)." extension '".ucfirst($extension)." $version'"); 617 ++$this->extensions; 618 } else { 619 $statusCode = 500; 620 $this->yellow->page->error($statusCode, "Please delete extension '$extension' manually!"); 621 } 622 return $statusCode; 623 } 624 625 // Remove extension file 626 public function removeExtensionFile($fileName) { 627 $statusCode = 200; 628 $fileName = $this->yellow->lookup->normalisePath($fileName); 629 if ($this->yellow->lookup->isValidFile($fileName) && is_file($fileName)) { 630 if (!$this->yellow->toolbox->deleteFile($fileName, $this->yellow->system->get("coreTrashDirectory"))) { 631 $statusCode = 500; 632 $this->yellow->page->error($statusCode, "Can't delete file '$fileName'!"); 633 } 634 if ($this->yellow->system->get("coreDebugMode")>=2) { 635 echo "YellowUpdate::removeExtensionFile file:$fileName action:delete<br />\n"; 636 } 637 } 638 return $statusCode; 639 } 640 641 // Return extensions from text, space separated 642 public function getExtensionsFromText($text) { 643 return array_unique(array_filter($this->yellow->toolbox->getTextArguments($text), "strlen")); 644 } 645 646 // Return extension about information 647 public function getExtensionAboutInformation($extensions) { 648 $settings = array(); 649 list($statusCode, $settingsCurrent) = $this->getExtensionSettings(true); 650 $settingsCurrent["Datenstrom Yellow"] = new YellowArray(); 651 $settingsCurrent["Datenstrom Yellow"]["version"] = YellowCore::RELEASE; 652 $settingsCurrent["Datenstrom Yellow"]["description"] = "Datenstrom Yellow is for people who make small websites."; 653 $settingsCurrent["Datenstrom Yellow"]["documentationUrl"] = "https://datenstrom.se/yellow/"; 654 foreach ($extensions as $extension) { 655 $found = false; 656 if (strtoloweru($extension)=="yellow") $extension = "Datenstrom Yellow"; 657 foreach ($settingsCurrent as $key=>$value) { 658 if (strtoloweru($key)==strtoloweru($extension)) { 659 $settings[$key] = $settingsCurrent[$key]; 660 $found = true; 661 break; 662 } 663 } 664 if (!$found) { 665 $statusCode = 500; 666 $this->yellow->page->error($statusCode, "Can't find extension '$extension'!"); 667 } 668 } 669 return array($statusCode, $settings); 670 } 671 672 // Return extension install information 673 public function getExtensionInstallInformation($extensions) { 674 $settings = array(); 675 list($statusCodeCurrent, $settingsCurrent) = $this->getExtensionSettings(true); 676 list($statusCodeAvailable, $settingsAvailable) = $this->getExtensionSettings(false); 677 $statusCode = max($statusCodeCurrent, $statusCodeAvailable); 678 foreach ($extensions as $extension) { 679 $found = false; 680 foreach ($settingsAvailable as $key=>$value) { 681 if (strtoloweru($key)==strtoloweru($extension)) { 682 if (!$settingsCurrent->isExisting($key)) $settings[$key] = $settingsAvailable[$key]; 683 $found = true; 684 break; 685 } 686 } 687 if (!$found) { 688 $statusCode = 500; 689 $this->yellow->page->error($statusCode, "Can't find extension '$extension'!"); 690 } 691 } 692 return array($statusCode, $settings); 693 } 694 695 // Return extension about information 696 public function getExtensionUninstallInformation($extensions, $extensionsProtected = "") { 697 $settings = array(); 698 list($statusCode, $settingsCurrent) = $this->getExtensionSettings(true); 699 foreach ($extensions as $extension) { 700 $found = false; 701 foreach ($settingsCurrent as $key=>$value) { 702 if (strtoloweru($key)==strtoloweru($extension)) { 703 $settings[$key] = $settingsCurrent[$key]; 704 $found = true; 705 break; 706 } 707 } 708 if (!$found) { 709 $statusCode = 500; 710 $this->yellow->page->error($statusCode, "Can't find extension '$extension'!"); 711 } 712 } 713 $protected = preg_split("/\s*,\s*/", $extensionsProtected); 714 foreach ($settings as $key=>$value) { 715 if (in_array($key, $protected)) unset($settings[$key]); 716 } 717 return array($statusCode, $settings); 718 } 719 720 // Return extension update information 721 public function getExtensionUpdateInformation($extensions) { 722 $settings = array(); 723 list($statusCodeCurrent, $settingsCurrent) = $this->getExtensionSettings(true); 724 list($statusCodeAvailable, $settingsAvailable) = $this->getExtensionSettings(false); 725 $statusCode = max($statusCodeCurrent, $statusCodeAvailable); 726 if (in_array("all", $extensions)) { 727 foreach ($settingsCurrent as $key=>$value) { 728 if ($settingsAvailable->isExisting($key)) { 729 $versionCurrent = $settingsCurrent[$key]->get("version"); 730 $versionAvailable = $settingsAvailable[$key]->get("version"); 731 if (strnatcasecmp($versionCurrent, $versionAvailable)<0) { 732 $settings[$key] = $settingsAvailable[$key]; 733 } 734 } 735 } 736 } else { 737 foreach ($extensions as $extension) { 738 $found = false; 739 foreach ($settingsCurrent as $key=>$value) { 740 if (strtoloweru($key)==strtoloweru($extension) && $settingsAvailable->isExisting($key)) { 741 $versionCurrent = $settingsCurrent[$key]->get("version"); 742 $versionAvailable = $settingsAvailable[$key]->get("version"); 743 if (strnatcasecmp($versionCurrent, $versionAvailable)<0) { 744 $settings[$key] = $settingsAvailable[$key]; 745 } 746 $found = true; 747 break; 748 } 749 } 750 if (!$found) { 751 $statusCode = 500; 752 $this->yellow->page->error($statusCode, "Can't find extension '$extension'!"); 753 } 754 } 755 } 756 return array($statusCode, $settings); 757 } 758 759 // Return extension settings 760 public function getExtensionSettings($current) { 761 $statusCode = 200; 762 $settings = array(); 763 if ($current) { 764 $fileNameCurrent = $this->yellow->system->get("coreExtensionDirectory").$this->yellow->system->get("updateInstalledFile"); 765 $fileData = $this->yellow->toolbox->readFile($fileNameCurrent); 766 $settings = $this->yellow->toolbox->getTextSettings($fileData, "extension"); 767 foreach ($settings->getArrayCopy() as $key=>$value) { 768 if (!$this->yellow->extension->isExisting($key)) unset($settings[$key]); 769 } 770 foreach ($this->yellow->extension->data as $key=>$value) { 771 if (!$settings->isExisting($key)) $settings[$key] = new YellowArray(); 772 $settings[$key]["extension"] = ucfirst($key); 773 $settings[$key]["version"] = $value["version"]; 774 } 775 } else { 776 $fileNameAvailable = $this->yellow->system->get("coreExtensionDirectory").$this->yellow->system->get("updateAvailableFile"); 777 $expire = $this->yellow->toolbox->getFileModified($fileNameAvailable) + 60*10; 778 if ($expire<=time()) { 779 $url = $this->yellow->system->get("updateAvailableUrl"); 780 if ($url=="auto") $url = "https://raw.githubusercontent.com/datenstrom/yellow/main/system/extensions/update-available.ini"; 781 list($statusCode, $fileData) = $this->getExtensionFile($url); 782 if ($statusCode==200 && !$this->yellow->toolbox->writeFile($fileNameAvailable, $fileData)) { 783 $statusCode = 500; 784 $this->yellow->page->error($statusCode, "Can't write file '$fileNameAvailable'!"); 785 } 786 } 787 $fileData = $this->yellow->toolbox->readFile($fileNameAvailable); 788 $settings = $this->yellow->toolbox->getTextSettings($fileData, "extension"); 789 } 790 $settings->uksort("strnatcasecmp"); 791 return array($statusCode, $settings); 792 } 793 794 // Return extension information 795 public function getExtensionInformation($settings) { 796 $extension = lcfirst($settings->get("extension")); 797 $version = $settings->get("version"); 798 $newModified = strtotime($settings->get("published")); 799 $oldModified = 0; 800 $invalid = false; 801 foreach ($settings as $key=>$value) { 802 if (strposu($key, "/")) { 803 $fileName = $this->yellow->lookup->normalisePath($key); 804 if (!$this->yellow->lookup->isValidFile($fileName)) $invalid = true; 805 if ($oldModified==0) $oldModified = $this->yellow->toolbox->getFileModified($fileName); 806 } 807 } 808 if ($invalid) $extension = $version = ""; 809 return array($extension, $version, $newModified, $oldModified); 810 } 811 812 // Return extension directories 813 public function getExtensionDirectories($zip, $pathBase) { 814 $paths = array(); 815 for ($index=0; $index<$zip->numFiles; ++$index) { 816 $entry = substru($zip->getNameIndex($index), strlenu($pathBase)); 817 if (preg_match("#^(.*\/).*?$#", $entry, $matches)) { 818 array_push($paths, $matches[1]); 819 } 820 } 821 return array_unique($paths); 822 } 823 824 // Return extension file names 825 public function getExtensionFileNames($settings, $reverse = false) { 826 $fileNames = array(); 827 foreach ($settings as $key=>$value) { 828 if (strposu($key, "/")) array_push($fileNames, $key); 829 } 830 if ($reverse) $fileNames = array_reverse($fileNames); 831 return $fileNames; 832 } 833 834 // Return extension root pages for content files 835 public function getExtensionContentRootPages() { 836 return $this->yellow->content->scanLocation(""); 837 } 838 839 // Return extension files names for content files 840 public function getExtensionContentFileNames($fileName, $pathBase, $entry, $flags, $paths, $page) { 841 if (preg_match("/multi-language/i", $flags)) { 842 $pathMultiLanguage = ""; 843 $languagesWanted = array($page->get("language"), "en"); 844 foreach ($languagesWanted as $language) { 845 foreach ($paths as $path) { 846 if ($this->yellow->lookup->normaliseToken(rtrim($path, "/"))==$language) { 847 $pathMultiLanguage = $path; 848 break; 849 } 850 } 851 if (!is_string_empty($pathMultiLanguage)) break; 852 } 853 $fileNameSource = $pathBase.$pathMultiLanguage.$entry; 854 } else { 855 $fileNameSource = $pathBase.$entry; 856 } 857 if ($this->yellow->system->get("coreMultiLanguageMode")) { 858 $contentDirectoryLength = strlenu($this->yellow->system->get("coreContentDirectory")); 859 $fileNameDestination = $page->fileName.substru($fileName, $contentDirectoryLength); 860 } else { 861 $fileNameDestination = $fileName; 862 } 863 return array($fileNameSource, $fileNameDestination); 864 } 865 866 // Return extension description including responsible developer/designer/translator 867 public function getExtensionDescription($key, $value) { 868 $description = $responsible = ""; 869 if ($value->isExisting("description")) $description = $value->get("description"); 870 if ($value->isExisting("developer")) $responsible = "Developed by ".$value["developer"]."."; 871 if ($value->isExisting("designer")) $responsible = "Designed by ".$value["designer"]."."; 872 if ($value->isExisting("translator")) $responsible = "Translated by ".$value["translator"]."."; 873 if (is_string_empty($description)) $description = "No description available."; 874 return "$description $responsible"; 875 } 876 877 // Return extension file 878 public function getExtensionFile($url) { 879 $curlHandle = curl_init(); 880 curl_setopt($curlHandle, CURLOPT_URL, $this->getExtensionDownloadUrl($url)); 881 curl_setopt($curlHandle, CURLOPT_USERAGENT, "Mozilla/5.0 (compatible; YellowUpdate/".YellowUpdate::VERSION."; SoftwareUpdater)"); 882 curl_setopt($curlHandle, CURLOPT_RETURNTRANSFER, 1); 883 curl_setopt($curlHandle, CURLOPT_CONNECTTIMEOUT, 30); 884 $fileData = curl_exec($curlHandle); 885 $statusCode = curl_getinfo($curlHandle, CURLINFO_HTTP_CODE); 886 $redirectUrl = ($statusCode>=300 && $statusCode<=399) ? curl_getinfo($curlHandle, CURLINFO_REDIRECT_URL) : ""; 887 if (PHP_VERSION_ID<80000) curl_close($curlHandle); 888 if ($statusCode==0) { 889 $statusCode = 450; 890 $this->yellow->page->error($statusCode, "Can't connect to the update server!"); 891 } 892 if ($statusCode!=450 && $statusCode!=200) { 893 $statusCode = 500; 894 $this->yellow->page->error($statusCode, "Can't download file '$url'!"); 895 } 896 if ($this->yellow->system->get("coreDebugMode")>=2 && !is_string_empty($redirectUrl)) { 897 echo "YellowUpdate::getExtensionFile redirected to url:$redirectUrl<br />\n"; 898 } 899 if ($this->yellow->system->get("coreDebugMode")>=2) { 900 echo "YellowUpdate::getExtensionFile status:$statusCode url:$url<br />\n"; 901 } 902 return array($statusCode, $fileData); 903 } 904 905 // Return extension download URL, redirect to known URL if necessary 906 public function getExtensionDownloadUrl($url) { 907 if (preg_match("#^https://github.com/(.+)/archive/refs/heads/main.zip$#", $url, $matches)) { 908 $url = "https://codeload.github.com/".$matches[1]."/zip/refs/heads/main"; 909 } 910 if (preg_match("#^https://github.com/(.+)/raw/main/(.+)$#", $url, $matches)) { 911 $url = "https://raw.githubusercontent.com/".$matches[1]."/main/".$matches[2]; 912 } 913 return $url; 914 } 915 916 // Return time of next daily update 917 public function getTimestampDaily() { 918 $timeOffset = 0; 919 foreach (str_split($this->yellow->system->get("sitename")) as $char) { 920 $timeOffset = ($timeOffset+ord($char)) % 60; 921 } 922 return mktime(0, 0, 0) + 60*60*24 + $timeOffset; 923 } 924 }