mikuli.cz

:)
git clone https://git.sr.ht/~ashymad/mikuli.cz
Log | Files | Refs

edit.php (114876B)


      1 <?php
      2 // Edit extension, https://github.com/annaesvensson/yellow-edit
      3 
      4 class YellowEdit {
      5     const VERSION = "0.9.18";
      6     public $yellow;         // access to API
      7     public $response;       // web response
      8     public $merge;          // text merge
      9     public $editable;       // page can be edited? (boolean)
     10 
     11     // Handle initialisation
     12     public function onLoad($yellow) {
     13         $this->yellow = $yellow;
     14         $this->response = new YellowEditResponse($yellow);
     15         $this->merge = new YellowEditMerge($yellow);
     16         $this->yellow->system->setDefault("editLocation", "/edit/");
     17         $this->yellow->system->setDefault("editUploadNewLocation", "/media/@group/@filename");
     18         $this->yellow->system->setDefault("editUploadExtensions", ".gif, .jpeg, .jpg, .mp3, .mp4, .ogg, .pdf, .png, .svg, .zip");
     19         $this->yellow->system->setDefault("editKeyboardShortcuts", "ctrl+b bold, ctrl+i italic, ctrl+k strikethrough, ctrl+e code, ctrl+s save, ctrl+alt+p preview");
     20         $this->yellow->system->setDefault("editToolbarButtons", "auto");
     21         $this->yellow->system->setDefault("editEndOfLine", "auto");
     22         $this->yellow->system->setDefault("editNewFile", "page-new-(.*).md");
     23         $this->yellow->system->setDefault("editUserPasswordMinLength", "8");
     24         $this->yellow->system->setDefault("editUserHashAlgorithm", "bcrypt");
     25         $this->yellow->system->setDefault("editUserHashCost", "10");
     26         $this->yellow->system->setDefault("editUserAccess", "create, edit, delete, restore, upload");
     27         $this->yellow->system->setDefault("editUserHome", "/");
     28         $this->yellow->system->setDefault("editLoginRestriction", "0");
     29         $this->yellow->system->setDefault("editLoginSessionTimeout", "2592000");
     30         $this->yellow->system->setDefault("editBruteForceProtection", "25");
     31     }
     32     
     33     // Handle update
     34     public function onUpdate($action) {
     35         if ($action=="clean" || $action=="daily") {
     36             $cleanup = false;
     37             $fileNameUser = $this->yellow->system->get("coreExtensionDirectory").$this->yellow->system->get("coreUserFile");
     38             $fileData = $this->yellow->toolbox->readFile($fileNameUser);
     39             $fileDataNew = "";
     40             foreach ($this->yellow->toolbox->getTextLines($fileData) as $line) {
     41                 if (preg_match("/^\s*(.*?)\s*:\s*(.*?)\s*$/", $line, $matches)) {
     42                     if (lcfirst($matches[1])=="email" && !is_string_empty($matches[2])) {
     43                         $status = $this->yellow->user->getUser("status", $matches[2]);
     44                         $reserved = strtotime($this->yellow->user->getUser("modified", $matches[2])) + 60*60*24;
     45                         $cleanup = $status!="active" && $status!="inactive" && $reserved<=time();
     46                     }
     47                 }
     48                 if (!$cleanup) $fileDataNew .= $line;
     49             }
     50             $fileDataNew = rtrim($fileDataNew)."\n";
     51             if ($fileData!=$fileDataNew && !$this->yellow->toolbox->writeFile($fileNameUser, $fileDataNew)) {
     52                 $this->yellow->toolbox->log("error", "Can't write file '$fileNameUser'!");
     53             }
     54         }
     55     }
     56     
     57     // Handle request
     58     public function onRequest($scheme, $address, $base, $location, $fileName) {
     59         $statusCode = 0;
     60         if ($this->isEditLocation($location)) {
     61             $this->editable = true;
     62             $scheme = $this->yellow->system->get("coreServerScheme");
     63             $address = $this->yellow->system->get("coreServerAddress");
     64             $base = rtrim($this->yellow->system->get("coreServerBase").$this->yellow->system->get("editLocation"), "/");
     65             list($scheme, $address, $base, $location, $fileName) = $this->yellow->lookup->getRequestInformation($scheme, $address, $base);
     66             $this->yellow->page->setRequestInformation($scheme, $address, $base, $location, $fileName, false);
     67             $statusCode = $this->processRequest($scheme, $address, $base, $location, $fileName);
     68         }
     69         return $statusCode;
     70     }
     71     
     72     // Handle command
     73     public function onCommand($command, $text) {
     74         switch ($command) {
     75             case "user":    $statusCode = $this->processCommandUser($command, $text); break;
     76             default:        $statusCode = 0;
     77         }
     78         return $statusCode;
     79     }
     80     
     81     // Handle command help
     82     public function onCommandHelp() {
     83         return "user [option email password]";
     84     }
     85     
     86     // Handle page meta data
     87     public function onParseMetaData($page) {
     88         $page->set("editPageUrl", $this->yellow->lookup->normaliseUrl(
     89             $this->yellow->system->get("coreServerScheme"),
     90             $this->yellow->system->get("coreServerAddress"),
     91             $this->yellow->system->get("coreServerBase"),
     92             rtrim($this->yellow->system->get("editLocation"), "/").$page->location));
     93     }
     94     
     95     // Handle page content element
     96     public function onParseContentElement($page, $name, $text, $attributes, $type) {
     97         $output = null;
     98         if ($name=="edit" && $type=="inline") {
     99             list($target, $description) = $this->yellow->toolbox->getTextList($text, " ", 2);
    100             if (is_string_empty($target) || $target=="-") $target = "main";
    101             if (is_string_empty($description)) $description = ucfirst($name);
    102             $pageTarget = $target=="main" ? $page->getPage("main") : $page->getPage("main")->getPage($target);
    103             $output = "<a href=\"".$pageTarget->get("editPageUrl")."\">".htmlspecialchars($description)."</a>";
    104         }
    105         return $output;
    106     }
    107     
    108     // Handle page layout
    109     public function onParsePageLayout($page, $name) {
    110         if ($this->editable) {
    111             $this->response->processPageData($page);
    112         }
    113     }
    114     
    115     // Handle page extra data
    116     public function onParsePageExtra($page, $name) {
    117         $output = null;
    118         if ($this->editable && $name=="header") {
    119             $assetLocation = $this->yellow->system->get("coreServerBase").$this->yellow->system->get("coreAssetLocation");
    120             $output = "<link rel=\"stylesheet\" type=\"text/css\" media=\"all\" href=\"{$assetLocation}edit.css\" />\n";
    121             $output .= "<script type=\"text/javascript\" src=\"{$assetLocation}edit.js\"></script>\n";
    122             $output .= "<script type=\"text/javascript\">\n";
    123             $output .= "// <![CDATA[\n";
    124             $output .= "yellow.page = ".json_encode($this->response->getPageData($page)).";\n";
    125             $output .= "yellow.system = ".json_encode($this->response->getSystemData()).";\n";
    126             $output .= "yellow.user = ".json_encode($this->response->getUserData()).";\n";
    127             $output .= "yellow.language = ".json_encode($this->response->getLanguageData()).";\n";
    128             $output .= "// ]]>\n";
    129             $output .= "</script>\n";
    130         }
    131         return $output;
    132     }
    133     
    134     // Process command to update user account
    135     public function processCommandUser($command, $text) {
    136         list($option) = $this->yellow->toolbox->getTextArguments($text);
    137         switch ($option) {
    138             case "":        $statusCode = $this->userShow($command, $text); break;
    139             case "add":     $statusCode = $this->userAdd($command, $text); break;
    140             case "change":  $statusCode = $this->userChange($command, $text); break;
    141             case "remove":  $statusCode = $this->userRemove($command, $text); break;
    142             default:        $statusCode = 400; echo "Yellow $command: Invalid arguments\n";
    143         }
    144         return $statusCode;
    145     }
    146     
    147     // Show user accounts
    148     public function userShow($command, $text) {
    149         $data = array();
    150         foreach ($this->yellow->user->settings as $key=>$value) {
    151             $data[$key] = "$value[email] - User account by $value[name].";
    152         }
    153         uksort($data, "strnatcasecmp");
    154         foreach ($data as $line) echo "$line\n";
    155         if (is_array_empty($data)) echo "Yellow $command: No user accounts\n";
    156         return 200;
    157     }
    158     
    159     // Add user account
    160     public function userAdd($command, $text) {
    161         $status = "ok";
    162         list($option, $email, $password) = $this->yellow->toolbox->getTextArguments($text);
    163         if (is_string_empty($email) || is_string_empty($password)) $status = $this->response->status = "incomplete";
    164         if ($status=="ok") $status = $this->getUserAccount("add", $email, $password);
    165         if ($status=="ok" && $this->isUserAccountTaken($email)) $status = "taken";
    166         switch ($status) {
    167             case "incomplete":  echo "ERROR updating settings: Please enter email and password!\n"; break;
    168             case "invalid":     echo "ERROR updating settings: Please enter a valid email!\n"; break;
    169             case "taken":       echo "ERROR updating settings: Please enter a different email!\n"; break;
    170             case "weak":        echo "ERROR updating settings: Please enter a different password!\n"; break;
    171             case "short":       echo "ERROR updating settings: Please enter a longer password!\n"; break;
    172         }
    173         if ($status=="ok") {
    174             $name = $this->yellow->system->get("sitename");
    175             $userLanguage = $this->yellow->system->get("language");
    176             $fileNameUser = $this->yellow->system->get("coreExtensionDirectory").$this->yellow->system->get("coreUserFile");
    177             $settings = array(
    178                 "name" => $name,
    179                 "description" => $this->yellow->language->getText("editUserDescription", $userLanguage),
    180                 "language" => $userLanguage,
    181                 "access" => $this->yellow->system->get("editUserAccess"),
    182                 "home" => $this->yellow->system->get("editUserHome"),
    183                 "hash" => $this->response->createHash($password),
    184                 "stamp" => $this->response->createStamp(),
    185                 "pending" => "none",
    186                 "failed" => "0",
    187                 "modified" => date("Y-m-d H:i:s", time()),
    188                 "status" => "active");
    189             $status = $this->yellow->user->save($fileNameUser, $email, $settings) ? "ok" : "error";
    190             if ($status=="error") echo "ERROR updating settings: Can't write file '$fileNameUser'!\n";
    191             $this->yellow->toolbox->log($status=="ok" ? "info" : "error", "Add user '".strtok($name, " ")."'");
    192         }
    193         if ($status=="ok") {
    194             $algorithm = $this->yellow->system->get("editUserHashAlgorithm");
    195             $status = substru($this->yellow->user->getUser("hash", $email), 0, 10)!="error-hash" ? "ok" : "error";
    196             if ($status=="error") echo "ERROR updating settings: Hash algorithm '$algorithm' not supported!\n";
    197         }
    198         $statusCode = $status=="ok" ? 200 : 500;
    199         echo "Yellow $command: User account ".($statusCode!=200 ? "not " : "")."added\n";
    200         return $statusCode;
    201     }
    202     
    203     // Change user account
    204     public function userChange($command, $text) {
    205         $status = "ok";
    206         list($option, $email, $password) = $this->yellow->toolbox->getTextArguments($text);
    207         if (is_string_empty($email)) $status = $this->response->status = "invalid";
    208         if ($status=="ok") $status = $this->getUserAccount("change", $email, $password);
    209         if ($status=="ok" && !$this->yellow->user->isExisting($email)) $status = "unknown";
    210         switch ($status) {
    211             case "invalid": echo "ERROR updating settings: Please enter a valid email!\n"; break;
    212             case "unknown": echo "ERROR updating settings: Can't find email '$email'!\n"; break;
    213             case "weak":    echo "ERROR updating settings: Please enter a different password!\n"; break;
    214             case "short":   echo "ERROR updating settings: Please enter a longer password!\n"; break;
    215         }
    216         if ($status=="ok") {
    217             $fileNameUser = $this->yellow->system->get("coreExtensionDirectory").$this->yellow->system->get("coreUserFile");
    218             $settings = array(
    219                 "hash" => is_string_empty($password) ? $this->yellow->user->getUser("hash", $email) : $this->response->createHash($password),
    220                 "failed" => "0",
    221                 "modified" => date("Y-m-d H:i:s", time()));
    222             $status = $this->yellow->user->save($fileNameUser, $email, $settings) ? "ok" : "error";
    223             if ($status=="error") echo "ERROR updating settings: Can't write file '$fileNameUser'!\n";
    224         }
    225         $statusCode = $status=="ok" ? 200 : 500;
    226         echo "Yellow $command: User account ".($statusCode!=200 ? "not " : "")."changed\n";
    227         return $statusCode;
    228     }
    229 
    230     // Remove user account
    231     public function userRemove($command, $text) {
    232         $status = "ok";
    233         list($option, $email) = $this->yellow->toolbox->getTextArguments($text);
    234         if (is_string_empty($email)) $status = $this->response->status = "invalid";
    235         if ($status=="ok") $status = $this->getUserAccount("remove", $email, "");
    236         if ($status=="ok" && !$this->yellow->user->isExisting($email)) $status = "unknown";
    237         switch ($status) {
    238             case "invalid": echo "ERROR updating settings: Please enter a valid email!\n"; break;
    239             case "unknown": echo "ERROR updating settings: Can't find email '$email'!\n"; break;
    240         }
    241         if ($status=="ok") {
    242             $name = $this->yellow->user->getUser("name", $email);
    243             $fileNameUser = $this->yellow->system->get("coreExtensionDirectory").$this->yellow->system->get("coreUserFile");
    244             $status = $this->yellow->user->remove($fileNameUser, $email) ? "ok" : "error";
    245             if ($status=="error") echo "ERROR updating settings: Can't write file '$fileNameUser'!\n";
    246             $this->yellow->toolbox->log($status=="ok" ? "info" : "error", "Remove user '".strtok($name, " ")."'");
    247         }
    248         $statusCode = $status=="ok" ? 200 : 500;
    249         echo "Yellow $command: User account ".($statusCode!=200 ? "not " : "")."removed\n";
    250         return $statusCode;
    251     }
    252     
    253     // Process request
    254     public function processRequest($scheme, $address, $base, $location, $fileName) {
    255         $statusCode = 0;
    256         if ($this->checkUserAuth($scheme, $address, $base, $location, $fileName)) {
    257             switch ($this->yellow->page->getRequest("action")) {
    258                 case "":            $statusCode = $this->processRequestShow($scheme, $address, $base, $location, $fileName); break;
    259                 case "login":       $statusCode = $this->processRequestLogin($scheme, $address, $base, $location, $fileName); break;
    260                 case "logout":      $statusCode = $this->processRequestLogout($scheme, $address, $base, $location, $fileName); break;
    261                 case "quit":        $statusCode = $this->processRequestQuit($scheme, $address, $base, $location, $fileName); break;
    262                 case "account":     $statusCode = $this->processRequestAccount($scheme, $address, $base, $location, $fileName); break;
    263                 case "configure":   $statusCode = $this->processRequestConfigure($scheme, $address, $base, $location, $fileName); break;
    264                 case "update":      $statusCode = $this->processRequestUpdate($scheme, $address, $base, $location, $fileName); break;
    265                 case "create":      $statusCode = $this->processRequestCreate($scheme, $address, $base, $location, $fileName); break;
    266                 case "edit":        $statusCode = $this->processRequestEdit($scheme, $address, $base, $location, $fileName); break;
    267                 case "delete":      $statusCode = $this->processRequestDelete($scheme, $address, $base, $location, $fileName); break;
    268                 case "restore":     $statusCode = $this->processRequestRestore($scheme, $address, $base, $location, $fileName); break;
    269                 case "preview":     $statusCode = $this->processRequestPreview($scheme, $address, $base, $location, $fileName); break;
    270                 case "upload":      $statusCode = $this->processRequestUpload($scheme, $address, $base, $location, $fileName); break;
    271             }
    272         } elseif ($this->checkUserUnauth($scheme, $address, $base, $location, $fileName)) {
    273             $this->yellow->lookup->requestHandler = "core";
    274             switch ($this->yellow->page->getRequest("action")) {
    275                 case "":            $statusCode = $this->processRequestShow($scheme, $address, $base, $location, $fileName); break;
    276                 case "signup":      $statusCode = $this->processRequestSignup($scheme, $address, $base, $location, $fileName); break;
    277                 case "forgot":      $statusCode = $this->processRequestForgot($scheme, $address, $base, $location, $fileName); break;
    278                 case "confirm":     $statusCode = $this->processRequestConfirm($scheme, $address, $base, $location, $fileName); break;
    279                 case "approve":     $statusCode = $this->processRequestApprove($scheme, $address, $base, $location, $fileName); break;
    280                 case "recover":     $statusCode = $this->processRequestRecover($scheme, $address, $base, $location, $fileName); break;
    281                 case "reactivate":  $statusCode = $this->processRequestReactivate($scheme, $address, $base, $location, $fileName); break;
    282                 case "verify":      $statusCode = $this->processRequestVerify($scheme, $address, $base, $location, $fileName); break;
    283                 case "change":      $statusCode = $this->processRequestChange($scheme, $address, $base, $location, $fileName); break;
    284                 case "remove":      $statusCode = $this->processRequestRemove($scheme, $address, $base, $location, $fileName); break;
    285             }
    286         }
    287         if ($statusCode==0) $statusCode = $this->yellow->processRequest($scheme, $address, $base, $location, $fileName, false);
    288         $this->checkUserFailed($scheme, $address, $base, $location, $fileName);
    289         return $statusCode;
    290     }
    291     
    292     // Process request to show file
    293     public function processRequestShow($scheme, $address, $base, $location, $fileName) {
    294         $statusCode = 0;
    295         if (is_readable($fileName)) {
    296             $statusCode = $this->yellow->processRequest($scheme, $address, $base, $location, $fileName, false);
    297         } else {
    298             if ($this->yellow->lookup->isRedirectLocation($location)) {
    299                 $location = $this->yellow->lookup->getRedirectLocation($location);
    300                 $location = $this->yellow->lookup->normaliseUrl($scheme, $address, $base, $location);
    301                 $statusCode = $this->yellow->sendStatus(301, $location);
    302             } else {
    303                 $statusCode = 404;
    304                 if ($this->response->isUserAccess("create", $location) && $this->response->isCreateLocation($location))
    305                     $statusCode = 434;
    306                 if ($this->response->isUserAccess("restore", $location) && $this->response->isDeletedLocation($location)) {
    307                     $statusCode = 435;
    308                 }
    309                 $this->yellow->page->error($statusCode);
    310                 $statusCode = $this->yellow->processRequest($scheme, $address, $base, $location, $fileName, false);
    311             }
    312         }
    313         return $statusCode;
    314     }
    315 
    316     // Process request for user login
    317     public function processRequestLogin($scheme, $address, $base, $location, $fileName) {
    318         $fileNameUser = $this->yellow->system->get("coreExtensionDirectory").$this->yellow->system->get("coreUserFile");
    319         $settings = array("failed" => "0", "modified" => date("Y-m-d H:i:s", time()));
    320         if ($this->yellow->user->save($fileNameUser, $this->response->userEmail, $settings)) {
    321             $home = $this->yellow->user->getUser("home", $this->response->userEmail);
    322             if (substru($location, 0, strlenu($home))==$home) {
    323                 $location = $this->yellow->lookup->normaliseUrl($scheme, $address, $base, $location);
    324                 $statusCode = $this->yellow->sendStatus(303, $location);
    325             } else {
    326                 $location = $this->yellow->lookup->normaliseUrl($scheme, $address, $base, $home);
    327                 $statusCode = $this->yellow->sendStatus(302, $location);
    328             }
    329         } else {
    330             $this->yellow->page->error(500, "Can't write file '$fileNameUser'!");
    331             $statusCode = $this->yellow->processRequest($scheme, $address, $base, $location, $fileName, false);
    332         }
    333         return $statusCode;
    334     }
    335     
    336     // Process request for user logout
    337     public function processRequestLogout($scheme, $address, $base, $location, $fileName) {
    338         $this->response->userEmail = "";
    339         $this->response->destroyCookies($scheme, $address, $base);
    340         $location = $this->yellow->lookup->normaliseUrl(
    341             $this->yellow->system->get("coreServerScheme"),
    342             $this->yellow->system->get("coreServerAddress"),
    343             $this->yellow->system->get("coreServerBase"),
    344             $location);
    345         $statusCode = $this->yellow->sendStatus(302, $location);
    346         return $statusCode;
    347     }
    348 
    349     // Process request for user signup
    350     public function processRequestSignup($scheme, $address, $base, $location, $fileName) {
    351         $this->response->action = "signup";
    352         $this->response->status = "ok";
    353         $name = trim(preg_replace("/[^\pL\d\-\. ]/u", "-", $this->yellow->page->getRequest("name")));
    354         $email = trim($this->yellow->page->getRequest("email"));
    355         $password = trim($this->yellow->page->getRequest("password"));
    356         $consent = trim($this->yellow->page->getRequest("consent"));
    357         if (is_string_empty($name) || is_string_empty($email) || is_string_empty($password) || is_string_empty($consent)) $this->response->status = "incomplete";
    358         if ($this->response->status=="ok") $this->response->status = $this->getUserAccount($this->response->action, $email, $password);
    359         if ($this->response->status=="ok" && $this->response->isLoginRestriction()) $this->response->status = "next";
    360         if ($this->response->status=="ok" && $this->isUserAccountTaken($email)) $this->response->status = "next";
    361         if ($this->response->status=="ok") {
    362             $userLanguage = $this->yellow->lookup->findContentLanguage($fileName, $this->yellow->system->get("language"));
    363             $fileNameUser = $this->yellow->system->get("coreExtensionDirectory").$this->yellow->system->get("coreUserFile");
    364             $settings = array(
    365                 "name" => $name,
    366                 "description" => $this->yellow->language->getText("editUserDescription", $userLanguage),
    367                 "language" => $userLanguage,
    368                 "access" => $this->yellow->system->get("editUserAccess"),
    369                 "home" => $this->yellow->system->get("editUserHome"),
    370                 "hash" => $this->response->createHash($password),
    371                 "stamp" => $this->response->createStamp(),
    372                 "pending" => "none",
    373                 "failed" => "0",
    374                 "modified" => date("Y-m-d H:i:s", time()),
    375                 "status" => "unconfirmed");
    376             $this->response->status = $this->yellow->user->save($fileNameUser, $email, $settings) ? "ok" : "error";
    377             if ($this->response->status=="error") $this->yellow->page->error(500, "Can't write file '$fileNameUser'!");
    378         }
    379         if ($this->response->status=="ok") {
    380             $algorithm = $this->yellow->system->get("editUserHashAlgorithm");
    381             $this->response->status = substru($this->yellow->user->getUser("hash", $email), 0, 10)!="error-hash" ? "ok" : "error";
    382             if ($this->response->status=="error") $this->yellow->page->error(500, "Hash algorithm '$algorithm' not supported!");
    383         }
    384         if ($this->response->status=="ok") {
    385             $this->response->status = $this->response->sendMail($scheme, $address, $base, $email, "confirm") ? "next" : "error";
    386             if ($this->response->status=="error") $this->yellow->page->error(500, "Can't send email message!");
    387         }
    388         $statusCode = $this->yellow->processRequest($scheme, $address, $base, $location, $fileName, false);
    389         return $statusCode;
    390     }
    391     
    392     // Process request to confirm user signup
    393     public function processRequestConfirm($scheme, $address, $base, $location, $fileName) {
    394         $this->response->action = "confirm";
    395         $this->response->status = "ok";
    396         $email = $this->yellow->page->getRequest("email");
    397         $this->response->status = $this->getUserStatus($email, $this->yellow->page->getRequest("action"));
    398         if ($this->response->status=="ok") {
    399             $fileNameUser = $this->yellow->system->get("coreExtensionDirectory").$this->yellow->system->get("coreUserFile");
    400             $settings = array("failed" => "0", "modified" => date("Y-m-d H:i:s", time()), "status" => "unapproved");
    401             $this->response->status = $this->yellow->user->save($fileNameUser, $email, $settings) ? "ok" : "error";
    402             if ($this->response->status=="error") $this->yellow->page->error(500, "Can't write file '$fileNameUser'!");
    403         }
    404         if ($this->response->status=="ok") {
    405             $this->response->status = $this->response->sendMail($scheme, $address, $base, $email, "approve") ? "done" : "error";
    406             if ($this->response->status=="error") $this->yellow->page->error(500, "Can't send email message!");
    407         }
    408         $statusCode = $this->yellow->processRequest($scheme, $address, $base, $location, $fileName, false);
    409         return $statusCode;
    410     }
    411     
    412     // Process request to approve user signup
    413     public function processRequestApprove($scheme, $address, $base, $location, $fileName) {
    414         $this->response->action = "approve";
    415         $this->response->status = "ok";
    416         $email = $this->yellow->page->getRequest("email");
    417         $this->response->status = $this->getUserStatus($email, $this->yellow->page->getRequest("action"));
    418         if ($this->response->status=="ok") {
    419             $name = $this->yellow->user->getUser("name", $email);
    420             $fileNameUser = $this->yellow->system->get("coreExtensionDirectory").$this->yellow->system->get("coreUserFile");
    421             $settings = array("failed" => "0", "modified" => date("Y-m-d H:i:s", time()), "status" => "active");
    422             $this->response->status = $this->yellow->user->save($fileNameUser, $email, $settings) ? "ok" : "error";
    423             if ($this->response->status=="error") $this->yellow->page->error(500, "Can't write file '$fileNameUser'!");
    424             $this->yellow->toolbox->log($this->response->status=="ok" ? "info" : "error", "Add user '".strtok($name, " ")."'");
    425         }
    426         if ($this->response->status=="ok") {
    427             $this->response->status = $this->response->sendMail($scheme, $address, $base, $email, "welcome") ? "done" : "error";
    428             if ($this->response->status=="error") $this->yellow->page->error(500, "Can't send email message!");
    429         }
    430         $statusCode = $this->yellow->processRequest($scheme, $address, $base, $location, $fileName, false);
    431         return $statusCode;
    432     }
    433 
    434     // Process request for forgotten password
    435     public function processRequestForgot($scheme, $address, $base, $location, $fileName) {
    436         $this->response->action = "forgot";
    437         $this->response->status = "ok";
    438         $email = trim($this->yellow->page->getRequest("email"));
    439         if (!filter_var($email, FILTER_VALIDATE_EMAIL)) $this->response->status = "invalid";
    440         if ($this->response->status=="ok" && !$this->yellow->user->isExisting($email)) $this->response->status = "next";
    441         if ($this->response->status=="ok") {
    442             $this->response->status = $this->response->sendMail($scheme, $address, $base, $email, "recover") ? "next" : "error";
    443             if ($this->response->status=="error") $this->yellow->page->error(500, "Can't send email message!");
    444         }
    445         $statusCode = $this->yellow->processRequest($scheme, $address, $base, $location, $fileName, false);
    446         return $statusCode;
    447     }
    448     
    449     // Process request to recover password
    450     public function processRequestRecover($scheme, $address, $base, $location, $fileName) {
    451         $this->response->action = "recover";
    452         $this->response->status = "ok";
    453         $email = trim($this->yellow->page->getRequest("email"));
    454         $password = trim($this->yellow->page->getRequest("password"));
    455         $this->response->status = $this->getUserStatus($email, $this->yellow->page->getRequest("action"));
    456         if ($this->response->status=="ok") {
    457             if (is_string_empty($password)) $this->response->status = "password";
    458             if ($this->response->status=="ok") $this->response->status = $this->getUserAccount($this->response->action, $email, $password);
    459             if ($this->response->status=="ok") {
    460                 $fileNameUser = $this->yellow->system->get("coreExtensionDirectory").$this->yellow->system->get("coreUserFile");
    461                 $settings = array("hash" => $this->response->createHash($password), "failed" => "0", "modified" => date("Y-m-d H:i:s", time()));
    462                 $this->response->status = $this->yellow->user->save($fileNameUser, $email, $settings) ? "ok" : "error";
    463                 if ($this->response->status=="error") $this->yellow->page->error(500, "Can't write file '$fileNameUser'!");
    464             }
    465             if ($this->response->status=="ok") {
    466                 $this->response->destroyCookies($scheme, $address, $base);
    467                 $this->response->status = "done";
    468             }
    469         }
    470         $statusCode = $this->yellow->processRequest($scheme, $address, $base, $location, $fileName, false);
    471         return $statusCode;
    472     }
    473     
    474     // Process request to reactivate account
    475     public function processRequestReactivate($scheme, $address, $base, $location, $fileName) {
    476         $this->response->action = "reactivate";
    477         $this->response->status = "ok";
    478         $email = $this->yellow->page->getRequest("email");
    479         $this->response->status = $this->getUserStatus($email, $this->yellow->page->getRequest("action"));
    480         if ($this->response->status=="ok") {
    481             $fileNameUser = $this->yellow->system->get("coreExtensionDirectory").$this->yellow->system->get("coreUserFile");
    482             $settings = array("failed" => "0", "modified" => date("Y-m-d H:i:s", time()), "status" => "active");
    483             $this->response->status = $this->yellow->user->save($fileNameUser, $email, $settings) ? "done" : "error";
    484             if ($this->response->status=="error") $this->yellow->page->error(500, "Can't write file '$fileNameUser'!");
    485         }
    486         $statusCode = $this->yellow->processRequest($scheme, $address, $base, $location, $fileName, false);
    487         return $statusCode;
    488     }
    489     
    490     // Process request to verify email
    491     public function processRequestVerify($scheme, $address, $base, $location, $fileName) {
    492         $this->response->action = "verify";
    493         $this->response->status = "ok";
    494         $email = $emailSource = $this->yellow->page->getRequest("email");
    495         $this->response->status = $this->getUserStatus($email, $this->yellow->page->getRequest("action"));
    496         if ($this->response->status=="ok") {
    497             $emailSource = $this->yellow->user->getUser("pending", $email);
    498             if ($this->yellow->user->getUser("status", $emailSource)!="active") $this->response->status = "done";
    499         }
    500         if ($this->response->status=="ok") {
    501             $fileNameUser = $this->yellow->system->get("coreExtensionDirectory").$this->yellow->system->get("coreUserFile");
    502             $settings = array("failed" => "0", "modified" => date("Y-m-d H:i:s", time()), "status" => "unchanged");
    503             $this->response->status = $this->yellow->user->save($fileNameUser, $email, $settings) ? "ok" : "error";
    504             if ($this->response->status=="error") $this->yellow->page->error(500, "Can't write file '$fileNameUser'!");
    505         }
    506         if ($this->response->status=="ok") {
    507             $this->response->status = $this->response->sendMail($scheme, $address, $base, $emailSource, "change") ? "done" : "error";
    508             if ($this->response->status=="error") $this->yellow->page->error(500, "Can't send email message!");
    509         }
    510         $statusCode = $this->yellow->processRequest($scheme, $address, $base, $location, $fileName, false);
    511         return $statusCode;
    512     }
    513     
    514     // Process request to change email or password
    515     public function processRequestChange($scheme, $address, $base, $location, $fileName) {
    516         $this->response->action = "change";
    517         $this->response->status = "ok";
    518         $email = $emailSource = trim($this->yellow->page->getRequest("email"));
    519         $this->response->status = $this->getUserStatus($email, $this->yellow->page->getRequest("action"));
    520         if ($this->response->status=="ok") {
    521             list($email, $hash) = $this->yellow->toolbox->getTextList($this->yellow->user->getUser("pending", $email), ":", 2);
    522             if (!$this->yellow->user->isExisting($email) || is_string_empty($hash)) $this->response->status = "done";
    523         }
    524         if ($this->response->status=="ok") {
    525             $fileNameUser = $this->yellow->system->get("coreExtensionDirectory").$this->yellow->system->get("coreUserFile");
    526             $settings = array(
    527                 "hash" => $hash,
    528                 "pending" => "none",
    529                 "failed" => "0",
    530                 "modified" => date("Y-m-d H:i:s", time()),
    531                 "status" => "active");
    532             $this->response->status = $this->yellow->user->save($fileNameUser, $email, $settings) ? "ok" : "error";
    533             if ($this->response->status=="error") $this->yellow->page->error(500, "Can't write file '$fileNameUser'!");
    534         }
    535         if ($this->response->status=="ok" && $email!=$emailSource) {
    536             $fileNameUser = $this->yellow->system->get("coreExtensionDirectory").$this->yellow->system->get("coreUserFile");
    537             $this->response->status = $this->yellow->user->remove($fileNameUser, $emailSource) ? "ok" : "error";
    538             if ($this->response->status=="error") $this->yellow->page->error(500, "Can't write file '$fileNameUser'!");
    539         }
    540         if ($this->response->status=="ok") {
    541             $this->response->destroyCookies($scheme, $address, $base);
    542             $this->response->status = "done";
    543         }
    544         $statusCode = $this->yellow->processRequest($scheme, $address, $base, $location, $fileName, false);
    545         return $statusCode;
    546     }
    547     
    548     // Process request to quit account
    549     public function processRequestQuit($scheme, $address, $base, $location, $fileName) {
    550         $this->response->action = "quit";
    551         $this->response->status = "ok";
    552         $name = trim($this->yellow->page->getRequest("name"));
    553         $email = $this->response->userEmail;
    554         if (is_string_empty($name)) $this->response->status = "none";
    555         if ($this->response->status=="ok" && $name!=$this->yellow->user->getUser("name", $email)) $this->response->status = "mismatch";
    556         if ($this->response->status=="ok") $this->response->status = $this->getUserAccount($this->response->action, $email, "");
    557         if ($this->response->status=="ok") {
    558             $this->response->status = $this->response->sendMail($scheme, $address, $base, $email, "remove") ? "next" : "error";
    559             if ($this->response->status=="error") $this->yellow->page->error(500, "Can't send email message!");
    560         }
    561         $statusCode = $this->yellow->processRequest($scheme, $address, $base, $location, $fileName, false);
    562         return $statusCode;
    563     }
    564     
    565     // Process request to remove account
    566     public function processRequestRemove($scheme, $address, $base, $location, $fileName) {
    567         $this->response->action = "remove";
    568         $this->response->status = "ok";
    569         $email = $this->yellow->page->getRequest("email");
    570         $this->response->status = $this->getUserStatus($email, $this->yellow->page->getRequest("action"));
    571         if ($this->response->status=="ok") {
    572             $name = $this->yellow->user->getUser("name", $email);
    573             $fileNameUser = $this->yellow->system->get("coreExtensionDirectory").$this->yellow->system->get("coreUserFile");
    574             $settings = array("failed" => "0", "modified" => date("Y-m-d H:i:s", time()), "status" => "removed");
    575             $this->response->status = $this->yellow->user->save($fileNameUser, $email, $settings) ? "ok" : "error";
    576             if ($this->response->status=="error") $this->yellow->page->error(500, "Can't write file '$fileNameUser'!");
    577             $this->yellow->toolbox->log($this->response->status=="ok" ? "info" : "error", "Remove user '".strtok($name, " ")."'");
    578         }
    579         if ($this->response->status=="ok") {
    580             $this->response->status = $this->response->sendMail($scheme, $address, $base, $email, "goodbye") ? "ok" : "error";
    581             if ($this->response->status=="error") $this->yellow->page->error(500, "Can't send email message!");
    582         }
    583         if ($this->response->status=="ok") {
    584             $fileNameUser = $this->yellow->system->get("coreExtensionDirectory").$this->yellow->system->get("coreUserFile");
    585             $this->response->status = $this->yellow->user->remove($fileNameUser, $email) ? "ok" : "error";
    586             if ($this->response->status=="error") $this->yellow->page->error(500, "Can't write file '$fileNameUser'!");
    587         }
    588         if ($this->response->status=="ok") {
    589             $this->response->destroyCookies($scheme, $address, $base);
    590             $this->response->status = "done";
    591         }
    592         $statusCode = $this->yellow->processRequest($scheme, $address, $base, $location, $fileName, false);
    593         return $statusCode;
    594     }
    595     
    596     // Process request to change account settings
    597     public function processRequestAccount($scheme, $address, $base, $location, $fileName) {
    598         $this->response->action = "account";
    599         $this->response->status = "ok";
    600         $email = trim($this->yellow->page->getRequest("email"));
    601         $emailSource = $this->response->userEmail;
    602         $password = trim($this->yellow->page->getRequest("password"));
    603         $name = trim(preg_replace("/[^\pL\d\-\. ]/u", "-", $this->yellow->page->getRequest("name")));
    604         $language = trim($this->yellow->page->getRequest("language"));
    605         if ($email!=$emailSource || !is_string_empty($password)) {
    606             if (is_string_empty($email)) $this->response->status = "invalid";
    607             if ($this->response->status=="ok") $this->response->status = $this->getUserAccount($this->response->action, $email, $password);
    608             if ($this->response->status=="ok" && $email!=$emailSource && $this->isUserAccountTaken($email)) $this->response->status = "taken";
    609             if ($this->response->status=="ok" && $email!=$emailSource) {
    610                 $fileNameUser = $this->yellow->system->get("coreExtensionDirectory").$this->yellow->system->get("coreUserFile");
    611                 $settings = array(
    612                     "name" => $name,
    613                     "description" => $this->yellow->user->getUser("description", $emailSource),
    614                     "language" => $language,
    615                     "access" => $this->yellow->user->getUser("access", $emailSource),
    616                     "home" => $this->yellow->user->getUser("home", $emailSource),
    617                     "hash" => $this->response->createHash("none"),
    618                     "stamp" => $this->response->createStamp(),
    619                     "pending" => $emailSource,
    620                     "failed" => "0",
    621                     "modified" => date("Y-m-d H:i:s", time()),
    622                     "status" => "unverified");
    623                 $this->response->status = $this->yellow->user->save($fileNameUser, $email, $settings) ? "ok" : "error";
    624                 if ($this->response->status=="error") $this->yellow->page->error(500, "Can't write file '$fileNameUser'!");
    625             }
    626             if ($this->response->status=="ok") {
    627                 $fileNameUser = $this->yellow->system->get("coreExtensionDirectory").$this->yellow->system->get("coreUserFile");
    628                 $settings = array(
    629                     "name" => $name,
    630                     "language" => $language,
    631                     "pending" => $email.":".(is_string_empty($password) ? $this->yellow->user->getUser("hash", $emailSource) : $this->response->createHash($password)),
    632                     "failed" => "0",
    633                     "modified" => date("Y-m-d H:i:s", time()));
    634                 $this->response->status = $this->yellow->user->save($fileNameUser, $emailSource, $settings) ? "ok" : "error";
    635                 if ($this->response->status=="error") $this->yellow->page->error(500, "Can't write file '$fileNameUser'!");
    636             }
    637             if ($this->response->status=="ok") {
    638                 $action = $email!=$emailSource ? "verify" : "change";
    639                 $this->response->status = $this->response->sendMail($scheme, $address, $base, $email, $action) ? "next" : "error";
    640                 if ($this->response->status=="error") $this->yellow->page->error(500, "Can't send email message!");
    641             }
    642         } else {
    643             if ($this->response->status=="ok") {
    644                 $fileNameUser = $this->yellow->system->get("coreExtensionDirectory").$this->yellow->system->get("coreUserFile");
    645                 $settings = array("name" => $name, "language" => $language, "failed" => "0", "modified" => date("Y-m-d H:i:s", time()));
    646                 $this->response->status = $this->yellow->user->save($fileNameUser, $email, $settings) ? "done" : "error";
    647                 if ($this->response->status=="error") $this->yellow->page->error(500, "Can't write file '$fileNameUser'!");
    648             }
    649         }
    650         if ($this->response->status=="done") {
    651             $location = $this->yellow->lookup->normaliseUrl($scheme, $address, $base, $location);
    652             $statusCode = $this->yellow->sendStatus(303, $location);
    653         } else {
    654             $statusCode = $this->yellow->processRequest($scheme, $address, $base, $location, $fileName, false);
    655         }
    656         return $statusCode;
    657     }
    658     
    659     // Process request to change settings
    660     public function processRequestConfigure($scheme, $address, $base, $location, $fileName) {
    661         $statusCode = 0;
    662         if ($this->response->isUserAccess("configure")) {
    663             $this->response->action = "configure";
    664             $this->response->status = "ok";
    665             $sitename = trim($this->yellow->page->getRequest("sitename"));
    666             $author = trim($this->yellow->page->getRequest("author"));
    667             $email = trim($this->yellow->page->getRequest("email"));
    668             if ($email!=$this->yellow->system->get("email")) {
    669                 if (is_string_empty($email) || !filter_var($email, FILTER_VALIDATE_EMAIL)) $this->response->status = "invalid";
    670             }
    671             if ($this->response->status=="ok") {
    672                 $fileNameSystem = $this->yellow->system->get("coreExtensionDirectory").$this->yellow->system->get("coreSystemFile");
    673                 $settings = array("sitename" => $sitename, "author" => $author, "email" => $email);
    674                 $file = $this->response->getFileSystem($scheme, $address, $base, $location, $fileNameSystem, $settings);
    675                 $this->response->status = (!$file->isError() && $this->yellow->system->save($fileNameSystem, $settings)) ? "done" : "error";
    676                 if ($this->response->status=="error") $this->yellow->page->error(500, "Can't write file '$fileNameSystem'!");
    677             }
    678             if ($this->response->status=="done") {
    679                 $location = $this->yellow->lookup->normaliseUrl($scheme, $address, $base, $location);
    680                 $statusCode = $this->yellow->sendStatus(303, $location);
    681             } else {
    682                 $statusCode = $this->yellow->processRequest($scheme, $address, $base, $location, $fileName, false);
    683             }
    684         }
    685         return $statusCode;
    686     }
    687 
    688     // Process request to update website
    689     public function processRequestUpdate($scheme, $address, $base, $location, $fileName) {
    690         $statusCode = 0;
    691         if ($this->response->isUserAccess("update")) {
    692             $this->response->action = "update";
    693             $this->response->status = "ok";
    694             if ($this->yellow->page->getRequest("option")=="check") {
    695                 list($statusCode, $rawData) = $this->response->getUpdateInformation();
    696                 $this->response->status = is_string_empty($rawData) ? "ok" : "updates";
    697                 $this->response->rawDataOutput = $rawData;
    698                 if ($statusCode!=200) {
    699                     $this->response->status = "error";
    700                     $this->response->rawDataOutput = "";
    701                 }
    702             } else {
    703                 $this->response->status = $this->yellow->command("update all")==0 ? "done" : "error";
    704             }
    705             if ($this->response->status=="done") {
    706                 $location = $this->yellow->lookup->normaliseUrl($scheme, $address, $base, $location);
    707                 $statusCode = $this->yellow->sendStatus(303, $location);
    708             } else {
    709                 $statusCode = $this->yellow->processRequest($scheme, $address, $base, $location, $fileName, false);
    710             }
    711         }
    712         return $statusCode;
    713     }
    714     
    715     // Process request to create page
    716     public function processRequestCreate($scheme, $address, $base, $location, $fileName) {
    717         $statusCode = 0;
    718         if ($this->response->isUserAccess("create", $location) && !is_string_empty($this->yellow->page->getRequest("rawdataedit"))) {
    719             $this->response->rawDataSource = $this->yellow->page->getRequest("rawdatasource");
    720             $this->response->rawDataEdit = $this->yellow->page->getRequest("rawdatasource");
    721             $this->response->rawDataEndOfLine = $this->yellow->page->getRequest("rawdataendofline");
    722             $rawData = $this->yellow->page->getRequest("rawdataedit");
    723             $page = $this->response->getPageNew($scheme, $address, $base, $location, $fileName,
    724                 $rawData, $this->response->getEndOfLine());
    725             if (!$page->isError()) {
    726                 if ($this->yellow->toolbox->writeFile($page->fileName, $page->rawData, true)) {
    727                     $location = $this->yellow->lookup->normaliseUrl($scheme, $address, $base, $page->location);
    728                     $statusCode = $this->yellow->sendStatus(303, $location);
    729                 } else {
    730                     $this->yellow->page->error(500, "Can't write file '$page->fileName'!");
    731                     $statusCode = $this->yellow->processRequest($scheme, $address, $base, $location, $fileName, false);
    732                 }
    733             } else {
    734                 $this->yellow->page->error(500, $page->errorMessage);
    735                 $statusCode = $this->yellow->processRequest($scheme, $address, $base, $location, $fileName, false);
    736             }
    737         }
    738         return $statusCode;
    739     }
    740     
    741     // Process request to edit page
    742     public function processRequestEdit($scheme, $address, $base, $location, $fileName) {
    743         $statusCode = 0;
    744         if ($this->response->isUserAccess("edit", $location) && !is_string_empty($this->yellow->page->getRequest("rawdataedit"))) {
    745             $this->response->rawDataSource = $this->yellow->page->getRequest("rawdatasource");
    746             $this->response->rawDataEdit = $this->yellow->page->getRequest("rawdataedit");
    747             $this->response->rawDataEndOfLine = $this->yellow->page->getRequest("rawdataendofline");
    748             $rawDataFile = $this->yellow->toolbox->readFile($fileName);
    749             $page = $this->response->getPageEdit($scheme, $address, $base, $location, $fileName,
    750                 $this->response->rawDataSource, $this->response->rawDataEdit, $rawDataFile, $this->response->rawDataEndOfLine);
    751             if (!$page->isError()) {
    752                 if ($this->yellow->lookup->isFileLocation($location)) {
    753                     $ok = $this->yellow->toolbox->renameFile($fileName, $page->fileName, true) &&
    754                         $this->yellow->toolbox->writeFile($page->fileName, $page->rawData);
    755                 } else {
    756                     $ok = $this->yellow->toolbox->renameDirectory(dirname($fileName), dirname($page->fileName), true) &&
    757                         $this->yellow->toolbox->writeFile($page->fileName, $page->rawData);
    758                 }
    759                 if ($ok) {
    760                     $location = $this->yellow->lookup->normaliseUrl($scheme, $address, $base, $page->location);
    761                     $statusCode = $this->yellow->sendStatus(303, $location);
    762                 } else {
    763                     $this->yellow->page->error(500, "Can't write file '$page->fileName'!");
    764                     $statusCode = $this->yellow->processRequest($scheme, $address, $base, $location, $fileName, false);
    765                 }
    766             } else {
    767                 $this->yellow->page->error(500, $page->errorMessage);
    768                 $statusCode = $this->yellow->processRequest($scheme, $address, $base, $location, $fileName, false);
    769             }
    770         }
    771         return $statusCode;
    772     }
    773 
    774     // Process request to delete page
    775     public function processRequestDelete($scheme, $address, $base, $location, $fileName) {
    776         $statusCode = 0;
    777         if ($this->response->isUserAccess("delete", $location) && is_file($fileName)) {
    778             $this->response->rawDataSource = $this->yellow->page->getRequest("rawdatasource");
    779             $this->response->rawDataEdit = $this->yellow->page->getRequest("rawdatasource");
    780             $this->response->rawDataEndOfLine = $this->yellow->page->getRequest("rawdataendofline");
    781             $rawDataFile = $this->yellow->toolbox->readFile($fileName);
    782             $page = $this->response->getPageDelete($scheme, $address, $base, $location, $fileName,
    783                 $rawDataFile, $this->response->rawDataEndOfLine);
    784             if (!$page->isError()) {
    785                 if ($this->yellow->lookup->isFileLocation($location)) {
    786                     $ok = $this->response->deleteFileLocation($location, $fileName);
    787                 } else {
    788                     $ok = $this->response->deleteDirectoryLocation($location, $fileName);
    789                 }
    790                 if ($ok) {
    791                     $location = $this->yellow->lookup->normaliseUrl($scheme, $address, $base, $location);
    792                     $statusCode = $this->yellow->sendStatus(303, $location);
    793                 } else {
    794                     $this->yellow->page->error(500, "Can't delete file '$fileName'!");
    795                     $statusCode = $this->yellow->processRequest($scheme, $address, $base, $location, $fileName, false);
    796                 }
    797             } else {
    798                 $this->yellow->page->error(500, $page->errorMessage);
    799                 $statusCode = $this->yellow->processRequest($scheme, $address, $base, $location, $fileName, false);
    800             }
    801         }
    802         return $statusCode;
    803     }
    804 
    805     // Process request to restore deleted page
    806     public function processRequestRestore($scheme, $address, $base, $location, $fileName) {
    807         $statusCode = 0;
    808         if ($this->response->isUserAccess("restore", $location) && !is_file($fileName)) {
    809             $page = $this->response->getPageRestore($scheme, $address, $base, $location, $fileName);
    810             if (!$page->isError()) {
    811                 if ($this->yellow->lookup->isFileLocation($location)) {
    812                     $ok = $this->response->restoreFileLocation($location);
    813                 } else {
    814                     $ok = $this->response->restoreDirectoryLocation($location);
    815                 }
    816                 if ($ok) {
    817                     $location = $this->yellow->lookup->normaliseUrl($scheme, $address, $base, $location);
    818                     $statusCode = $this->yellow->sendStatus(303, $location);
    819                 } else {
    820                     $this->yellow->page->error(500, "Can't restore file '$fileName'!");
    821                     $statusCode = $this->yellow->processRequest($scheme, $address, $base, $location, $fileName, false);
    822                 }
    823             } else {
    824                 $this->yellow->page->error(500, $page->errorMessage);
    825                 $statusCode = $this->yellow->processRequest($scheme, $address, $base, $location, $fileName, false);
    826             }
    827         }
    828         return $statusCode;
    829     }
    830         
    831     // Process request to show preview
    832     public function processRequestPreview($scheme, $address, $base, $location, $fileName) {
    833         $page = $this->response->getPagePreview($scheme, $address, $base, $location, $fileName,
    834             $this->yellow->page->getRequest("rawdataedit"), $this->yellow->page->getRequest("rawdataendofline"));
    835         $page->headerData = array(
    836             "Cache-Control"=>"no-cache, no-store",
    837             "Content-Type"=>$this->yellow->toolbox->getMimeContentType("a.html"),
    838             "Last-Modified"=>$this->yellow->toolbox->getHttpDateFormatted(time()));
    839         $statusCode = $this->yellow->sendData($page->statusCode, $page->headerData, $page->outputData);
    840         if ($this->yellow->system->get("coreDebugMode")>=1) echo "YellowEdit::processRequestPreview file:$fileName<br />\n";
    841         return $statusCode;
    842     }
    843     
    844     // Process request to upload file
    845     public function processRequestUpload($scheme, $address, $base, $location, $fileName) {
    846         $data = array();
    847         $fileNameTemp = $_FILES["file"]["tmp_name"];
    848         $fileNameShort = preg_replace("/[^\pL\d\-\.]/u", "-", basename($_FILES["file"]["name"]));
    849         $fileSizeMax = $this->yellow->toolbox->getNumberBytes(ini_get("upload_max_filesize"));
    850         $extension = strtoloweru(($pos = strrposu($fileNameShort, ".")) ? substru($fileNameShort, $pos) : "");
    851         $extensions = preg_split("/\s*,\s*/", $this->yellow->system->get("editUploadExtensions"));
    852         if ($this->response->isUserAccess("upload", $location) && is_uploaded_file($fileNameTemp) &&
    853             filesize($fileNameTemp)<=$fileSizeMax && in_array($extension, $extensions)) {
    854             $file = $this->response->getFileUpload($scheme, $address, $base, $location, $fileNameTemp, $fileNameShort);
    855             if (!$file->isError() && $this->yellow->toolbox->copyFile($fileNameTemp, $file->fileName, true)) {
    856                 $data["location"] = $file->getLocation();
    857             } else {
    858                 $data["error"] = "Can't write file '$file->fileName'!";
    859             }
    860         } else {
    861             $data["error"] = "Can't write file '$fileNameShort'!";
    862         }
    863         $headerData = array(
    864             "Cache-Control"=>"no-cache, no-store",
    865             "Content-Type"=>$this->yellow->toolbox->getMimeContentType("a.json"),
    866             "Last-Modified"=>$this->yellow->toolbox->getHttpDateFormatted(time()));
    867         return $this->yellow->sendData(isset($data["error"]) ? 500 : 200, $headerData, json_encode($data));
    868     }
    869     
    870     // Check user authentication
    871     public function checkUserAuth($scheme, $address, $base, $location, $fileName) {
    872         $action = $this->yellow->page->getRequest("action");
    873         $authToken = $this->yellow->toolbox->getCookie("yellowauthtoken");
    874         $csrfToken = $this->yellow->toolbox->getCookie("yellowcsrftoken");
    875         if (is_string_empty($action) || $this->isRequestSameSite("POST", $scheme, $address)) {
    876             if ($action=="login") {
    877                 $email = $this->yellow->page->getRequest("email");
    878                 $password = $this->yellow->page->getRequest("password");
    879                 if ($this->response->checkAuthLogin($email, $password)) {
    880                     $this->response->createCookies($scheme, $address, $base, $email);
    881                     $this->response->userEmail = $email;
    882                     $this->response->language = $this->getUserLanguage($email);
    883                 } else {
    884                     $this->response->userFailedError = "login";
    885                     $this->response->userFailedEmail = $email;
    886                     $this->response->userFailedExpire = PHP_INT_MAX;
    887                 }
    888             } elseif (!is_string_empty($authToken) && !is_string_empty($csrfToken)) {
    889                 $csrfTokenReceived = isset($_POST["yellowcsrftoken"]) ? $_POST["yellowcsrftoken"] : "";
    890                 $csrfTokenIrrelevant = is_string_empty($action);
    891                 if ($this->response->checkAuthToken($authToken, $csrfToken, $csrfTokenReceived, $csrfTokenIrrelevant)) {
    892                     $this->response->userEmail = $email = $this->response->getAuthEmail($authToken);
    893                     $this->response->language = $this->getUserLanguage($email);
    894                 } else {
    895                     $this->response->userFailedError = "auth";
    896                     $this->response->userFailedEmail = $this->response->getAuthEmail($authToken);
    897                     $this->response->userFailedExpire = $this->response->getAuthExpire($authToken);
    898                 }
    899             }
    900             $this->yellow->user->set($this->response->userEmail);
    901         }
    902         return $this->response->isUser();
    903     }
    904 
    905     // Check user without authentication
    906     public function checkUserUnauth($scheme, $address, $base, $location, $fileName) {
    907         $ok = false;
    908         $action = $this->yellow->page->getRequest("action");
    909         if (is_string_empty($action) || $action=="signup" || $action=="forgot") {
    910             $ok = true;
    911         } elseif ($this->yellow->page->isRequest("actiontoken")) {
    912             $actionToken = $this->yellow->page->getRequest("actiontoken");
    913             $email = $this->yellow->page->getRequest("email");
    914             $action = $this->yellow->page->getRequest("action");
    915             $expire = $this->yellow->page->getRequest("expire");
    916             $language = $this->yellow->page->getRequest("language");
    917             if ($this->response->checkActionToken($actionToken, $email, $action, $expire)) {
    918                 $ok = true;
    919                 $this->response->language = $this->getActionLanguage($language);
    920             } else {
    921                 $this->response->userFailedError = "action";
    922                 $this->response->userFailedEmail = $email;
    923                 $this->response->userFailedExpire = $expire;
    924             }
    925         }
    926         return $ok;
    927     }
    928 
    929     // Check user failed
    930     public function checkUserFailed($scheme, $address, $base, $location, $fileName) {
    931         if (!is_string_empty($this->response->userFailedError)) {
    932             if ($this->response->userFailedExpire>time() && $this->yellow->user->isExisting($this->response->userFailedEmail)) {
    933                 $email = $this->response->userFailedEmail;
    934                 $failed = $this->yellow->user->getUser("failed", $email)+1;
    935                 $fileNameUser = $this->yellow->system->get("coreExtensionDirectory").$this->yellow->system->get("coreUserFile");
    936                 $status = $this->yellow->user->save($fileNameUser, $email, array("failed" => $failed)) ? "ok" : "error";
    937                 if ($status=="error") $this->yellow->page->error(500, "Can't write file '$fileNameUser'!");
    938                 if ($failed==$this->yellow->system->get("editBruteForceProtection")) {
    939                     $statusBeforeProtection = $this->yellow->user->getUser("status", $email);
    940                     $statusAfterProtection = ($statusBeforeProtection=="active" || $statusBeforeProtection=="inactive") ? "inactive" : "failed";
    941                     if ($status=="ok") {
    942                         $status = $this->yellow->user->save($fileNameUser, $email, array("status" => $statusAfterProtection)) ? "ok" : "error";
    943                         if ($status=="error") $this->yellow->page->error(500, "Can't write file '$fileNameUser'!");
    944                     }
    945                     if ($status=="ok" && $statusBeforeProtection=="active") {
    946                         $status = $this->response->sendMail($scheme, $address, $base, $email, "reactivate") ? "done" : "error";
    947                         if ($status=="error") $this->yellow->page->error(500, "Can't send email message!");
    948                     }
    949                 }
    950             }
    951             if ($this->response->userFailedError=="login" || $this->response->userFailedError=="auth") {
    952                 $this->response->destroyCookies($scheme, $address, $base);
    953                 $this->response->status = "error";
    954                 $this->yellow->page->error(430);
    955             } else {
    956                 $this->response->status = "error";
    957                 $this->yellow->page->error(500, "Link has expired!");
    958             }
    959         }
    960     }
    961     
    962     // Return user status changes
    963     public function getUserStatus($email, $action) {
    964         switch ($action) {
    965             case "confirm":     $statusExpected = "unconfirmed"; break;
    966             case "approve":     $statusExpected = "unapproved"; break;
    967             case "recover":     $statusExpected = "active"; break;
    968             case "reactivate":  $statusExpected = "inactive"; break;
    969             case "verify":      $statusExpected = "unverified"; break;
    970             case "change":      $statusExpected = "active"; break;
    971             case "remove":      $statusExpected = "active"; break;
    972         }
    973         return $this->yellow->user->getUser("status", $email)==$statusExpected ? "ok" : "done";
    974     }
    975 
    976     // Return user account changes
    977     public function getUserAccount($action, $email, $password) {
    978         $status = null;
    979         foreach ($this->yellow->extension->data as $key=>$value) {
    980             if (method_exists($value["object"], "onEditUserAccount")) {
    981                 $status = $value["object"]->onEditUserAccount($action, $email, $password);
    982                 if (!is_null($status)) break;
    983             }
    984         }
    985         if (is_null($status)) {
    986             $status = "ok";
    987             if (!is_string_empty($password) && strlenu($password)<$this->yellow->system->get("editUserPasswordMinLength")) $status = "short";
    988             if (!is_string_empty($password) && $password==$email) $status = "weak";
    989             if (!is_string_empty($email) && !filter_var($email, FILTER_VALIDATE_EMAIL)) $status = "invalid";
    990         }
    991         return $status;
    992     }
    993     
    994     // Return user language
    995     public function getUserLanguage($email) {
    996         $language = $this->yellow->user->getUser("language", $email);
    997         if (!$this->yellow->language->isExisting($language)) $language = $this->yellow->system->get("language");
    998         return $language;
    999     }
   1000 
   1001     // Return action language
   1002     public function getActionLanguage($language) {
   1003         if (!$this->yellow->language->isExisting($language)) $language = $this->yellow->system->get("language");
   1004         return $language;
   1005     }
   1006     
   1007     // Check if user account is taken
   1008     public function isUserAccountTaken($email) {
   1009         $taken = false;
   1010         if ($this->yellow->user->isExisting($email)) {
   1011             $status = $this->yellow->user->getUser("status", $email);
   1012             $reserved = strtotime($this->yellow->user->getUser("modified", $email)) + 60*60*24;
   1013             if ($status=="active" || $status=="inactive" || $reserved>time()) $taken = true;
   1014         }
   1015         return $taken;
   1016     }
   1017     
   1018     // Check if request came from same site
   1019     public function isRequestSameSite($method, $scheme, $address) {
   1020         $origin = "";
   1021         if (preg_match("#^(\w+)://([^/]+)(.*)$#", $this->yellow->toolbox->getServer("HTTP_REFERER"), $matches)) $origin = "$matches[1]://$matches[2]";
   1022         if ($this->yellow->toolbox->getServer("HTTP_ORIGIN")) $origin = $this->yellow->toolbox->getServer("HTTP_ORIGIN");
   1023         return $this->yellow->toolbox->getServer("REQUEST_METHOD")==$method && $origin=="$scheme://$address";
   1024     }
   1025     
   1026     // Check if edit location
   1027     public function isEditLocation($location) {
   1028         $locationLength = strlenu($this->yellow->system->get("editLocation"));
   1029         return substru($location, 0, $locationLength)==$this->yellow->system->get("editLocation");
   1030     }
   1031 }
   1032     
   1033 class YellowEditResponse {
   1034     public $yellow;             // access to API
   1035     public $extension;          // access to extension
   1036     public $userEmail;          // user email
   1037     public $userFailedError;    // error of failed authentication
   1038     public $userFailedEmail;    // email of failed authentication
   1039     public $userFailedExpire;   // expiration time of failed authentication
   1040     public $rawDataSource;      // raw data of page for comparison
   1041     public $rawDataEdit;        // raw data of page for editing
   1042     public $rawDataOutput;      // raw data of dynamic output
   1043     public $rawDataReadonly;    // raw data is read only? (boolean)
   1044     public $rawDataEndOfLine;   // end of line format for raw data
   1045     public $language;           // response language
   1046     public $action;             // response action
   1047     public $status;             // response status
   1048     
   1049     public function __construct($yellow) {
   1050         $this->yellow = $yellow;
   1051         $this->extension = $yellow->extension->get("edit");
   1052         $this->userEmail = "";
   1053     }
   1054     
   1055     // Process page data
   1056     public function processPageData($page) {
   1057         if ($this->isUser()) {
   1058             if (is_string_empty($this->rawDataSource)) $this->rawDataSource = $page->rawData;
   1059             if (is_string_empty($this->rawDataEdit)) $this->rawDataEdit = $page->rawData;
   1060             if (is_string_empty($this->rawDataEndOfLine)) $this->rawDataEndOfLine = $this->getEndOfLine($page->rawData);
   1061             if ($page->statusCode==404 || $this->yellow->toolbox->isLocationArguments()) {
   1062                 $this->rawDataEdit = $this->getRawDataGenerated($page);
   1063                 $this->rawDataReadonly = true;
   1064             }
   1065             if ($page->statusCode==434 || $page->statusCode==435)  {
   1066                 $this->rawDataEdit = $this->getRawDataNew($page, true);
   1067                 $this->rawDataReadonly = false;
   1068             }
   1069         }
   1070         if (is_string_empty($this->language)) $this->language = $page->get("language");
   1071         if (is_string_empty($this->action)) $this->action = $this->isUser() ? "none" : "login";
   1072         if (is_string_empty($this->status)) $this->status = "none";
   1073         if ($this->status=="error") $this->action = "error";
   1074     }
   1075     
   1076     // Return new page
   1077     public function getPageNew($scheme, $address, $base, $location, $fileName, $rawData, $endOfLine) {
   1078         $rawData = $this->yellow->lookup->normaliseLines($rawData, $endOfLine);
   1079         $page = new YellowPage($this->yellow);
   1080         $page->setRequestInformation($scheme, $address, $base, $location, $fileName, false);
   1081         $page->parseMeta($rawData, 200);
   1082         $this->editContentFile($page, "precreate", $this->userEmail);
   1083         if ($this->yellow->content->find($page->location)) {
   1084             $page->location = $this->getPageNewLocation($page->rawData, $page->location, $page->get("editNewLocation"));
   1085             $page->fileName = $this->getPageNewFile($page->location, $page->fileName, $page->get("editNewPrefix"));
   1086             $pageCounter = 0;
   1087             while ($this->yellow->content->find($page->location) || is_string_empty($page->fileName)) {
   1088                 $page->rawData = $this->yellow->toolbox->setMetaData($page->rawData, "title", $this->getTitleNext($page->rawData));
   1089                 $page->rawData = $this->yellow->lookup->normaliseLines($page->rawData, $endOfLine);
   1090                 $page->location = $this->getPageNewLocation($page->rawData, $page->location, $page->get("editNewLocation"));
   1091                 $page->fileName = $this->getPageNewFile($page->location, $page->fileName, $page->get("editNewPrefix"));
   1092                 if (++$pageCounter>999) break;
   1093             }
   1094             if ($this->yellow->content->find($page->location) || is_string_empty($page->fileName)) {
   1095                 $page->error(500, "Page '".$page->get("title")."' is not possible!");
   1096             }
   1097         } else {
   1098             $page->fileName = $this->getPageNewFile($page->location);
   1099         }
   1100         if (!$this->isUserAccess("create", $page->location)) {
   1101             $page->error(500, "Page '".$page->get("title")."' is restricted!");
   1102         }
   1103         $this->editContentFile($page, "create", $this->userEmail);
   1104         return $page;
   1105     }
   1106     
   1107     // Return modified page
   1108     public function getPageEdit($scheme, $address, $base, $location, $fileName, $rawDataSource, $rawDataEdit, $rawDataFile, $endOfLine) {
   1109         $rawDataSource = $this->yellow->lookup->normaliseLines($rawDataSource, $endOfLine);
   1110         $rawDataEdit = $this->yellow->lookup->normaliseLines($rawDataEdit, $endOfLine);
   1111         $rawDataFile = $this->yellow->lookup->normaliseLines($rawDataFile, $endOfLine);
   1112         $rawData = $this->extension->merge->merge($rawDataSource, $rawDataEdit, $rawDataFile);
   1113         $page = new YellowPage($this->yellow);
   1114         $page->setRequestInformation($scheme, $address, $base, $location, $fileName, false);
   1115         $page->parseMeta($rawData, 200);
   1116         $this->editContentFile($page, "preedit", $this->userEmail);
   1117         $pageSource = new YellowPage($this->yellow);
   1118         $pageSource->setRequestInformation($scheme, $address, $base, $location, $fileName, false);
   1119         $pageSource->parseMeta($rawDataSource, 200);
   1120         if ($this->isMetaModified($pageSource, $page) && $page->location!=$this->yellow->content->getHomeLocation($page->location)) {
   1121             $page->location = $this->getPageNewLocation($page->rawData, $page->location, $page->get("editNewLocation"), true);
   1122             $page->fileName = $this->getPageNewFile($page->location, $page->fileName, $page->get("editNewPrefix"));
   1123             if ($page->location!=$pageSource->location && ($this->yellow->content->find($page->location) || is_string_empty($page->fileName))) {
   1124                 $page->error(500, "Page '".$page->get("title")."' is not possible!");
   1125             }
   1126         }
   1127         if (is_string_empty($page->rawData)) $page->error(500, "Page has been modified by someone else!");
   1128         if (!$this->isUserAccess("edit", $page->location) ||
   1129             !$this->isUserAccess("edit", $pageSource->location)) {
   1130             $page->error(500, "Page '".$page->get("title")."' is restricted!");
   1131         }
   1132         $this->editContentFile($page, "edit", $this->userEmail);
   1133         return $page;
   1134     }
   1135     
   1136     // Return deleted page
   1137     public function getPageDelete($scheme, $address, $base, $location, $fileName, $rawData, $endOfLine) {
   1138         $rawData = $this->yellow->lookup->normaliseLines($rawData, $endOfLine);
   1139         $page = new YellowPage($this->yellow);
   1140         $page->setRequestInformation($scheme, $address, $base, $location, $fileName, false);
   1141         $page->parseMeta($rawData, 200);
   1142         if (!$this->isUserAccess("delete", $page->location)) {
   1143             $page->error(500, "Page '".$page->get("title")."' is restricted!");
   1144         }
   1145         $this->editContentFile($page, "delete", $this->userEmail);
   1146         return $page;
   1147     }
   1148 
   1149     // Return restored page
   1150     public function getPageRestore($scheme, $address, $base, $location, $fileName) {
   1151         $page = new YellowPage($this->yellow);
   1152         $page->setRequestInformation($scheme, $address, $base, $location, $fileName, false);
   1153         $page->parseMeta("");
   1154         if (!$this->isUserAccess("restore", $page->location)) {
   1155             $page->error(500, "Page '".$page->get("title")."' is restricted!");
   1156         }
   1157         $this->editContentFile($page, "restore", $this->userEmail);
   1158         return $page;
   1159     }
   1160     
   1161     // Return preview page
   1162     public function getPagePreview($scheme, $address, $base, $location, $fileName, $rawData, $endOfLine) {
   1163         $rawData = $this->yellow->lookup->normaliseLines($rawData, $endOfLine);
   1164         $page = new YellowPage($this->yellow);
   1165         $page->setRequestInformation($scheme, $address, $base, $location, $fileName, false);
   1166         $page->parseMeta($rawData, 200);
   1167         $this->yellow->language->set($page->get("language"));
   1168         $class = "page-preview layout-".$page->get("layout");
   1169         $output = "<div class=\"".htmlspecialchars($class)."\"><div class=\"content\"><div class=\"main\">";
   1170         if ($this->yellow->system->get("editToolbarButtons")!="none") $output .= "<h1>".$page->getHtml("titleContent")."</h1>\n";
   1171         $output .= $page->getContentHtml();
   1172         $output .= "</div></div></div>";
   1173         $page->statusCode = 200;
   1174         $page->outputData = $output;
   1175         return $page;
   1176     }
   1177     
   1178     // Return uploaded file
   1179     public function getFileUpload($scheme, $address, $base, $pageLocation, $fileNameTemp, $fileNameShort) {
   1180         $file = new YellowPage($this->yellow);
   1181         $file->setRequestInformation($scheme, $address, $base, "/".$fileNameShort, $fileNameShort, false);
   1182         $file->parseMeta(null);
   1183         $file->set("fileNameTemp", $fileNameTemp);
   1184         $file->set("fileNameShort", $fileNameShort);
   1185         if ($file->get("type")=="html" || $file->get("type")=="svg") {
   1186             $fileData = $this->yellow->toolbox->readFile($fileNameTemp);
   1187             $fileData = $this->yellow->lookup->normaliseData($fileData, $file->get("type"));
   1188             if (is_string_empty($fileData) || !$this->yellow->toolbox->writeFile($fileNameTemp, $fileData)) {
   1189                 $file->error(500, "Can't write file '$fileNameTemp'!");
   1190             }
   1191         }
   1192         $this->editMediaFile($file, "upload", $this->userEmail);
   1193         $fileNameShort = basename($file->fileName);
   1194         $file->location = $this->getFileNewLocation($fileNameShort, $pageLocation, $file->get("fileNewLocation"));
   1195         $file->fileName = substru($file->location, 1);
   1196         $fileCounter = 0;
   1197         while (is_file($file->fileName)) {
   1198             $fileNameShort = $this->getFileNext(basename($file->fileName));
   1199             $file->location = $this->getFileNewLocation($fileNameShort, $pageLocation, $file->get("fileNewLocation"));
   1200             $file->fileName = substru($file->location, 1);
   1201             if (++$fileCounter>999) break;
   1202         }
   1203         if (is_file($file->fileName)) $file->error(500, "File '".$file->get("fileNameShort")."' is not possible!");
   1204         return $file;
   1205     }
   1206 
   1207     // Return system file
   1208     public function getFileSystem($scheme, $address, $base, $pageLocation, $fileNameSystem, $settings) {
   1209         $file = new YellowPage($this->yellow);
   1210         $file->setRequestInformation($scheme, $address, $base, "/".$fileNameSystem, $fileNameSystem, false);
   1211         $file->parseMeta(null);
   1212         foreach ($settings as $key=>$value) $file->set($key, $value);
   1213         $this->editSystemFile($file, "configure", $this->userEmail);
   1214         return $file;
   1215     }
   1216     
   1217     // Return page data including status information
   1218     public function getPageData($page) {
   1219         $data = array();
   1220         $data["scheme"] = $this->yellow->page->scheme;
   1221         $data["address"] = $this->yellow->page->address;
   1222         $data["base"] = $this->yellow->page->base;
   1223         $data["location"] = $this->yellow->page->location;
   1224         if ($this->isUser()) {
   1225             $data["title"] = $this->yellow->toolbox->getMetaData($this->rawDataEdit, "title");
   1226             $data["rawDataSource"] = $this->rawDataSource;
   1227             $data["rawDataEdit"] = $this->rawDataEdit;
   1228             $data["rawDataNew"] = $this->getRawDataNew($page);
   1229             $data["rawDataOutput"] = strval($this->rawDataOutput);
   1230             $data["rawDataReadonly"] = intval($this->rawDataReadonly);
   1231             $data["rawDataEndOfLine"] = $this->rawDataEndOfLine;
   1232         }
   1233         if ($this->action!="none") $data = array_merge($data, $this->getRequestData());
   1234         $data["action"] = $this->action;
   1235         $data["status"] = $this->status;
   1236         $data["statusCode"] = $this->yellow->page->statusCode;
   1237         return $data;
   1238     }
   1239     
   1240     // Return system data
   1241     public function getSystemData() {
   1242         $data = array();
   1243         $data["coreServerScheme"] = $this->yellow->system->get("coreServerScheme");
   1244         $data["coreServerAddress"] = $this->yellow->system->get("coreServerAddress");
   1245         $data["coreServerBase"] = $this->yellow->system->get("coreServerBase");
   1246         $data["coreDebugMode"] = $this->yellow->system->get("coreDebugMode");
   1247         $data = array_merge($data, $this->yellow->system->getSettings("", "Location"));
   1248         if ($this->isUser()) {
   1249             $data["coreFileSizeMax"] = $this->yellow->toolbox->getNumberBytes(ini_get("upload_max_filesize"));
   1250             $data["coreProductRelease"] = "Datenstrom Yellow ".YellowCore::RELEASE;
   1251             $data["coreExtensions"] = array();
   1252             foreach ($this->yellow->extension->data as $key=>$value) {
   1253                 $data["coreExtensions"][$key] = $value["class"];
   1254             }
   1255             $data["coreLanguages"] = array();
   1256             foreach ($this->yellow->toolbox->enumerate("language") as $language) {
   1257                 $data["coreLanguages"][$language] = $this->yellow->language->getTextHtml("languageDescription", $language);
   1258             }
   1259             $data["editSettingsActions"] = $this->getSettingsActions();
   1260             $data["editUploadExtensions"] = $this->yellow->system->get("editUploadExtensions");
   1261             $data["editKeyboardShortcuts"] = $this->yellow->system->get("editKeyboardShortcuts");
   1262             $data["editToolbarButtons"] = $this->getToolbarButtons();
   1263             $data["editStatusValues"] = $this->getStatusValues();
   1264             $data["emojiToolbarButtons"] = $this->yellow->system->get("emojiToolbarButtons");
   1265             $data["iconToolbarButtons"] = $this->yellow->system->get("iconToolbarButtons");
   1266             if ($this->isUserAccess("configure")) {
   1267                 $data["sitename"] = $this->yellow->system->get("sitename");
   1268                 $data["author"] = $this->yellow->system->get("author");
   1269                 $data["email"] = $this->yellow->system->get("email");
   1270             }
   1271         } else {
   1272             $data["editLoginEmail"] = $this->yellow->page->get("editLoginEmail");
   1273             $data["editLoginPassword"] = $this->yellow->page->get("editLoginPassword");
   1274             $data["editLoginRestriction"] = intval($this->isLoginRestriction());
   1275         }
   1276         return $data;
   1277     }
   1278     
   1279     // Return user data
   1280     public function getUserData() {
   1281         $data = array();
   1282         if ($this->isUser()) {
   1283             $data["email"] = $this->userEmail;
   1284             $data["name"] = $this->yellow->user->getUser("name", $this->userEmail);
   1285             $data["description"] = $this->yellow->user->getUser("description", $this->userEmail);
   1286             $data["language"] = $this->yellow->user->getUser("language", $this->userEmail);
   1287             $data["status"] = $this->yellow->user->getUser("status", $this->userEmail);
   1288             $data["access"] = $this->yellow->user->getUser("access", $this->userEmail);
   1289             $data["home"] = $this->yellow->user->getUser("home", $this->userEmail);
   1290         }
   1291         return $data;
   1292     }
   1293     
   1294     // Return language data
   1295     public function getLanguageData() {
   1296         $dataLanguage = $this->yellow->language->getSettings("language", "", $this->language);
   1297         $dataEdit = $this->yellow->language->getSettings("edit", "", $this->language);
   1298         return array_merge($dataLanguage, $dataEdit);
   1299     }
   1300     
   1301     // Return request data
   1302     public function getRequestData() {
   1303         $data = array();
   1304         foreach ($_REQUEST as $key=>$value) {
   1305             if ($key=="password" || $key=="yellowauthtoken" || $key=="yellowcsrftoken" ||
   1306                 substru($key, 0, 7)=="rawdata") {
   1307                 continue;
   1308             }
   1309             $data["request".ucfirst($key)] = trim($value);
   1310         }
   1311         return $data;
   1312     }
   1313     
   1314     // Return settings actions
   1315     public function getSettingsActions() {
   1316         $settingsActions = "account";
   1317         if ($this->isUserAccess("configure")) $settingsActions .= ", configure";
   1318         if ($this->isUserAccess("update")) $settingsActions .= ", update";
   1319         return $settingsActions=="account" ? "none" : $settingsActions;
   1320     }
   1321     
   1322     // Return toolbar buttons
   1323     public function getToolbarButtons() {
   1324         $toolbarButtons = $this->yellow->system->get("editToolbarButtons");
   1325         if ($toolbarButtons=="auto") {
   1326             $toolbarButtons = "format, bold, italic, strikethrough, code, separator, list, link, file";
   1327             if ($this->yellow->extension->isExisting("emoji")) $toolbarButtons .= ", emoji";
   1328             if ($this->yellow->extension->isExisting("icon")) $toolbarButtons .= ", icon";
   1329             $toolbarButtons .= ", status, preview";
   1330         }
   1331         return $toolbarButtons;
   1332     }
   1333     
   1334     // Return status values
   1335     public function getStatusValues() {
   1336         $statusValues = "";
   1337         if ($this->yellow->extension->isExisting("private")) $statusValues .= ", private";
   1338         if ($this->yellow->extension->isExisting("draft")) $statusValues .= ", draft";
   1339         $statusValues .= ", unlisted";
   1340         return ltrim($statusValues, ", ");
   1341     }
   1342     
   1343     // Return end of line format
   1344     public function getEndOfLine($rawData = "") {
   1345         $endOfLine = $this->yellow->system->get("editEndOfLine");
   1346         if ($endOfLine=="auto") {
   1347             $rawData = is_string_empty($rawData) ? PHP_EOL : substru($rawData, 0, 4096);
   1348             $endOfLine = strposu($rawData, "\r")===false ? "lf" : "crlf";
   1349         }
   1350         return $endOfLine;
   1351     }
   1352     
   1353     // Return update information
   1354     public function getUpdateInformation() {
   1355         $statusCode = 200;
   1356         $rawData = "";
   1357         if ($this->yellow->extension->isExisting("update")) {
   1358             list($statusCodeCurrent, $settingsCurrent) = $this->yellow->extension->get("update")->getExtensionSettings(true);
   1359             list($statusCodeLatest, $settingsLatest) = $this->yellow->extension->get("update")->getExtensionSettings(false);
   1360             $statusCode = max($statusCodeCurrent, $statusCodeLatest);
   1361             foreach ($settingsCurrent as $key=>$value) {
   1362                 if ($settingsLatest->isExisting($key)) {
   1363                     $versionCurrent = $settingsCurrent[$key]->get("version");
   1364                     $versionLatest = $settingsLatest[$key]->get("version");
   1365                     if (strnatcasecmp($versionCurrent, $versionLatest)<0) {
   1366                         $rawData .= htmlspecialchars(ucfirst($key)." $versionLatest")."<br />";
   1367                     }
   1368                 }
   1369             }
   1370             if (!is_string_empty($rawData)) $rawData = "<p>$rawData</p>\n";
   1371         }
   1372         return array($statusCode, $rawData);
   1373     }
   1374 
   1375     // Return raw data for generated page
   1376     public function getRawDataGenerated($page) {
   1377         $title = $page->get("title");
   1378         $text = $this->yellow->language->getText("editDataGenerated", $page->get("language"));
   1379         return "---\nTitle: $title\n---\n$text";
   1380     }
   1381     
   1382     // Return raw data for new page
   1383     public function getRawDataNew($page, $customTitle = false) {
   1384         $fileName = "";
   1385         foreach ($this->yellow->content->path($page->location)->reverse() as $ancestor) {
   1386             if ($ancestor->isExisting("layoutNew")) {
   1387                 $name = $this->yellow->lookup->normaliseName($ancestor->get("layoutNew"));
   1388                 $location = $this->yellow->content->getHomeLocation($page->location)."shared/";
   1389                 $fileName = $this->yellow->lookup->findFileFromContentLocation($location, true).$this->yellow->system->get("editNewFile");
   1390                 $fileName = str_replace("(.*)", $name, $fileName);
   1391                 if (is_file($fileName)) break;
   1392             }
   1393         }
   1394         if (!is_file($fileName)) {
   1395             $name = $this->yellow->lookup->normaliseName($this->yellow->system->get("layout"));
   1396             $location = $this->yellow->content->getHomeLocation($page->location)."shared/";
   1397             $fileName = $this->yellow->lookup->findFileFromContentLocation($location, true).$this->yellow->system->get("editNewFile");
   1398             $fileName = str_replace("(.*)", $name, $fileName);
   1399         }
   1400         if (is_file($fileName)) {
   1401             $rawData = $this->yellow->toolbox->readFile($fileName);
   1402             $rawData = preg_replace("/@timestamp/i", time(), $rawData);
   1403             $rawData = preg_replace("/@datetime/i", date("Y-m-d H:i:s"), $rawData);
   1404             $rawData = preg_replace("/@date/i", date("Y-m-d"), $rawData);
   1405             $rawData = preg_replace("/@usershort/i", strtok($this->yellow->user->getUser("name", $this->userEmail), " "), $rawData);
   1406             $rawData = preg_replace("/@username/i", $this->yellow->user->getUser("name", $this->userEmail), $rawData);
   1407             $rawData = preg_replace("/@userlanguage/i", $this->yellow->user->getUser("language", $this->userEmail), $rawData);
   1408         } else {
   1409             $rawData = "---\nTitle: Page\n---\n";
   1410         }
   1411         if ($customTitle) {
   1412             $title = $this->yellow->toolbox->createTextTitle($page->location);
   1413             $rawData = $this->yellow->toolbox->setMetaData($rawData, "title", $title);
   1414         }
   1415         return $rawData;
   1416     }
   1417     
   1418     // Return location for new/modified page
   1419     public function getPageNewLocation($rawData, $pageLocation, $editNewLocation, $pageMatchLocation = false) {
   1420         $location = is_string_empty($editNewLocation) ? "@title" : $editNewLocation;
   1421         $location = preg_replace("/@title/i", $this->getPageNewTitle($rawData), $location);
   1422         $location = preg_replace("/@timestamp/i", $this->getPageNewData($rawData, "published", "U"), $location);
   1423         $location = preg_replace("/@date/i", $this->getPageNewData($rawData, "published", "Y-m-d"), $location);
   1424         $location = preg_replace("/@year/i", $this->getPageNewData($rawData, "published", "Y"), $location);
   1425         $location = preg_replace("/@month/i", $this->getPageNewData($rawData, "published", "m"), $location);
   1426         $location = preg_replace("/@day/i", $this->getPageNewData($rawData, "published", "d"), $location);
   1427         $location = preg_replace("/@tag/i", $this->getPageNewData($rawData, "tag"), $location);
   1428         $location = preg_replace("/@author/i", $this->getPageNewData($rawData, "author"), $location);
   1429         if (!preg_match("/^\//", $location)) {
   1430             if ($this->yellow->lookup->isFileLocation($pageLocation) || !$pageMatchLocation) {
   1431                 $location = $this->yellow->lookup->getDirectoryLocation($pageLocation).$location;
   1432             } else {
   1433                 $location = $this->yellow->lookup->getDirectoryLocation(rtrim($pageLocation, "/")).$location;
   1434             }
   1435         }
   1436         if (preg_match("/\d/", $location)) {
   1437             $locationNew = "";
   1438             $tokens = explode("/", $location);
   1439             for ($i=1; $i<count($tokens); ++$i) {
   1440                 $locationNew .= "/".$this->yellow->lookup->normaliseToken($tokens[$i]);
   1441             }
   1442             $location = $locationNew;
   1443         }
   1444         if ($pageMatchLocation) {
   1445             $location = rtrim($location, "/").($this->yellow->lookup->isFileLocation($pageLocation) ? "" : "/");
   1446         }
   1447         return $location;
   1448     }
   1449     
   1450     // Return title for new/modified page
   1451     public function getPageNewTitle($rawData) {
   1452         $title = $this->yellow->toolbox->getMetaData($rawData, "title");
   1453         $titleSlug = $this->yellow->toolbox->getMetaData($rawData, "titleSlug");
   1454         $value = is_string_empty($titleSlug) ? $title : $titleSlug;
   1455         $value = $this->yellow->lookup->normaliseName($value, false, false, true);
   1456         return trim(preg_replace("/-+/", "-", $value), "-");
   1457     }
   1458     
   1459     // Return data for new/modified page
   1460     public function getPageNewData($rawData, $key, $dateFormat = "") {
   1461         $value = $this->yellow->toolbox->getMetaData($rawData, $key);
   1462         if (preg_match("/^(.*?)\,(.*)$/", $value, $matches)) $value = $matches[1];
   1463         if (!is_string_empty($dateFormat)) $value = date($dateFormat, strtotime($value));
   1464         if (is_string_empty($value)) $value = "none";
   1465         $value = $this->yellow->lookup->normaliseName($value, false, false, true);
   1466         return trim(preg_replace("/-+/", "-", $value), "-");
   1467     }
   1468     
   1469     // Return file name for new/modified page
   1470     public function getPageNewFile($location, $pageFileName = "", $pagePrefix = "") {
   1471         $fileName = $this->yellow->lookup->findFileFromContentLocation($location);
   1472         if (!is_string_empty($fileName)) {
   1473             if (!is_dir(dirname($fileName))) {
   1474                 $path = "";
   1475                 $tokens = explode("/", $fileName);
   1476                 for ($i=0; $i<count($tokens)-1; ++$i) {
   1477                     if (!is_dir($path.$tokens[$i])) {
   1478                         if (!preg_match("/^[\d\-\_\.]+(.*)$/", $tokens[$i])) {
   1479                             $number = 1;
   1480                             foreach ($this->yellow->toolbox->getDirectoryEntries($path, "/^[\d\-\_\.]+(.*)$/", true, true, false) as $entry) {
   1481                                 if ($number!=1 && $number!=intval($entry)) break;
   1482                                 $number = intval($entry)+1;
   1483                             }
   1484                             $tokens[$i] = "$number-".$tokens[$i];
   1485                         }
   1486                         $tokens[$i] = $this->yellow->lookup->normaliseName($tokens[$i], false, false, true);
   1487                     }
   1488                     $path .= $tokens[$i]."/";
   1489                 }
   1490                 $fileName = $path.$tokens[$i];
   1491                 $pageFileName = is_string_empty($pageFileName) ? $fileName : $pageFileName;
   1492             }
   1493             $prefix = $this->getPageNewPrefix($location, $pageFileName, $pagePrefix);
   1494             if ($this->yellow->lookup->isFileLocation($location)) {
   1495                 if (preg_match("#^(.*)\/(.+?)$#", $fileName, $matches)) {
   1496                     $path = $matches[1];
   1497                     $text = $this->yellow->lookup->normaliseName($matches[2], true, true);
   1498                     if (preg_match("/^[\d\-\_\.]*$/", $text)) $prefix = "";
   1499                     $fileName = $path."/".$prefix.$text.$this->yellow->system->get("coreContentExtension");
   1500                 }
   1501             } else {
   1502                 if (preg_match("#^(.*)\/(.+?)$#", dirname($fileName), $matches)) {
   1503                     $path = $matches[1];
   1504                     $text = $this->yellow->lookup->normaliseName($matches[2], true, false);
   1505                     if (preg_match("/^[\d\-\_\.]*$/", $text)) $prefix = "";
   1506                     $fileName = $path."/".$prefix.$text."/".$this->yellow->system->get("coreContentDefaultFile");
   1507                 }
   1508             }
   1509         }
   1510         return $fileName;
   1511     }
   1512     
   1513     // Return prefix for new/modified page
   1514     public function getPageNewPrefix($location, $pageFileName, $pagePrefix) {
   1515         if (is_string_empty($pagePrefix)) {
   1516             if ($this->yellow->lookup->isFileLocation($location)) {
   1517                 if (preg_match("#^(.*)\/(.+?)$#", $pageFileName, $matches)) $pagePrefix = $matches[2];
   1518             } else {
   1519                 if (preg_match("#^(.*)\/(.+?)$#", dirname($pageFileName), $matches)) $pagePrefix = $matches[2];
   1520             }
   1521         }
   1522         return $this->yellow->lookup->normalisePrefix($pagePrefix, true);
   1523     }
   1524     
   1525     // Return location for new file
   1526     public function getFileNewLocation($fileNameShort, $pageLocation, $fileNewLocation) {
   1527         $location = is_string_empty($fileNewLocation) ? $this->yellow->system->get("editUploadNewLocation") : $fileNewLocation;
   1528         $location = preg_replace("/@timestamp/i", time(), $location);
   1529         $location = preg_replace("/@date/i", date("Y-m-d"), $location);
   1530         $location = preg_replace("/@type/i", $this->yellow->toolbox->getFileType($fileNameShort), $location);
   1531         $location = preg_replace("/@group/i", $this->getFileNewGroup($fileNameShort), $location);
   1532         $location = preg_replace("/@folder/i", $this->getFileNewFolder($pageLocation), $location);
   1533         $location = preg_replace("/@filename/i", strtoloweru($fileNameShort), $location);
   1534         if (!preg_match("/^\//", $location)) {
   1535             $location = $this->yellow->system->get("coreMediaLocation").$location;
   1536         }
   1537         return $location;
   1538     }
   1539     
   1540     // Return group for new file
   1541     public function getFileNewGroup($fileNameShort) {
   1542         $group = "none";
   1543         $fileType = $this->yellow->toolbox->getFileType($fileNameShort);
   1544         $locationMedia = $this->yellow->system->get("coreMediaLocation");
   1545         $locationGroup = $this->yellow->system->get("coreDownloadLocation");
   1546         if (preg_match("/(gif|jpeg|jpg|png|svg)$/", $fileType)) {
   1547             $locationGroup = $this->yellow->system->get("coreImageLocation");
   1548         }
   1549         if (preg_match("#^$locationMedia(.+?)\/#", $locationGroup, $matches)) {
   1550             $group = strtoloweru($matches[1]);
   1551         }
   1552         return $group;
   1553     }
   1554 
   1555     // Return folder for new file
   1556     public function getFileNewFolder($pageLocation) {
   1557         $parentTopLocation = $this->yellow->content->getParentTopLocation($pageLocation);
   1558         if ($parentTopLocation==$this->yellow->content->getHomeLocation($pageLocation)) $parentTopLocation .= "home";
   1559         return strtoloweru(trim($parentTopLocation, "/"));
   1560     }
   1561     
   1562     // Return next file name
   1563     public function getFileNext($fileNameShort) {
   1564         $fileText = $fileNumber = $fileExtension = "";
   1565         if (preg_match("/^(.*?)(\d*)(\..*?)?$/", $fileNameShort, $matches)) {
   1566             $fileText = $matches[1];
   1567             $fileNumber = is_string_empty($matches[2]) ? "-2" : $matches[2]+1;
   1568             $fileExtension = $matches[3];
   1569         }
   1570         return $fileText.$fileNumber.$fileExtension;
   1571     }
   1572     
   1573     // Return next title
   1574     public function getTitleNext($rawData) {
   1575         $titleText = $titleNumber = "";
   1576         if (preg_match("/^(.*?)(\d*)$/", $this->yellow->toolbox->getMetaData($rawData, "title"), $matches)) {
   1577             $titleText = $matches[1];
   1578             $titleNumber = is_string_empty($matches[2]) ? " 2" : $matches[2]+1;
   1579         }
   1580         return $titleText.$titleNumber;
   1581     }
   1582     
   1583     // Send mail to user
   1584     public function sendMail($scheme, $address, $base, $email, $action) {
   1585         if ($action=="approve") {
   1586             $userName = $this->yellow->system->get("author");
   1587             $userEmail = $this->yellow->system->get("email");
   1588             $userLanguage = $this->extension->getUserLanguage($userEmail);
   1589         } else {
   1590             $userName = $this->yellow->user->getUser("name", $email);
   1591             $userEmail = $email;
   1592             $userLanguage = $this->extension->getUserLanguage($email);
   1593         }
   1594         if ($action=="welcome" || $action=="goodbye") {
   1595             $url = "$scheme://$address$base/";
   1596         } else {
   1597             $expire = time() + 60*60*24;
   1598             $actionToken = $this->createActionToken($email, $action, $expire);
   1599             $locationArguments = "/action:$action/email:$email/expire:$expire/language:$userLanguage/actiontoken:$actionToken/";
   1600             $url = "$scheme://$address$base".$this->yellow->lookup->normaliseArguments($locationArguments, false);
   1601         }
   1602         $prefix = "edit".ucfirst($action);
   1603         $message = $this->yellow->language->getText("{$prefix}Message", $userLanguage);
   1604         $message = str_replace("\\n", "\r\n", $message);
   1605         $message = preg_replace("/@useraccount/i", $email, $message);
   1606         $message = preg_replace("/@usershort/i", strtok($userName, " "), $message);
   1607         $message = preg_replace("/@username/i", $userName, $message);
   1608         $message = preg_replace("/@userlanguage/i", $userLanguage, $message);
   1609         $sitename = $this->yellow->system->get("sitename");
   1610         $siteEmail = $this->yellow->system->get("from");
   1611         $subject = $this->yellow->language->getText("{$prefix}Subject", $userLanguage);
   1612         $footer = $this->yellow->language->getText("editMailFooter", $userLanguage);
   1613         $footer = str_replace("\\n", "\r\n", $footer);
   1614         $footer = preg_replace("/@sitename/i", $sitename, $footer);
   1615         $mailHeaders = array(
   1616             "To" => $this->yellow->lookup->normaliseAddress("$userName <$userEmail>"),
   1617             "From" => $this->yellow->lookup->normaliseAddress("$sitename <$siteEmail>"),
   1618             "Subject" => $subject,
   1619             "Date" => date(DATE_RFC2822),
   1620             "Mime-Version" => "1.0",
   1621             "Content-Type" => "text/plain; charset=utf-8",
   1622             "X-Request-Url" => "$scheme://$address$base");
   1623         $mailMessage = "$message\r\n\r\n$url\r\n-- \r\n$footer";
   1624         return $this->yellow->toolbox->mail($action, $mailHeaders, $mailMessage);
   1625     }
   1626     
   1627     // Create browser cookies
   1628     public function createCookies($scheme, $address, $base, $email) {
   1629         $expire = time() + $this->yellow->system->get("editLoginSessionTimeout");
   1630         $authToken = $this->createAuthToken($email, $expire);
   1631         $csrfToken = $this->createCsrfToken();
   1632         setcookie("yellowauthtoken", $authToken, $expire, "$base/", "", $scheme=="https", true);
   1633         setcookie("yellowcsrftoken", $csrfToken, $expire, "$base/", "", $scheme=="https", false);
   1634     }
   1635     
   1636     // Destroy browser cookies
   1637     public function destroyCookies($scheme, $address, $base) {
   1638         setcookie("yellowauthtoken", "", 1, "$base/");
   1639         setcookie("yellowcsrftoken", "", 1, "$base/");
   1640     }
   1641     
   1642     // Create authentication token
   1643     public function createAuthToken($email, $expire) {
   1644         $hash = $this->yellow->user->getUser("hash", $email);
   1645         $signature = $this->yellow->toolbox->createHash($hash."auth".$expire, "sha256");
   1646         if (is_string_empty($signature)) $signature = "padd"."error-hash-algorithm-sha256";
   1647         return substrb($signature, 4).$this->yellow->user->getUser("stamp", $email).dechex($expire);
   1648     }
   1649     
   1650     // Create action token
   1651     public function createActionToken($email, $action, $expire) {
   1652         $hash = $this->yellow->user->getUser("hash", $email);
   1653         $signature = $this->yellow->toolbox->createHash($hash.$action.$expire, "sha256");
   1654         if (is_string_empty($signature)) $signature = "padd"."error-hash-algorithm-sha256";
   1655         return substrb($signature, 4);
   1656     }
   1657     
   1658     // Create CSRF token
   1659     public function createCsrfToken() {
   1660         return $this->yellow->toolbox->createSalt(64);
   1661     }
   1662     
   1663     // Create password hash
   1664     public function createHash($password) {
   1665         $algorithm = $this->yellow->system->get("editUserHashAlgorithm");
   1666         $cost = $this->yellow->system->get("editUserHashCost");
   1667         $hash = $this->yellow->toolbox->createHash($password, $algorithm, $cost);
   1668         if (is_string_empty($hash)) $hash = "error-hash-algorithm-$algorithm";
   1669         return $hash;
   1670     }
   1671     
   1672     // Create user stamp
   1673     public function createStamp() {
   1674         $stamp = $this->yellow->toolbox->createSalt(20);
   1675         while ($this->getAuthEmail("none", $stamp)) {
   1676             $stamp = $this->yellow->toolbox->createSalt(20);
   1677         }
   1678         return $stamp;
   1679     }
   1680     
   1681     // Check user authentication from email and password
   1682     public function checkAuthLogin($email, $password) {
   1683         $algorithm = $this->yellow->system->get("editUserHashAlgorithm");
   1684         $hash = $this->yellow->user->getUser("hash", $email);
   1685         return $this->yellow->user->getUser("status", $email)=="active" &&
   1686             $this->yellow->toolbox->verifyHash($password, $algorithm, $hash);
   1687     }
   1688 
   1689     // Check user authentication from tokens
   1690     public function checkAuthToken($authToken, $csrfTokenExpected, $csrfTokenReceived, $csrfTokenIrrelevant) {
   1691         $signature = "$5y$".substrb($authToken, 0, 96);
   1692         $email = $this->getAuthEmail($authToken);
   1693         $expire = $this->getAuthExpire($authToken);
   1694         $hash = $this->yellow->user->getUser("hash", $email);
   1695         return $expire>time() && $this->yellow->user->getUser("status", $email)=="active" &&
   1696             $this->yellow->toolbox->verifyHash($hash."auth".$expire, "sha256", $signature) &&
   1697             ($this->yellow->toolbox->verifyToken($csrfTokenExpected, $csrfTokenReceived) || $csrfTokenIrrelevant);
   1698     }
   1699     
   1700     // Check action token
   1701     public function checkActionToken($actionToken, $email, $action, $expire) {
   1702         $signature = "$5y$".$actionToken;
   1703         $hash = $this->yellow->user->getUser("hash", $email);
   1704         return $expire>time() && $this->yellow->user->isExisting($email) &&
   1705             $this->yellow->toolbox->verifyHash($hash.$action.$expire, "sha256", $signature);
   1706     }
   1707     
   1708     // Return user email from authentication, timing attack safe email lookup
   1709     public function getAuthEmail($authToken, $stamp = "") {
   1710         $email = "";
   1711         if (is_string_empty($stamp)) $stamp = substrb($authToken, 96, 20);
   1712         foreach ($this->yellow->user->settings as $key=>$value) {
   1713             if ($this->yellow->toolbox->verifyToken($value["stamp"], $stamp)) $email = $key;
   1714         }
   1715         return $email;
   1716     }
   1717     
   1718     // Return expiration time from authentication
   1719     public function getAuthExpire($authToken) {
   1720         return hexdec(substrb($authToken, 96+20));
   1721     }
   1722     
   1723     // Change content file
   1724     public function editContentFile($page, $action, $email) {
   1725         if (!$page->isError()) {
   1726             foreach ($this->yellow->extension->data as $key=>$value) {
   1727                 if (method_exists($value["object"], "onEditContentFile")) $value["object"]->onEditContentFile($page, $action, $email);
   1728             }
   1729         }
   1730     }
   1731 
   1732     // Change media file
   1733     public function editMediaFile($file, $action, $email) {
   1734         if (!$file->isError()) {
   1735             foreach ($this->yellow->extension->data as $key=>$value) {
   1736                 if (method_exists($value["object"], "onEditMediaFile")) $value["object"]->onEditMediaFile($file, $action, $email);
   1737             }
   1738         }
   1739     }
   1740     
   1741     // Change system file
   1742     public function editSystemFile($file, $action, $email) {
   1743         if (!$file->isError()) {
   1744             foreach ($this->yellow->extension->data as $key=>$value) {
   1745                 if (method_exists($value["object"], "onEditSystemFile")) $value["object"]->onEditSystemFile($file, $action, $email);
   1746             }
   1747         }
   1748     }
   1749     
   1750     // Delete file
   1751     public function deleteFileLocation($location, $fileName) {
   1752         $rawData = $this->yellow->toolbox->readFile($fileName);
   1753         $rawData = $this->yellow->toolbox->setMetaData($rawData, "pageOriginalLocation", $location);
   1754         $rawData = $this->yellow->toolbox->setMetaData($rawData, "pageOriginalFileName", $fileName);
   1755         return $this->yellow->toolbox->writeFile($fileName, $rawData) &&
   1756             $this->yellow->toolbox->deleteFile($fileName, $this->yellow->system->get("coreTrashDirectory"));
   1757     }
   1758     
   1759     // Delete directory
   1760     public function deleteDirectoryLocation($location, $fileName) {
   1761         $rawData = $this->yellow->toolbox->readFile($fileName);
   1762         $rawData = $this->yellow->toolbox->setMetaData($rawData, "pageOriginalLocation", $location);
   1763         $rawData = $this->yellow->toolbox->setMetaData($rawData, "pageOriginalFileName", $fileName);
   1764         return $this->yellow->toolbox->writeFile($fileName, $rawData) &&
   1765             $this->yellow->toolbox->deleteDirectory(dirname($fileName), $this->yellow->system->get("coreTrashDirectory"));
   1766     }
   1767     
   1768     // Restore deleted file from trash
   1769     public function restoreFileLocation($location) {
   1770         $fileNameDeleted = $fileNameRestored = "";
   1771         $deleted = 0;
   1772         $pathTrash = $this->yellow->system->get("coreTrashDirectory");
   1773         $regex = "/^.*\\".$this->yellow->system->get("coreContentExtension")."$/";
   1774         foreach ($this->yellow->toolbox->getDirectoryEntries($pathTrash, $regex, false, false) as $entry) {
   1775             $rawDataOriginal = $this->yellow->toolbox->readFile($entry);
   1776             $locationOriginal = $this->yellow->toolbox->getMetaData($rawDataOriginal, "pageOriginalLocation");
   1777             $fileNameOriginal = $this->yellow->toolbox->getMetaData($rawDataOriginal, "pageOriginalFileName");
   1778             $deletedOriginal = $this->yellow->toolbox->getFileDeleted($entry);
   1779             if ($location==$locationOriginal && $deleted<=$deletedOriginal) {
   1780                 $fileNameDeleted = $entry;
   1781                 $fileNameRestored = $fileNameOriginal;
   1782                 $rawDataRestored = $rawDataOriginal;
   1783                 $rawDataRestored = $this->yellow->toolbox->unsetMetaData($rawDataRestored, "pageOriginalLocation");
   1784                 $rawDataRestored = $this->yellow->toolbox->unsetMetaData($rawDataRestored, "pageOriginalFileName");
   1785                 $deleted = $deletedOriginal;
   1786             }
   1787         }
   1788         return !is_string_empty($fileNameDeleted) && $this->yellow->lookup->isContentFile($fileNameRestored) &&
   1789             $this->yellow->toolbox->renameFile($fileNameDeleted, $fileNameRestored, true) &&
   1790             $this->yellow->toolbox->writeFile($fileNameRestored, $rawDataRestored);
   1791     }
   1792     
   1793     // Restore deleted directory from trash
   1794     public function restoreDirectoryLocation($location) {
   1795         $pathDeleted = $fileNameRestored = "";
   1796         $deleted = 0;
   1797         $pathTrash = $this->yellow->system->get("coreTrashDirectory");
   1798         foreach ($this->yellow->toolbox->getDirectoryEntries($pathTrash, "/.*/", false, true) as $entry) {
   1799             $fileName = $entry."/".$this->yellow->system->get("coreContentDefaultFile");
   1800             if (!is_file($fileName)) continue;
   1801             $rawDataOriginal = $this->yellow->toolbox->readFile($fileName);
   1802             $locationOriginal = $this->yellow->toolbox->getMetaData($rawDataOriginal, "pageOriginalLocation");
   1803             $fileNameOriginal = $this->yellow->toolbox->getMetaData($rawDataOriginal, "pageOriginalFileName");
   1804             $deletedOriginal = $this->yellow->toolbox->getFileDeleted($entry);
   1805             if ($location==$locationOriginal && $deleted<=$deletedOriginal) {
   1806                 $pathDeleted = $entry;
   1807                 $fileNameRestored = $fileNameOriginal;
   1808                 $rawDataRestored = $rawDataOriginal;
   1809                 $rawDataRestored = $this->yellow->toolbox->unsetMetaData($rawDataRestored, "pageOriginalLocation");
   1810                 $rawDataRestored = $this->yellow->toolbox->unsetMetaData($rawDataRestored, "pageOriginalFileName");
   1811                 $deleted = $deletedOriginal;
   1812             }
   1813         }
   1814         return !is_string_empty($pathDeleted) && $this->yellow->lookup->isContentFile($fileNameRestored) &&
   1815             $this->yellow->toolbox->renameDirectory($pathDeleted, dirname($fileNameRestored), true) &&
   1816             $this->yellow->toolbox->writeFile($fileNameRestored, $rawDataRestored);
   1817     }
   1818     
   1819     // Check if location can be created
   1820     public function isCreateLocation($location) {
   1821         return !is_string_empty($this->yellow->lookup->findFileFromContentLocation($location));
   1822     }
   1823     
   1824     // Check if location has been deleted
   1825     public function isDeletedLocation($location) {
   1826         $found = false;
   1827         $pathTrash = $this->yellow->system->get("coreTrashDirectory");
   1828         $regex = "/^.*\\".$this->yellow->system->get("coreContentExtension")."$/";
   1829         $fileNames = $this->yellow->toolbox->getDirectoryEntries($pathTrash, $regex, false, false);
   1830         foreach ($this->yellow->toolbox->getDirectoryEntries($pathTrash, "/.*/", false, true) as $entry) {
   1831             $fileName = $entry."/".$this->yellow->system->get("coreContentDefaultFile");
   1832             if (is_file($fileName)) array_push($fileNames, $fileName);
   1833         }
   1834         foreach ($fileNames as $fileName) {
   1835             $rawDataOriginal = $this->yellow->toolbox->readFile($fileName, 4096);
   1836             $locationOriginal = $this->yellow->toolbox->getMetaData($rawDataOriginal, "pageOriginalLocation");
   1837             if ($location==$locationOriginal) {
   1838                 $found = true;
   1839                 break;
   1840             }
   1841         }
   1842         return $found;
   1843     }
   1844     
   1845     // Check if meta data has been modified
   1846     public function isMetaModified($pageSource, $pageOther) {
   1847         return substrb($pageSource->rawData, 0, $pageSource->metaDataOffsetBytes) !=
   1848             substrb($pageOther->rawData, 0, $pageOther->metaDataOffsetBytes);
   1849     }
   1850     
   1851     // Check if login with restriction
   1852     public function isLoginRestriction() {
   1853         return $this->yellow->system->get("editLoginRestriction");
   1854     }
   1855     
   1856     // Check if user is logged in
   1857     public function isUser() {
   1858         return !is_string_empty($this->userEmail);
   1859     }
   1860     
   1861     // Check if user with access
   1862     public function isUserAccess($action, $location = "") {
   1863         $userHome = $this->yellow->user->getUser("home", $this->userEmail);
   1864         $tokens = preg_split("/\s*,\s*/", $this->yellow->user->getUser("access", $this->userEmail));
   1865         return in_array($action, $tokens) && (is_string_empty($location) || substru($location, 0, strlenu($userHome))==$userHome);
   1866     }
   1867 }
   1868     
   1869 class YellowEditMerge {
   1870     public $yellow;     // access to API
   1871     const ADD = "+";    // merge types
   1872     const MODIFY = "*";
   1873     const REMOVE = "-";
   1874     const SAME = " ";
   1875     
   1876     public function __construct($yellow) {
   1877         $this->yellow = $yellow;
   1878     }
   1879     
   1880     // Merge text, null if not possible
   1881     public function merge($textSource, $textMine, $textYours, $showDiff = false) {
   1882         if ($textMine!=$textYours) {
   1883             $diffMine = $this->buildDiff($textSource, $textMine);
   1884             $diffYours = $this->buildDiff($textSource, $textYours);
   1885             $diff = $this->mergeDiff($diffMine, $diffYours);
   1886             $output = $this->getOutput($diff, $showDiff);
   1887         } else {
   1888             $output = $textMine;
   1889         }
   1890         return $output;
   1891     }
   1892     
   1893     // Build differences to common source
   1894     public function buildDiff($textSource, $textOther) {
   1895         $diff = array();
   1896         $lastRemove = -1;
   1897         $textStart = 0;
   1898         $textSource = $this->yellow->toolbox->getTextLines($textSource);
   1899         $textOther = $this->yellow->toolbox->getTextLines($textOther);
   1900         $sourceEnd = $sourceSize = count($textSource);
   1901         $otherEnd = $otherSize = count($textOther);
   1902         while ($textStart<$sourceEnd && $textStart<$otherEnd && $textSource[$textStart]==$textOther[$textStart]) {
   1903             ++$textStart;
   1904         }
   1905         while ($textStart<$sourceEnd && $textStart<$otherEnd && $textSource[$sourceEnd-1]==$textOther[$otherEnd-1]) {
   1906             --$sourceEnd;
   1907             --$otherEnd;
   1908         }
   1909         for ($pos=0; $pos<$textStart; ++$pos) {
   1910             array_push($diff, array(YellowEditMerge::SAME, $textSource[$pos], false));
   1911         }
   1912         $lcs = $this->buildDiffLCS($textSource, $textOther, $textStart, $sourceEnd-$textStart, $otherEnd-$textStart);
   1913         for ($x=0,$y=0,$xEnd=$otherEnd-$textStart,$yEnd=$sourceEnd-$textStart; $x<$xEnd || $y<$yEnd;) {
   1914             $max = $lcs[$y][$x];
   1915             if ($y<$yEnd && $lcs[$y+1][$x]==$max) {
   1916                 array_push($diff, array(YellowEditMerge::REMOVE, $textSource[$textStart+$y], false));
   1917                 if ($lastRemove==-1) $lastRemove = count($diff)-1;
   1918                 ++$y;
   1919                 continue;
   1920             }
   1921             if ($x<$xEnd && $lcs[$y][$x+1]==$max) {
   1922                 if ($lastRemove==-1 || $diff[$lastRemove][0]!=YellowEditMerge::REMOVE) {
   1923                     array_push($diff, array(YellowEditMerge::ADD, $textOther[$textStart+$x], false));
   1924                     $lastRemove = -1;
   1925                 } else {
   1926                     $diff[$lastRemove] = array(YellowEditMerge::MODIFY, $textOther[$textStart+$x], false);
   1927                     ++$lastRemove;
   1928                     if (count($diff)==$lastRemove) $lastRemove = -1;
   1929                 }
   1930                 ++$x;
   1931                 continue;
   1932             }
   1933             array_push($diff, array(YellowEditMerge::SAME, $textSource[$textStart+$y], false));
   1934             $lastRemove = -1;
   1935             ++$x;
   1936             ++$y;
   1937         }
   1938         for ($pos=$sourceEnd;$pos<$sourceSize; ++$pos) {
   1939             array_push($diff, array(YellowEditMerge::SAME, $textSource[$pos], false));
   1940         }
   1941         return $diff;
   1942     }
   1943     
   1944     // Build longest common subsequence
   1945     public function buildDiffLCS($textSource, $textOther, $textStart, $yEnd, $xEnd) {
   1946         $lcs = array_fill(0, $yEnd+1, array_fill(0, $xEnd+1, 0));
   1947         for ($y=$yEnd-1; $y>=0; --$y) {
   1948             for ($x=$xEnd-1; $x>=0; --$x) {
   1949                 if ($textSource[$textStart+$y]==$textOther[$textStart+$x]) {
   1950                     $lcs[$y][$x] = $lcs[$y+1][$x+1]+1;
   1951                 } else {
   1952                     $lcs[$y][$x] = max($lcs[$y][$x+1], $lcs[$y+1][$x]);
   1953                 }
   1954             }
   1955         }
   1956         return $lcs;
   1957     }
   1958     
   1959     // Merge differences
   1960     public function mergeDiff($diffMine, $diffYours) {
   1961         $diff = array();
   1962         $posMine = $posYours = 0;
   1963         while ($posMine<count($diffMine) && $posYours<count($diffYours)) {
   1964             $typeMine = $diffMine[$posMine][0];
   1965             $typeYours = $diffYours[$posYours][0];
   1966             if ($typeMine==YellowEditMerge::SAME) {
   1967                 array_push($diff, $diffYours[$posYours]);
   1968             } elseif ($typeYours==YellowEditMerge::SAME) {
   1969                 array_push($diff, $diffMine[$posMine]);
   1970             } elseif ($typeMine==YellowEditMerge::ADD && $typeYours==YellowEditMerge::ADD) {
   1971                 $this->mergeConflict($diff, $diffMine[$posMine], $diffYours[$posYours], false);
   1972             } elseif ($typeMine==YellowEditMerge::MODIFY && $typeYours==YellowEditMerge::MODIFY) {
   1973                 $this->mergeConflict($diff, $diffMine[$posMine], $diffYours[$posYours], false);
   1974             } elseif ($typeMine==YellowEditMerge::REMOVE && $typeYours==YellowEditMerge::REMOVE) {
   1975                 array_push($diff, $diffMine[$posMine]);
   1976             } elseif ($typeMine==YellowEditMerge::ADD) {
   1977                 array_push($diff, $diffMine[$posMine]);
   1978             } elseif ($typeYours==YellowEditMerge::ADD) {
   1979                 array_push($diff, $diffYours[$posYours]);
   1980             } else {
   1981                 $this->mergeConflict($diff, $diffMine[$posMine], $diffYours[$posYours], true);
   1982             }
   1983             if ($typeMine==YellowEditMerge::ADD || $typeYours==YellowEditMerge::ADD) {
   1984                 if ($typeMine==YellowEditMerge::ADD) ++$posMine;
   1985                 if ($typeYours==YellowEditMerge::ADD) ++$posYours;
   1986             } else {
   1987                 ++$posMine;
   1988                 ++$posYours;
   1989             }
   1990         }
   1991         for (;$posMine<count($diffMine); ++$posMine) {
   1992             array_push($diff, $diffMine[$posMine]);
   1993             $typeMine = $diffMine[$posMine][0];
   1994             $typeYours = " ";
   1995         }
   1996         for (;$posYours<count($diffYours); ++$posYours) {
   1997             array_push($diff, $diffYours[$posYours]);
   1998             $typeYours = $diffYours[$posYours][0];
   1999             $typeMine = " ";
   2000         }
   2001         return $diff;
   2002     }
   2003     
   2004     // Merge potential conflict
   2005     public function mergeConflict(&$diff, $diffMine, $diffYours, $conflict) {
   2006         if (!$conflict && $diffMine[1]==$diffYours[1]) {
   2007             array_push($diff, $diffMine);
   2008         } else {
   2009             array_push($diff, array($diffMine[0], $diffMine[1], true));
   2010             array_push($diff, array($diffYours[0], $diffYours[1], true));
   2011         }
   2012     }
   2013     
   2014     // Return merged text, null if not possible
   2015     public function getOutput($diff, $showDiff = false) {
   2016         $output = "";
   2017         $conflict = false;
   2018         if (!$showDiff) {
   2019             for ($i=0; $i<count($diff); ++$i) {
   2020                 if ($diff[$i][0]!=YellowEditMerge::REMOVE) $output .= $diff[$i][1];
   2021                 $conflict |= $diff[$i][2];
   2022             }
   2023         } else {
   2024             for ($i=0; $i<count($diff); ++$i) {
   2025                 $output .= $diff[$i][2] ? "! " : $diff[$i][0]." ";
   2026                 $output .= $diff[$i][1];
   2027             }
   2028         }
   2029         return !$conflict ? $output : null;
   2030     }
   2031 }