$maxtitle) {
        $title = substr($title, 0, $maxtitle-3);
        $title = trim(clean_utf8($title)).'...';
    }
    return $title;
}
function clean_text($text) {
    // Clean up control characters, special quotes, ligatures, special spacing, etc.
    $text = str_replace('\t', '    ', $text);
    $text = preg_replace('#[\x00-\x09\x0B-\x1F]#', '', $text);
    $unicode_specials = array(
        // 0x7F-0x9F control characters
        '','','','','','','
','','','','','','','',
        '','','','','','','','','','','','','','',
        '','','','','',
        // Unicode spacing
        '','','','','\xE2\x80\xA8','\xE2\x80\xA9','','','','','',
        // Unicode quotes
        '‘','’','“','”');
    $ascii_specials = array(
        // Strip 0x7F-0x9F control characters
        '','','','','','','','','','','','','','',
        '','','','','','','','','','','','','','',
        '','','','','',
        // Replacements for unicode spacing
        ' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',
        // Replacements for unicode quotes
        "'","'",'"','"');
    $text = str_replace($unicode_specials, $ascii_specials, $text);
    return $text;
}
function clean_template($template) {
    global $templates, $defaulttemplate;
    foreach ($templates as $tem) {
        if ($tem === $template) { return $tem; }
    }
    return $defaulttemplate;
}
function clean_url($url) {
    global $filesediturl, $filesrealurl;
    // Turn editor-relative links into page-relative links
    if (startswith($url, $filesediturl)) {
        $url = $filesrealurl.substr($url, strlen($filesediturl));
    }
    // Remove domain/path of local absolute URLs
    $base = preg_replace('#(pageeditor/)?(index\.php)?(\?.*)?$#', '', $_SERVER['REQUEST_URI']);
    $regex = '#^https?://\[?'.preg_quote(str_replace('/', '', $_SERVER['HTTP_HOST']), '#').'\]?(:[0-9]+)?'.preg_quote($base, '#').'#i';
    $url = preg_replace($regex, '', $url);
    return $url;
}
function make_editor_relative_urls($html) {
    global $filesediturl, $filesrealurl;
    return str_replace(' src="'.htmlspecialchars($filesrealurl),
                       ' src="'.htmlspecialchars($filesediturl), $html);
}
function wrap_largeimg_callback($matches) {
    $href = htmlspecialchars(clean_text(hex2bin($matches[3])));
    return '';
}
define('ST_TEXT', 0);
define('ST_TAGNAME', 1);
define('ST_ATTR', 2);
define('ST_COMMENT', 3);
function clean_html($html) {
    // 
 is not included yet because it needs special handling
    $allowed = array(
        // block elements
        'address','aside','blockquote','dd','dl','dt','div','figcaption',
        'figure','h1','h2','h3','h4','h5','h6','hgroup','hr','li','ol','p','ul',
        // text elements
        'abbr','b','big','bdi','bdo','br','cite','code','del','dfn','em',
        'i','ins','kbd','mark','nobr','q','rp','rt','rtc','ruby','s','samp',
        'small','span','strong','sub','sup','time','tt','u','var','wbr',
        // tables
        'caption','col','colgroup','table','tbody','td','tfoot','th','thead',
        'tr',
        // special elements
        'a','area','audio','img','map','track','video');
    /* deprecated tags:
        acronym --> abbr
        big --> keep
        blink --> ignore
        center --> div
        listing --> pre (not common)
        marquee --> ignore
        nobr --> keep
        plaintext --> ~pre (not common)
        spacer --> ignore
        strike --> s
        tt --> code
        xmp --> pre+code?
    */
    $allowed_attrs = array('dir', 'id', 'lang', 'role', 'title', 'translate', /*'style',*/);
    $allowed_attr_regex = '#(aria-[a-z0-9-])+#';
    $allowed_attrs_tag = array(
        'a' => array('download', 'href', 'hreflang', 'rel', 'rev', 'name',
                     'nofollow', 'target', 'type', 'referrerpolicy'),
        'area' => array('alt', 'crossorigin', 'coords', 'download', 'href',
                     'hreflang', 'rel', 'shape', 'name', 'nofollow', 'target',
                     'type', 'referrerpolicy'),
        'audio' => array('crossorigin', 'loop', 'muted', 'preload', 'src'),
        'blockquote' => array('cite'),
        'del' => array('cite','datetime'),
        /*'iframe' => array('allowfullscreen', 'allowpaymentrequest', 'height',
                          'name', 'referrerpolicy', 'sandbox', 'src',
                          'srcdoc', 'width'),*/
        'img' => array('alt', 'border', 'crossorigin', 'height', 'ismap',
                       'longdesc', 'refererpolicy', 'sizes', 'src', 'srcset',
                       'usemap', 'width'),
        'ins' => array('cite','datetime'),
        'li' => array('value'),
        'map' => array('name'),
        'ol' => array('reversed', 'start', 'type'),
        'q' => array('cite'),
        'table' => array('cellpadding', 'cellspacing'),
        'td' => array('rowspan', 'colspan'),
        'th' => array('rowspan', 'colspan'),
        'track' => array('default', 'kind', 'label', 'src', 'srclang'),
        'time' => array('datetime'),
        'video' => array('crossorigin', 'height', 'loop', 'muted', 'preload',
                         'poster', 'src', 'width'),
    );
    $tag_replacements = array(
        'acronym' => 'abbr',
        'image' => 'img',
        'strike' => 's',
        'tt' => 'code',
    );
    $nonenc = array('<','>','"',"'");
    $enc = array('<','>','"',''');
    $html = clean_text($html);
    $cleaned = '';
    $state = ST_TEXT;
    $textstart = 0;
    $tagstart = 0;
    $attrstart = 0;
    $i = 0;
    $done = false;
    while (!$done) {
        switch ($state) {
        case ST_TEXT:
            $length = strcspn($html, "<", $i);
            // encode for extra security (except & which would destroy &enties;)
            $cleaned .= str_replace($nonenc, $enc, substr($html, $i, $length));
            $i += $length;
            if ($i < strlen($html)) {
                $state = ST_TAGNAME;
                $i++;
            } else {
                $done = true;
            }
            break;
        case ST_TAGNAME:
            $length = strcspn($html, " \r\n\t><", $i);
            $tagname = strtolower(trim(substr($html, $i, $length)));
            $endtag = (strlen($tagname) >= 1 && $tagname[0] === '/');
            $tagname = preg_replace('#^/\\s*#', '', $tagname);
            if ($endtag) {
                $i += $length;
                if ($i < strlen($html) && $html[$i] === '>') {
                    $i++;
                }
                if (in_array($tagname, $allowed, true)) {
                    $cleaned .= ''.$tagname.'>';
                }
                $state = ST_TEXT;
            } elseif (substr($html, $i, 3) === '!--') {
                $i += 3;
                $state = ST_COMMENT;
            // TODO special handling for script and style elements
            } else {
                $i += $length;
                $tagattrs = array();
                $state = ST_ATTR;
            }
            break;
        case ST_ATTR:
            $i += strspn($html, " \r\n\t", $i);
            $ch = ($i < strlen($html) ? $html[$i] : null);
            $selfclosechar = '';
            if ($ch === '/') {
                $i++;
                $i += strspn($html, " \r\n\t", $i);
                $ch = ($i < strlen($html) ? $html[$i] : null);
                $selfclosechar = '/';
            }
            if ($ch === '>' || $ch === '<' || $ch === null) {
                // End of tag
                $i++;
                if (isset($tag_replacements[$tagname])) {
                    $tagname = $tag_replacements[$tagname];
                }
                if (in_array($tagname, $allowed, true)) {
                    $cleaned .= '<'.$tagname;
                    // TODO sort attributes?
                    foreach ($tagattrs as $attrname => $attrvalue) {
                        if (in_array($attrname, $allowed_attrs, true) ||
                            preg_match($allowed_attr_regex, $attrname) ||
                            (isset($allowed_attrs_tag[$tagname]) &&
                                in_array($attrname, $allowed_attrs_tag[$tagname], true))) {
                            $cleaned .= ' '.$attrname.($attrvalue !== null ?
                                '="'.str_replace($nonenc, $enc, $attrvalue).'"' : '');
                        }
                    }
                    $cleaned .= $selfclosechar.'>';
                }
                $state = $ch === '<' ? ST_TAGNAME : ST_TEXT;
                break;
            }
            // Attribute name
            $length = strcspn($html, " \r\n\t\"\'=/><&", $i);
            $attrname = strtolower(substr($html, $i, $length));
            $i += $length;
            $i += strspn($html, " \r\n\t", $i);
            $ch = ($i < strlen($html) ? $html[$i] : null);
            $attrvalue = null;
            if ($ch === '=') {
                // Attribute value
                $i++;
                $i += strspn($html, " \r\n\t", $i);
                $ch = ($i < strlen($html) ? $html[$i] : null);
                if ($ch === '"' || $ch === "'") {
                    $quotechar = $ch;
                    $i++;
                    $length = strcspn($html, $quotechar, $i);
                    $attrvalue = substr($html, $i, $length);
                    $i += $length;
                    $ch = ($i < strlen($html) ? $html[$i] : null);
                    if ($ch === $quotechar) {
                        $i++;
                    }
                } else {
                    $length = strcspn($html, " \r\n\t\"\'=><&", $i);
                    $attrvalue = substr($html, $i, $length);
                    $i += $length;
                }
            }
            if (!isset($tagattrs[$attrname])) { // ignore duplicates
                if ($attrname === 'src' || $attrname === 'href') {
                    $attrvalue = clean_url($attrvalue);
                }
                $tagattrs[$attrname] = $attrvalue;
            }
            break;
        case ST_COMMENT:
            $end = strpos($html, '-->', $i-2);
            if ($end === false) {
                $comment = substr($html, $i);
                $end = strlen($html);
            } elseif ($end <= $i) {
                //  or 
                $comment = '';
                $end += 3;
            } else {
                $comment = substr($html, $i, $end-$i);
                $end += 3;
            }
            // safety measure: remove < and > inside comments,
            // in case there would be a bug in the parser
            $cleaned .= '';
            $i = $end;
            $state = ST_TEXT;
            break;
        }
    }
    // Add  around ![LARGEIMG_...]() $cleaned = preg_replace_callback('/
    $cleaned = preg_replace_callback('/![LARGEIMG_([^]() /', 'wrap_largeimg_callback', $cleaned);
    // TODO strip "class" attributes
    // TODO change  *
/', 'wrap_largeimg_callback', $cleaned);
    // TODO strip "class" attributes
    // TODO change  * |   *|  if x is not block level (actually, the spaces outside of the element (e.g. ) might matter... check this)
    // TODO strip  * |... if x is not block level
    // TODO strip special elements (script, style)
    // TODO finally, ensure that tags are closed/nested correctly
    return $cleaned;
}
function filename_from_title($pagetitle) {
    if ($pagetitle === '') {
        return '';
    }
    $non_ascii = array(
        'À','Á','Â','Ã','Ä','Å', 'Æ','Ç','È','É','Ê','Ë','Ì','Í','Î','Ï','Ð','Ñ','Ò','Ó','Ô','Õ','Ö','Ø','Ù','Ú','Û','Ü','Ý', 'Þ',
        'à','á','â','ã','ä','å', 'æ','ç','è','é','ê','ë','ì','í','î','ï','ð','ñ','ò','ó','ô','õ','ö','ø','ù','ú','û','ü','ý', 'þ','ÿ',
        'Ā','ā','Ă','ă','Ą','ą','Ć','ć','Ĉ','ĉ','Ċ','ċ','Č','č','Ď','ď','Đ','đ','Ē','ē','Ĕ','ĕ','Ė','ė','Ę','ę','Ě','ě','Ĝ','ĝ','Ğ',
        'ğ','Ġ','ġ','Ģ','ģ','Ĥ','ĥ','Ħ','ħ','Ĩ','ĩ','Ī','ī','Ĭ','ĭ','Į','į','İ','ı', 'IJ', 'ij','Ĵ','ĵ','Ķ','ķ','ĸ','Ĺ','ĺ','Ļ','ļ',
        'Ľ','ľ','Ŀ','ŀ','Ł','ł','Ń','ń','Ņ','ņ','Ň','ň','ʼn', 'Ŋ', 'ŋ','Ō','ō','Ŏ','ŏ','Ő','ő', 'Œ', 'œ','Ŕ','ŕ','Ŗ','ŗ','Ř','ř',
        'Ś','ś','Ŝ','ŝ','Ş','ş','Š','š','Ţ','ţ','Ť','ť','Ŧ','ŧ','Ũ','ũ','Ū','ū','Ŭ','ŭ','Ů','ů','Ű','ű','Ų','ų','Ŵ','ŵ','Ŷ','ŷ','Ÿ',
        'Ź','ź','Ż','ż','Ž','ž','ſ',
    );
    $ascii = array(
        'a','a','a','a','a','a','ae','c','e','e','e','e','i','i','i','i','d','n','o','o','o','o','o','o','u','u','u','u','y','th',
        'a','a','a','a','a','a','ae','c','e','e','e','e','i','i','i','i','d','n','o','o','o','o','o','o','u','u','u','u','y','th','y',
        'a','a','a','a','a','a','c','c','c','c','c','c','c','c','d','d','d','d','e','e','e','e','e','e','e','e','e','e','g','g','g',
        'g','g','g','g','g','h','h','h','h','i','i','i','i','i','i','i','i','i','i','ij','ij','j','j','k','k','k','l','l','l','l',
        'l','l','l','l','l','l','n','n','n','n','n','n','n','ng','ng','o','o','o','o','o','o','oe','oe','r','r','r','r','r','r',
        's','s','s','s','s','s','s','s','t','t','t','t','t','t','u','u','u','u','u','u','u','u','u','u','u','u','w','w','y','y','y',
        'z','z','z','z','z','z','s',
    );
    $name = preg_replace('#[ +.,:,!?]+#', '_', strtolower(str_replace($non_ascii, $ascii, $pagetitle)));
    $name = clean_filename($name);
    if ($name === '' || 4*count($name) < count($pagetitle)) {
        $name = substr(sha1($pagetitle), 0, 10);
    }
    return $name;
}
function read_page_list() {
    $pageinfos = array();
    $path = configpath("/pages.txt");
    if (!file_exists($path)) {
        return $pageinfos;
    }
    $f = fopen($path, "r");
    if (!$f) {
        return $pageinfos;
    }
    while (($line = fgets($f)) !== false) {
        $line = trim($line);
        if (!$line) continue;
        $pageinfo = explode("\t", $line);
        $pageinfos[] = $pageinfo;
    }
    fclose($f);
    return $pageinfos;
}
/**
 * Modifies the page list. The changes are written to disk.
 * It can perform insertion, deletion and moving, depending on the paramters.
 *
 * @param string|null pagename Old name of page, or null if a new page should be added.
 * @param string|null newname New name of page. Ignored when deleting.
 * @param string|null newtitle New title of page. Ignored when deleting.
 * @param string|null newtemplate New template of page. Ignored when deleting.
 * @param int|null newindex New index of page. Set to -1 to place last, or null to delete.
 */
function update_page_list($pagename, $newname, $newtitle, $newtemplate, $newindex=-1) {
    global $pageinfos, $error;
    // Update in $pageinfos
    $newpageinfos = array();
    for ($i = 0; $i < count($pageinfos); $i++) {
        $pi = $pageinfos[$i];
        if ($i === $newindex) {
            // Insert page at this index
            $newpageinfos[] = array($newtemplate, $newname, $newtitle);
        }
        if ($pi[PI_NAME] !== $pagename) {
            // This page is not to be deleted/moved
            $newpageinfos[] = $pi;
        }
    }
    if ($newindex === -1 || $newindex == count($pageinfos)) {
        $newpageinfos[] = array($newtemplate, $newname, $newtitle);
    }
    // Write to file
    $reallist = configpath("/pages.txt");
    $tmplist = configpath("/pages.txt.tmp");
    $newf = fopen($tmplist, "w");
    if (!$newf) {
        $error = gettxt('ERROR_WRITEPAGELIST');
    }
    foreach ($newpageinfos as $pi) {
        fwrite($newf, implode("\t", $pi)."\n");
    }
    fclose($newf);
    if (!rename($tmplist, $reallist)) {
        $error = gettxt('ERROR_RENAMEPAGELIST');
    }
    $pageinfos = $newpageinfos;
    // Set "regeneration needed" flag
    mark_regen_needed();
}
function mark_regen_needed() {
    global $regen_needed_since;
    if ($regen_needed_since === null) {
        $regen_needed_since = time();
        file_put_contents(configpath('/regen_needed.txt'), $regen_needed_since);
    }
}
function generate_page($pagename, $pagetitle, $pagecontent, $pagetemplate) {
    global $pageinfos, $siteconfig;
    $tpl = file_get_contents(configpath("/templates/${pagetemplate}.html"));
    if ($pagename === 'index') {
        $title = $siteconfig['sitename'];
    } else {
        $title = $pagetitle.' - '.$siteconfig['sitename'];
    }
    $menu_li = '';
    foreach ($pageinfos as $pageinfo) {
        $menu_li .= ''.htmlspecialchars($pageinfo[PI_TITLE])." \n";
    }
    $langselect = ''; // TODO multi-language support
    $tpl = str_replace(array('${title}', '${content}', '${menu_li}', '${langselect}', '${sitename}', '${footer}'),
                       array($title, $pagecontent, $menu_li, $langselect, $siteconfig['sitename'], $siteconfig['footer']), $tpl);
    return file_put_contents(webpath("/${pagename}.html"), $tpl) !== false;
}
function read_template_list() {
    $templates = array();
    $dir = opendir(configpath('/templates'));
    while (($entry = readdir($dir)) !== false) {
        if (preg_match('#^[^.\t\n][^\t\n]*\.html$#D', $entry)) {
            $templates[] = preg_replace('#\.html$#D', '', $entry);
        }
    }
    closedir($dir);
    return $templates;
}
function read_config_file($filename, $defaults) {
    $cfg = $defaults;
    $cfgfile = configpath($filename);
    if (!file_exists($cfgfile)) return $cfg;
    $f = fopen($cfgfile, 'r');
    if (!$f) return $cfg;
    while (($line = fgets($f)) !== false) {
        $pieces = explode('=', $line, 2);
        if (count($pieces) == 1) continue;
        $key = trim($pieces[0]);
        $value = trim($pieces[1]);
        $cfg[$key] = $value;
    }
    fclose($f);
    return $cfg;
}
function read_site_config() {
    $defaults = array(
        'sitename' => 'Enter name here',
        'footer' => 'Copyright © '.date('Y'),
    );
    return read_config_file('/siteconfig.txt', $defaults);
}
function read_auth_config() {
    return read_config_file('/users.txt', array());
}
function update_config_file($filename, $cfg) {
    $f = fopen(configpath($filename), 'w');
    if (!$f) return false;
    foreach ($cfg as $key => $value) {
        $k = str_replace(array('=', "\n"), array('', ''), trim($key));
        $v = str_replace("\n", '', trim($value));
        fwrite($f, "$k = $v\n");
    }
    fclose($f);
    mark_regen_needed();
    return true;
}
function update_site_config($cfg) {
    if (!update_config_file('/siteconfig.txt', $cfg)) return false;
    mark_regen_needed();
    return true;
}
function update_auth_config($cfg) {
    return update_config_file('/users.txt', $cfg);
}
function unsaved_site_config() {
    global $siteconfig, $editsitename, $editfooter;
    return $siteconfig['sitename'] !== $editsitename ||
           $siteconfig['footer'] !== $editfooter;
}
function unsaved_edit_state() {
    global $pagecontent, $pagename, $pageindex, $pageinfo,
           $editpagename, $editpagetitle, $editpageindex, $editpagetemplate;
    return $editpagename !== $pageinfo[PI_NAME] ||
        $editpagetitle !== $pageinfo[PI_TITLE] ||
        $editpagetemplate !== $pageinfo[PI_TEMPLATE] ||
        $editpageindex !== $pageindex ||
        $pagecontent !== read_page($pagename);
}
function get_page_info($pageinfos, $name) {
    foreach ($pageinfos as $i => $pageinfo) {
        if ($pageinfo[PI_NAME] === $name) return array($i, $pageinfo);
    }
    return null;
}
function index_page_defaults() {
    return array($defaulttemplate, 'index', 'Start page');
}
function read_page($pagename) {
    return file_get_contents(configpath("/pages/${pagename}.txt"));
}
function read_regen_needed() {
    $path = configpath('/regen_needed.txt');
    if (!file_exists($path)) return null;
    else return intval(trim(file_get_contents($path)));
}
function already_exists($pageinfos, $pagename) {
    // Check if it exists in the page list
    if (get_page_info($pageinfos, $pagename) !== null) return true;
    // Check if it is some html file not created with this tool
    return !file_exists(configpath("/pages/${pagename}.txt")) &&
           file_exists(webpath("/${pagename}.html"));
}
function is_image_filename($filename) {
    return preg_match('/\.(png|jpg|jpeg|jpe|gif|bmp|ico|svg|svgz)$/i', $filename);
}
function upload_file_ext_allowed($filename) {
    global $allowedexts;
    // Always disallow hidden files
    if (preg_match('/^\./', $filename)) {
        return 'ERROR_UPLOAD_HIDDEN';
    }
    if (!preg_match('/\.('.$allowedexts.')$/i', $filename)) {
        return 'ERROR_UPLOAD_FILEEXT';
    }
    $filename = preg_replace('/\.('.$allowedexts.')$/i', '', $filename);
    // Disallow double extensions (except for the allowed ones, like .tar.gz)
    if (preg_match('/\.[a-zA-Z]+$/', $filename)) {
        return 'ERROR_UPLOAD_DOUBLEFILEEXT';
    }
    // Block too long filenames
    if (strlen($filename) > 100) {
        return 'ERROR_UPLOAD_NAMETOOLONG';
    }
    return true;
}
function clean_upload_filename($filename) {
    $filename = clean_one_line($filename);
    $filename = preg_replace('#[/\\:]#', '', $filename);
    $filename = preg_replace('/_small\.([a-zA-Z0-9]+)$/i', '_small.$1', $filename);
    return $filename;
}
function startswith($s, $beginning) {
    return substr($s, 0, strlen($beginning)) === $beginning;
}
function is_webp($s) {
    return startswith($s, 'RIFF') && substr($s, 8, 7) == 'WEBPVP8';
}
function is_small_image($filename) {
    return preg_match('/_small\.jpg$/', $filename);
}
function get_small_name($filename) {
    return preg_replace('/\.([a-zA-Z0-9]+)$/', '_small.jpg', $filename);
}
function can_scale_image($filename) {
    return preg_match('/.(jpg|jpeg|png|webp)$/i', $filename);
}
function create_lowres_picture($cleanedname) {
    global $filesdir, $image_width, $image_height;
    if (!can_scale_image($cleanedname) || is_small_image($cleanedname) ||
        !function_exists('getimagesize')) {
        return false;
    }
    $origname = $filesdir.'/'.$cleanedname;
    $newname = $filesdir.'/'.get_small_name($cleanedname);
    // Check the file header
    $f = fopen($origname, 'rb');
    if (!$f) return false;
    $header = fread($f, 16);
    fclose($f);
    if (!startswith($header, "\xff\xd8\xff") && // JPEG
        !startswith($header, "\x89PNG\x0d\x0a\x1a\x0a") && // PNG
        //!startswith($header, 'GIF8') && // GIF
        !is_webp($header)) {
        return false;
    }
    // Check if it needs to be scaled down
    // TODO check for images with EXIF rotation also. These need to be rotated
    $size = getimagesize($origname);
    if (!$size || ($size[0] < $image_width && $size[1] < $image_height)) {
        return false;
    }
    $type = $size[2];
    if ($type != IMAGETYPE_PNG && $type != IMAGETYPE_JPEG && $type != IMAGETYPE_WEBP) {
        return false;
    }
    // Determine new size
    $scalew = floatval($image_width) / $size[0];
    $scaleh = floatval($image_height) / $size[1];
    $scale = min($scalew, $scaleh);
    $width = intval($size[0] * $scale);
    $height = intval($size[1] * $scale);
    // Perform scaling
    switch ($type) {
    case IMAGETYPE_PNG:  $origimg = imagecreatefrompng($origname);  break;
    case IMAGETYPE_JPEG: $origimg = imagecreatefromjpeg($origname); break;
    case IMAGETYPE_WEBP: $origimg = imagecreatefromwebp($origname); break;
    }
    if (!$origimg) return false;
    //$newimg = imagescale($origimg, $width, $height);
    $newimg = imagecreatetruecolor($width, $height);
    if (!$newimg) return false;
    imagecopyresampled($newimg, $origimg, 0, 0, 0, 0, $width, $height, $size[0], $size[1]);
    imagedestroy($origimg);
    // Write file
    $output = fopen($newname, 'wb');
    imagejpeg($newimg, $output);
    fclose($output);
    return true;
}
function redirect_get() {
    global $pagename;
    if ($pagename == 'index') {
        header("Location: .");
        //header("Location: ?nosdr"); // avoid same-document reference
    } else {
        header("Location: ?page=".urlencode($pagename));
    }
    exit();
}
function login_success_redirect() {
    if (isset($_POST['iframeLoginForm'])) {
        // Close login iframe
        // FIXME if the user logs in with a different user, the username
        //       will not be updated until page reload
        ?>
';
}
function verify_anti_csrf() {
    global $secretvalue;
    if ($_SERVER['REQUEST_METHOD'] == 'POST' || !empty($_POST)) {
        $pieces = explode(':', get_req_var('anti_csrf', ''), 2);
        $time = intval($pieces[0]);
        $token = isset($pieces[1]) ? $pieces[1] : '';
        if ($time < time() - 3600*24*30) { // 30 days should be enough for most people
            die("Page has expired, please try again. (CSRF token expired)");
        }
        if (sha1($secretvalue.':'.$token) !== sha1($secretvalue.':'.make_anti_csrf_token($time))) {
            die("CSRF token error");
        }
    }
}
$auth = false;
$username = null;
$login_error = false;
$login_session = false;
if ($auth_http_enabled && isset($_SERVER['REMOTE_USER'])) {
    // HTTP authentication. Controlled by web server
    $username = $_SERVER['REMOTE_USER'];
    $auth = true;
} else if ($auth_session_enabled && isset($_POST['username']) && isset($_POST['password'])) {
    // Attempt to log in
    $authconfig = read_auth_config();
    $login_username = get_post_var('username');
    if (check_password($login_username, get_post_var('password'))) {
        session_start();
        $username = $login_username;
        $_SESSION['username'] = $username;
        $_SESSION['sessionHash'] = hash('sha256', $secretvalue.':'.$username);
        session_write_close();
        $pagename = 'index';
        login_success_redirect();
    } else {
        $login_error = gettxt('LOGIN_ERROR_USERPASS');
    }
} else if ($auth_session_enabled && isset($_POST['button_logout'])) {
    // Delete session on server
    session_start();
    $_SESSION = array();
    session_destroy();
    // Delete session cookie
    $cookieParams = session_get_cookie_params();
    function getCookieParam($key) {
        global $cookieParams;
        return (isset($cookieParams[$key]) ? $cookieParams[$key] : NULL);
    }
    $path = getCookieParam('path');
    $domain = getCookieParam('domain');
    $secure = getCookieParam('secure');
    $httpOnly = getCookieParam('httpOnly');
    if ($httpOnly) {
        setcookie(session_name(), '', 0, $path, $domain, $secure, $httpOnly);
    } else if ($secure) {
        setcookie(session_name(), '', 0, $path, $domain, $secure);
    } else if ($domain) {
        setcookie(session_name(), '', 0, $path, $domain);
    } else if ($path) {
        setcookie(session_name(), '', 0, $path);
    } else {
        setcookie(session_name(), '', 0);
    }
    redirect_get();
} else if ($auth_session_enabled && isset($_COOKIE[session_name()])) {
    // Check session
    session_start();
    if (isset($_SESSION['username'])) {
        $sessionUsername = $_SESSION['username'];
        $expectedHash = hash('sha256', $secretvalue.':'.hash('sha256', $secretvalue.':'.$sessionUsername));
        if (isset($_SESSION['sessionHash']) &&
            hash('sha256', $secretvalue.':'.$_SESSION['sessionHash']) === $expectedHash) {
            $username = $sessionUsername;
            $auth = true;
            $login_session = true;
        }
    } else {
        $login_error = gettxt('LOGIN_ERROR_EXPIRED');
    }
    session_write_close();
}
if (!$auth_session_enabled) {
    die('Internal error. Authentication in '.$title.' and/or the web server is not configured correctly');
}
if (isset($_GET['sessionKeepAlive'])) {
    header('Content-Type: text/plain; charset=UTF-8');
    echo $auth ? "1" : "0";
    exit();
}
if (!$auth || isset($_GET['iframeLoginForm'])) {
    // Not authenticated
?>
'.htmlspecialchars($prompttext)."
\n";
    echo ''."\n";
    echo '\n\n";
}
?>
 
 
 
'."\n";
    }
}
if (file_exists($wysihtmldir.'/wysihtml.min.js')) {
    include_wysihtml('.min');
} elseif (file_exists($wysihtmldir.'/wysihtml.js')) {
    include_wysihtml('');
}
?>