<?php

namespace Volt;

//////////////////////////////////////////////////
// Bric Version 1.9.4
const VERSION = '1.9.7';
//////////////////////////////////////////////////

//////////////////////////////////////////////////
// template parameters
//////////////////////////////////////////////////
$localhost_preview = '0';
$baseURL = 'https://www.gawrjuhs.art/';
$users = [];
$users[] = [
    'username' => sanitizeString('admin'),
    'password_hash' => trim('$2y$10$YwgwUqnurPEHCssc5ba5xeFuEz12QkbZjm3iLoYKLtaD2sMuvv9zO '),
    'groups' => array_map('Volt\\sanitizeString', explode(',', 'administrator,editor')),
];
$users[] = [
    'username' => sanitizeString(''),
    'password_hash' => trim(''),
    'groups' => array_map('Volt\\sanitizeString', explode(',', '')),
];
$users[] = [
    'username' => sanitizeString(''),
    'password_hash' => trim(''),
    'groups' => array_map('Volt\\sanitizeString', explode(',', '')),
];
$users[] = [
    'username' => sanitizeString(''),
    'password_hash' => trim(''),
    'groups' => array_map('Volt\\sanitizeString', explode(',', '')),
];
$users[] = [
    'username' => sanitizeString(''),
    'password_hash' => trim(''),
    'groups' => array_map('Volt\\sanitizeString', explode(',', '')),
];
$required_editor_groups = trim('');
$scale_image = '0';
$image_width = '2000';
$image_height = '2000';
$perform_backup = '1';
$is_preview = 'false';

//////////////////////////////////////////////////
// constants and variables
//////////////////////////////////////////////////
const BASE_FOLDER_CONTENT = '_cms';
const BASE_FOLDER_BACKUP = 'backup';
const BASE_FOLDER_TEXT = 'text';
const BASE_FOLDER_BLOG = 'blog';
const BASE_FOLDER_MEDIA = 'media';
const BASE_PATH = __DIR__ . DIRECTORY_SEPARATOR . '..' . DIRECTORY_SEPARATOR . BASE_FOLDER_CONTENT . DIRECTORY_SEPARATOR;
const BASE_PATH_TEXT = BASE_PATH . BASE_FOLDER_TEXT . DIRECTORY_SEPARATOR;
const BASE_PATH_BLOG = BASE_PATH . BASE_FOLDER_BLOG . DIRECTORY_SEPARATOR;
const BASE_PATH_MEDIA = BASE_PATH . BASE_FOLDER_MEDIA . DIRECTORY_SEPARATOR;
const BASE_PATH_BACKUP = BASE_PATH . BASE_FOLDER_BACKUP . DIRECTORY_SEPARATOR;
const FILE_EXT_TXT = 'txt';
const MAX_LOGIN_ATTEMPTS = 10;
const SESSION_LOGIN = 'login';
const SESSION_LOGIN_ATTEMPTS = 'login_attempts';
const SESSION_LOGIN_CSRF_TOKEN = 'X-CSRF-Token';
const SESSION_LOGIN_USERNAME = 'login_username';
const SESSION_LOGIN_GROUPS = 'login_groups';
const SESSION_LOGIN_AUTHORIZATION_EDITOR = 'authorization_editor';
const HTTP_X_CSRF_TOKEN = 'HTTP_X_CSRF_TOKEN';
const HTTP_ORIGIN = 'HTTP_ORIGIN';
const HTACCESS = 'Deny from all';
const VOLT_CONTENT = 'volt-content-';
const VOLT_BLOG_ITEM = 'volt-blog-item-';
const IMAGE_REGEX = '%<img.*?src=[\"\'](.*?)[\"\'].*?>%i';
const LOGIN_REGEX = '%<a class="volt-protect-login-page" href="(.*?)"></a>%i';
const FORBIDDEN_REGEX = '%<a class="volt-protect-forbidden-page" href="(.*?)"></a>%i';

DEFINE('ENABLED_ZIP', extension_loaded('zip'));
DEFINE('ENABLED_GD', extension_loaded('gd'));
if ($perform_backup === '1' && ENABLED_ZIP) {
    DEFINE('ENABLED_BACKUP', true);
} else {
    DEFINE('ENABLED_BACKUP', false);
}
DEFINE('NOW', (new \DateTime())->format('Y-m-d\TH:i'));
DEFINE('NOW_MILLIS', getDateMillis(NOW));

$page = 1;
$post = "";
$author = "";
$category = "";
$date = "";
$tag;
$filter;
$search = "";
$search_array = [];
$query = "";
$display_detail = false;
$display_filter = false;
$last_page = 1;
$author_archive = array();
$category_archive = array();
$date_year_archive = array();
$date_month_archive = array();
$tag_archive = array();
$feed = "";

$page_path = dirname($_SERVER['PHP_SELF']);
if (substr($page_path, -1) !== '/') {
    $page_path = $page_path . '/';
}
$fully_qualified_page_path = 'http' . (isset($_SERVER['HTTPS']) ? 's' : '') . '://' . $_SERVER['HTTP_HOST'] . $page_path;
$fully_qualified_domain = 'http' . (isset($_SERVER['HTTPS']) ? 's' : '') . '://' . $_SERVER['HTTP_HOST'];

//////////////////////////////////////////////////
// start session, create folders
//////////////////////////////////////////////////
initializeContent();

if ($localhost_preview === '1' &&
    $baseURL !== 'http://setTheBaseURLInProjectSettings.com/' &&
    (
        (array_key_exists('api', $_GET) && array_key_exists('localhost_preview', $_GET)) || array_key_exists('debug', $_GET))
    ) {
    $localhost_preview = true;
    setHeader('Access-Control-Allow-Origin', '*');
} else {
    $localhost_preview = false;
}

if ($scale_image === '1' && ENABLED_GD && is_numeric($image_width) && is_numeric($image_height)) {
    $scale_image = true;
    $image_width = $image_width + 0;
    $image_height = $image_height + 0;
} else {
    $scale_image = false;
}

//////////////////////////////////////////////////
// check for HTTP method GET
//////////////////////////////////////////////////
if ($_SERVER['REQUEST_METHOD'] == 'GET') {

    if (array_key_exists('api', $_GET)) {

        // is user logged in
        if ($_GET['api'] == 'login') {
            startSession();
            if (array_key_exists(HTTP_X_CSRF_TOKEN, $_SERVER) && $_SERVER[HTTP_X_CSRF_TOKEN] === 'fetch') {
                setHeader(SESSION_LOGIN_CSRF_TOKEN, $_SESSION[SESSION_LOGIN_CSRF_TOKEN]);
            }
            echo getJsonLoginInfo();
            exit();
        }

        // get content by filename
        if ($_GET['api'] == 'content' && array_key_exists('name', $_GET)) {
            $file = BASE_PATH_TEXT . sanitizeString($_GET['name']) . '.' . FILE_EXT_TXT;
            if (file_exists($file)) {
                $content = file_get_contents($file);
                if ($localhost_preview) {
                    $content = str_replace('src="/' . BASE_FOLDER_CONTENT, 'src="' . $baseURL . BASE_FOLDER_CONTENT, $content);
                }
                echo $content;
            }
            exit();
        }

        // get blog item
        if ($_GET['api'] == 'blog' && array_key_exists('blog-id', $_GET) && array_key_exists('id', $_GET)) {
            setHeader('Content-Type', 'application/json');
            $blog_id = $_GET['blog-id'];
            $id = $_GET['id'];
            $db = new \PragmaPHP\FileDB\FileDB(BASE_PATH_BLOG . $blog_id);
            $data = $db->read($id);
            if (!empty($data) && count($data) === 1) {
                echo json_encode($data[0]);
            }
            exit();
        }

        // get blog
        if ($_GET['api'] == 'blog' && array_key_exists('blog-id', $_GET)) {
            if (strpos($_SERVER['HTTP_ACCEPT'], 'application/json') !== false) {
                setHeader('Content-Type', 'application/json');
                $blog_id = $_GET['blog-id'];
                $db = new \PragmaPHP\FileDB\FileDB(BASE_PATH_BLOG . $blog_id);
                $data = $db->readAll();
                echo json_encode(['blog' => $data]);
                exit();
            } else {
                $content = getBlogContent(
                    $_GET['blog-id'],
                    $_GET['blog-items'],
                    $_GET['blog-layout'],
                    $_GET['blog-date-pattern'],
                    $_GET['blog-hide-future-items'],
                    $_GET['blog-author-text'],
                    $_GET['blog-category-text'],
                    $_GET['blog-tag-text'],
                    $_GET['blog-separator-text'],
                    $_GET['blog-readmore-text'],
                    $_GET['blog-back-text'],
                    $_GET['blog-prev-post-text'],
                    $_GET['blog-next-post-text'],
                    $_GET['blog-prev-page-text'],
                    $_GET['blog-next-page-text'],
                    1,  // replace_metadata
                    0,  // htaccess_rewrite
                    '', // rss_publisher
                    '', // rss_title
                    '', // rss_description
                    'en-us', // rss_language
                    '', // rss_image
                    $_GET['blog-heading-list'],
                    $_GET['blog-heading-detail'],
                    true, // scroll_detail_top
                    '', // disqus_shorthand
                    $_GET['blog-metadata-display'],
                    $_GET['blog-share-display']
                );
                if ($localhost_preview) {
                    $content = str_replace('src="/' . BASE_FOLDER_CONTENT, 'src="' . $baseURL . BASE_FOLDER_CONTENT, $content);
                }
                echo $content;
            }
        }

        // get content list
        if ($_GET['api'] == 'content') {
            setHeader('Content-Type', 'application/json');
            if (isLoggedIn()) {
                $files = array();
                $filesTemp = glob(BASE_PATH_TEXT . '*');
                foreach ($filesTemp as $file) {
                    $ext = strtolower(pathinfo($file, PATHINFO_EXTENSION));
                    if ($ext == FILE_EXT_TXT) {
                        $files[] = [
                            'url' => '/' . BASE_FOLDER_CONTENT . '/' . BASE_FOLDER_TEXT . '/' . basename($file),
                            'filesize' => filesize($file),
                            'name' => pathinfo($file)['filename'],
                        ];
                    }
                }
                echo json_encode(['content' => $files]);
                exit();
            } else {
                echo json_encode(['content' => []]);
                exit();
            }
        }

        // get image list
        if ($_GET['api'] == 'image') {
            setHeader('Content-Type', 'application/json');
            if (isLoggedIn()) {
                $files = array();
                $filesTemp = array_filter(glob(BASE_PATH_MEDIA . '*'), 'is_file');
                usort($filesTemp, function ($a, $b) {
                    return filemtime($b) <=> filemtime($a);
                });
                foreach ($filesTemp as $file) {
                    $files[] = getImageInfo($file);
                }
                echo json_encode(['image' => $files]);
                exit();
            } else {
                echo json_encode(['image' => []]);
                exit();
            }
        }

    }

    // debug parameters
    if (array_key_exists('debug', $_GET)) {
        setHeader('Content-Type', 'application/json');
        $user_exist = false;
        foreach ($users as $key => $user) {
            if (!empty($user) && !empty($user['username']) && !empty($user['password_hash'])) {
                $user_exist = true;
            }
        }
        echo json_encode([
            'php_version' => phpversion(),
            'php_post_max_size' => ini_get('post_max_size'),
            'php_enabled_zip' => ENABLED_ZIP,
            'php_enabled_gd' => ENABLED_GD,
            'php_document_root' => $_SERVER['DOCUMENT_ROOT'],
            'php_script_filename' => $_SERVER['SCRIPT_FILENAME'],
            'php_context_document_root' => $_SERVER['CONTEXT_DOCUMENT_ROOT'],
            'version' => VERSION,
            'localhost_preview' => $localhost_preview,
            'baseURL' => $baseURL,
            'file_permission' => substr(sprintf('%o', fileperms(__DIR__ . DIRECTORY_SEPARATOR . '..' . DIRECTORY_SEPARATOR)), -4),
            'scale_image' => $scale_image,
            'image_width' => $image_width,
            'image_height' => $image_height,
            'enabled_backup' => ENABLED_BACKUP,
            'user_exist' => $user_exist,
            'cms_folder_exist' => file_exists(BASE_PATH),
            'time' => NOW,
        ]);
        exit();
    }

}

//////////////////////////////////////////////////
// check for HTTP method POST
//////////////////////////////////////////////////
if ($_SERVER['REQUEST_METHOD'] == 'POST') {

    if (array_key_exists('api', $_REQUEST)) {

        // log in
        if ($_REQUEST['api'] == 'login') {
            startSession();
            if (array_key_exists('username', $_REQUEST) &&
                array_key_exists('password', $_REQUEST) &&
                array_key_exists(HTTP_X_CSRF_TOKEN, $_SERVER) &&
                !empty($_REQUEST['username']) &&
                !empty($_REQUEST['password']) &&
                !empty($_SERVER[HTTP_X_CSRF_TOKEN])) {
                if ($_SESSION[SESSION_LOGIN_ATTEMPTS] < MAX_LOGIN_ATTEMPTS && hash_equals($_SESSION[SESSION_LOGIN_CSRF_TOKEN], $_SERVER[HTTP_X_CSRF_TOKEN])) {
                    $username = sanitizeString($_REQUEST['username']);
                    $password = $_REQUEST['password'];
                    foreach ($users as $key => $user) {
                        if (!empty($user) && !empty($user['username']) && !empty($user['password_hash'])) {
                            if ($username == $user['username'] && password_verify($password, $user['password_hash'])) {
                                backupContent();
                                $_SESSION[SESSION_LOGIN] = true;
                                $_SESSION[SESSION_LOGIN_USERNAME] = $user['username'];
                                $_SESSION[SESSION_LOGIN_GROUPS] = $user['groups'];
                                $_SESSION[SESSION_LOGIN_ATTEMPTS] = 0;
                                $_SESSION[SESSION_LOGIN_AUTHORIZATION_EDITOR] = isMemberOfGroup($required_editor_groups);
                                echo getJsonLoginInfo();
                                exit();
                            }
                        }
                    }
                }
            }
            // no match, raise login attempts
            $_SESSION[SESSION_LOGIN_ATTEMPTS]++;
            http_response_code(401);
            echo getJsonLoginInfo();
            exit();
        }

        // log out
        if ($_REQUEST['api'] == 'logout') {
            startSession();
            $_SESSION[SESSION_LOGIN] = false;
            $_SESSION[SESSION_LOGIN_USERNAME] = '';
            $_SESSION[SESSION_LOGIN_GROUPS] = [];
            $_SESSION[SESSION_LOGIN_ATTEMPTS] = 0;
            $_SESSION[SESSION_LOGIN_AUTHORIZATION_EDITOR] = false;
            echo getJsonLoginInfo();
            exit();
        }

        // set content by filename
        if ($_REQUEST['api'] == 'content') {
            enforceEditor();
            foreach ($_REQUEST as $key => $value) {
                if (strpos($key, VOLT_CONTENT) === 0 && strpos($key, VOLT_CONTENT . VOLT_BLOG_ITEM) === false) {
                    $key = substr($key, 13);
                    $file = BASE_PATH_TEXT . sanitizeString($key) . '.' . FILE_EXT_TXT;
                    file_put_contents($file, $value);
                }
            }
            exit();
        }

        // set blog item
        if ($_REQUEST['api'] == 'blog') {
            enforceEditor();
            setHeader('Content-Type', 'application/json');
            $blog_item = [];
            foreach ($_REQUEST as $key => $value) {
                if (strpos($key, VOLT_BLOG_ITEM) === 0) {
                    $key = substr($key, 15);
                    $blog_item[$key] = $value;
                }
            }
            $blog_id = $blog_item['blog-id'];
            unset($blog_item['blog-id']);
            $id = '';
            if (isset($blog_item['id'])) {
                $id = $blog_item['id'];
                unset($blog_item['id']);
            }
            if (!empty($blog_id)) {
                $db = new \PragmaPHP\FileDB\FileDB(BASE_PATH_BLOG . $blog_id);
                if (empty($id)) {
                    $id = $db->create($blog_item);
                } else {
                    $id = $db->update($id, $blog_item);
                }
                echo json_encode(['_id' => $id]);
            }
            exit();
        }

        // upload image
        if ($_REQUEST['api'] == 'image') {
            enforceEditor();
            if (!empty($_FILES)) {
                $image = current($_FILES);
                if (!empty($image) && $image['error'] == UPLOAD_ERR_OK) {
                    $name = sanitizeString(pathinfo($image['name'], PATHINFO_FILENAME));
                    $ext = strtolower(pathinfo($image['name'], PATHINFO_EXTENSION));
                    $file = BASE_PATH_MEDIA . $name . '.' . $ext;
                    if (file_exists($file)) {
                        $file = BASE_PATH_MEDIA . $name . '_' . base_convert(time(), 10, 36) . '.' . $ext;
                    }
                    if (move_uploaded_file($image['tmp_name'], $file)) {
                        $file = scaleImage($file, $scale_image, $image_width, $image_height);
                        chmod($file, 0644);
                        echo json_encode(getImageInfo($file));
                        exit();
                    }
                }
            }
            http_response_code(401);
            exit();
        }

    }

}

//////////////////////////////////////////////////
// check for HTTP method DELETE
//////////////////////////////////////////////////
if ($_SERVER['REQUEST_METHOD'] == 'DELETE') {

    if (array_key_exists('api', $_REQUEST)) {

        // delete image
        if ($_REQUEST['api'] == 'image' && array_key_exists('name', $_REQUEST)) {
            enforceEditor();
            $name = sanitizeString(pathinfo($_REQUEST['name'], PATHINFO_FILENAME));
            $ext = strtolower(pathinfo($_REQUEST['name'], PATHINFO_EXTENSION));
            $file = BASE_PATH_MEDIA . $name . '.' . $ext;
            if (file_exists($file)) {
                echo BASE_PATH_MEDIA . $name;
                unlink($file);
            }
            exit();
        }

        // delete blog item
        if ($_REQUEST['api'] == 'blog' && array_key_exists('blog-id', $_REQUEST) && array_key_exists('id', $_REQUEST)) {
            enforceEditor();
            $blog_id = $_REQUEST['blog-id'];
            $id = $_REQUEST['id'];
            if (!empty($blog_id) && !empty($id)) {
                $db = new \PragmaPHP\FileDB\FileDB(BASE_PATH_BLOG . $blog_id);
                $db->delete($id);
            }
            exit();
        }

    }

}

//////////////////////////////////////////////////
// functions
//////////////////////////////////////////////////
function setHeader($key, $value)
{
    header($key . ': ' . $value);
}

function sanitizeString($value)
{
    $value = trim($value);
    $value = strtolower($value);
    $value = preg_replace('/\s+/', '-', $value);
    $value = preg_replace('/[^a-z0-9_\-]+/', '', $value);
    return $value;
}

function initializeContent()
{
    if (!file_exists(BASE_PATH)) {
        mkdir(BASE_PATH);
        chmod(BASE_PATH, 0755);
    }
    if (!file_exists(BASE_PATH_TEXT)) {
        mkdir(BASE_PATH_TEXT);
        chmod(BASE_PATH_TEXT, 0755);
    }
    if (!file_exists(BASE_PATH_TEXT . '.htaccess')) {
        file_put_contents(BASE_PATH_TEXT . '.htaccess', HTACCESS);
    }
    if (!file_exists(BASE_PATH_BLOG)) {
        mkdir(BASE_PATH_BLOG);
        chmod(BASE_PATH_BLOG, 0755);
    }
    if (!file_exists(BASE_PATH_BLOG . '.htaccess')) {
        file_put_contents(BASE_PATH_BLOG . '.htaccess', HTACCESS);
    }
    if (!file_exists(BASE_PATH_MEDIA)) {
        mkdir(BASE_PATH_MEDIA);
        chmod(BASE_PATH_MEDIA, 0755);
    }
    if (!file_exists(BASE_PATH_BACKUP)) {
        mkdir(BASE_PATH_BACKUP);
        chmod(BASE_PATH_BACKUP, 0755);
    }
    if (!file_exists(BASE_PATH_BACKUP . '.htaccess')) {
        file_put_contents(BASE_PATH_BACKUP . '.htaccess', HTACCESS);
    }
}

function backupContent()
{
    if (ENABLED_BACKUP) {
        zipFiles(BASE_PATH_BACKUP . 'backup_' . date('Y-m-d') . '_text.zip', BASE_PATH_TEXT);
        $blog_dirs = array_filter(glob(BASE_PATH_BLOG . '*'), 'is_dir');
        foreach ($blog_dirs as $folder) {
            zipFiles(BASE_PATH_BACKUP . 'backup_' . date('Y-m-d') . '_blog_' . basename($folder) . '.zip', $folder . DIRECTORY_SEPARATOR);
        }
    }
}

function zipFiles($zipFile, $folder)
{
    if (!file_exists($zipFile)) {
        $files = glob($folder . '*');
        if (count($files) > 0) {
            $zip = new \ZipArchive();
            $zip->open($zipFile, \ZipArchive::CREATE | \ZipArchive::OVERWRITE);
            foreach ($files as $file) {
                $zip->addFile($file, basename($file));
            }
            $zip->close();
        }
    }
}

function getJsonLoginInfo()
{
    setHeader('Content-Type', 'application/json');
    return json_encode([
        'login' => $_SESSION[SESSION_LOGIN],
        'username' => $_SESSION[SESSION_LOGIN_USERNAME],
        'editor' => $_SESSION[SESSION_LOGIN_AUTHORIZATION_EDITOR],
    ]);
}

function startSession()
{
    if (session_status() === PHP_SESSION_NONE) {
        $session_settings = [
            'httponly' => true,
            'samesite' => 'Strict',
            'secure' => isset($_SERVER['HTTPS']) ? true : false
        ];
        session_set_cookie_params($session_settings);
        $s = session_start();
        if ($s) {
            if (!array_key_exists(SESSION_LOGIN, $_SESSION)) {
                $_SESSION[SESSION_LOGIN] = false;
            }
            if (!array_key_exists(SESSION_LOGIN_USERNAME, $_SESSION)) {
                $_SESSION[SESSION_LOGIN_USERNAME] = '';
            }
            if (!array_key_exists(SESSION_LOGIN_GROUPS, $_SESSION)) {
                $_SESSION[SESSION_LOGIN_GROUPS] = [];
            }
            if (!array_key_exists(SESSION_LOGIN_ATTEMPTS, $_SESSION)) {
                $_SESSION[SESSION_LOGIN_ATTEMPTS] = 0;
            }
            if (!array_key_exists(SESSION_LOGIN_AUTHORIZATION_EDITOR, $_SESSION)) {
                $_SESSION[SESSION_LOGIN_AUTHORIZATION_EDITOR] = false;
            }
            if (!array_key_exists(SESSION_LOGIN_CSRF_TOKEN, $_SESSION)) {
                if (function_exists('random_bytes')) {
                    $_SESSION[SESSION_LOGIN_CSRF_TOKEN] = bin2hex(random_bytes(32));
                } else {
                    $_SESSION[SESSION_LOGIN_CSRF_TOKEN] = bin2hex(rand());
                }
            }
        }
    }
}

function isLoggedIn()
{
    startSession();
    return (!empty($_SESSION) && array_key_exists(SESSION_LOGIN, $_SESSION) && $_SESSION[SESSION_LOGIN] === true);
}

function enforceLogin()
{
    if (!isLoggedIn()) {
        http_response_code(401);
        echo getJsonLoginInfo();
        exit();
    }
}

function enforceEditor()
{
    global $required_editor_groups;
    enforceLogin();
    if (!isMemberOfGroup($required_editor_groups)) {
        http_response_code(401);
        echo getJsonLoginInfo();
        exit();
    }
}

function isMemberOfGroup($required_groups)
{
    $required_groups = array_map('Volt\\sanitizeString', explode(',', $required_groups));
    if (!empty($required_groups[0]) && empty(array_intersect($required_groups, $_SESSION[SESSION_LOGIN_GROUPS]))) {
        return false;
    } else {
        return true;
    }
}

function protectPage($login_redirect, $forbidden_redirect, $required_groups)
{
    if (!isLoggedIn()) {
        if ($login_redirect) {
            header('Location: ' . getLoginPage(ob_get_clean()) . '?ref=' . $_SERVER['REQUEST_URI']);
            exit();
        } else {
            if ($forbidden_redirect) {
                header('Location: ' . getForbiddenPage(ob_get_clean()) . '?ref=' . $_SERVER['REQUEST_URI']);
                exit();
            } else {
                ob_end_clean();
                http_response_code(401);
                echo "<h1>Forbidden</h1>";
                exit;
            }
        }
    } else {
        if (!isMemberOfGroup($required_groups)) {
            if ($forbidden_redirect) {
                header('Location: ' . getForbiddenPage(ob_get_clean()) . '?ref=' . $_SERVER['REQUEST_URI']);
                exit();
            } else {
                ob_end_clean();
                http_response_code(401);
                echo "<h1>Forbidden</h1>";
                exit;
            }
        }
    }
}

function getImageInfo($file)
{
    $size_temp = getimagesize($file);
    if (is_array($size_temp)) {
        $size = [$size_temp[0], $size_temp[1]];
    } else {
        $size = [1,1];
    }
    return [
        'url' => '/' . BASE_FOLDER_CONTENT . '/' . BASE_FOLDER_MEDIA . '/' . basename($file),
        'size' => $size,
        'name' => pathinfo($file)['filename'],
        'filesize' => filesize($file),
    ];
}

function scaleImage($file, $scale_image, $image_width, $image_height)
{
    if ($scale_image) {
        $image_info = getimagesize($file);
        if ($image_info[0] > $image_width || $image_info[1] > $image_height) {
            $image = false;
            $ratio_width = $image_width / $image_info[0];
            $ratio_height = $image_height / $image_info[1];
            $ratio = min($ratio_width, $ratio_height);
            $new_width = (int) $image_info[0] * $ratio;
            switch ($image_info[2]) {
                // IMAGETYPE_JPEG / IMG_JPG
                case 2:
                    if (imagetypes() & 2) {
                        $image = imagecreatefromjpeg($file);
                        $image = imagescale($image, $new_width);
                        imagejpeg($image, $file);
                    }
                    break;
                default:
                    break;
            }
        }
    }
    return $file;
}

function parseBlogURL()
{
    global $page, $post, $author, $category, $date, $tag, $search, $search_array, $sort_by, $feed, $query, $display_detail, $display_filter, $filter;
    if ($_SERVER['REQUEST_METHOD'] == 'GET') {
        $urlQuery = parse_url($_SERVER['REQUEST_URI'], PHP_URL_QUERY);
        if (!$urlQuery) {
            $urlQuery = '';
        }
        parse_str($urlQuery, $parsedUrl);
        if (isset($parsedUrl['page'])) {
            $page = $parsedUrl['page'];
        }
        if (isset($parsedUrl['post'])) {
            $post = $parsedUrl['post'];
        } else if (isset($_REQUEST['post'])) {
            $post = $_REQUEST['post'];
        }
        if (isset($parsedUrl['author'])) {
            $author = $parsedUrl['author'];
        } else if (isset($_REQUEST['author'])) {
            $author = $_REQUEST['author'];
        }
        if (isset($parsedUrl['category'])) {
            $category = $parsedUrl['category'];
        } else if (isset($_REQUEST['category'])) {
            $category = $_REQUEST['category'];
        }
        if (isset($parsedUrl['date'])) {
            $date = $parsedUrl['date'];
        } else if (isset($_REQUEST['date'])) {
            $date = $_REQUEST['date'];
        }
        if (isset($parsedUrl['tag'])) {
            $tag = $parsedUrl['tag'];
        } else if (isset($_REQUEST['tag'])) {
            $tag = $_REQUEST['tag'];
        }
        if (isset($parsedUrl['search'])) {
            $search = $parsedUrl['search'];
            $search_array = buildSearchArray($search);
        } else if (isset($_REQUEST['search'])) {
            $search = $_REQUEST['search'];
            $search_array = buildSearchArray($search);
        }
        if (isset($parsedUrl['sort_by'])) {
            $sort_by = $parsedUrl['sort_by'];
        } else if (isset($_REQUEST['sort_by'])) {
            $sort_by = $_REQUEST['sort_by'];
        }
        if (isset($parsedUrl['feed'])) {
            $feed = $parsedUrl['feed'];
        } else if (isset($_REQUEST['feed'])) {
            $feed = $_REQUEST['feed'];
        }
    } else if ($_SERVER['REQUEST_METHOD'] == 'POST') {
        if (isset($_REQUEST['search'])) {
            $search = $_REQUEST['search'];
            $search_array = buildSearchArray($search);
        }
        if (isset($_REQUEST['sort_by'])) {
            $sort_by = $_REQUEST['sort_by'];
        }
    }

    if (!is_numeric($page)) {
        $page = 1;
    }
    if ($post != "") {
        $display_detail = true;
    }
    if ($category != "" || $date != "" || $author != "" || $search != "") {
        $display_filter = true;
    }
    if (!empty($author)) {
        $query = $query . "&author=" . $author;
        $filter = $author;
    }
    if (!empty($category)) {
        $query = $query . "&category=" . $category;
        $filter = $category;
    }
    if (!empty($date)) {
        $query = $query . "&date=" . $date;
        $filter = $date;
    }
    if (!empty($tag)) {
        $query = $query . "&tag=" . $tag;
        $filter = $tag;
    }
    if (!empty($search)) {
        $query = $query . "&search=" . $search;
        $filter = $search;
    }
}

function slugify($slug)
{
    $slug = preg_replace('~[^\pL\d]+~u', '-', $slug);
    $slug = trim($slug, '-');
    $slug = preg_replace('~-+~', '-', $slug);
    $slug = strtolower($slug);
    if (empty($slug)) {
        return 'n-a';
    }
    return $slug;
}

function buildFilterArray(&$values, &$filter_archive)
{
    $array = array();
    $s = "";
    if (!empty($values)) {
        foreach ($values as $value) {
            if ($value != "") {
                $s = slugify($value);
                array_push($array, $s);
                $filter_archive[$s] = $value;
            }
        }
    }
    return $array;
}

function buildCategoryArray(&$values)
{
    global $category_archive;
    return buildFilterArray($values, $category_archive);
}

function buildDateArray($date_str)
{
    global $date_year_archive, $date_month_archive;
    if (is_int($date_str)) {
        $date_str = date("Y-m-d", $date_str);
    }
    if (!empty($date_str)) {
        $s = substr($date_str, 0, 4);
        $date_year_archive[$s] = $s;
        $s = substr($date_str, 0, 7);
        $date_month_archive[$s] = $s;
    }
}

function buildAuthorArray(&$values)
{
    global $author_archive;
    return buildFilterArray($values, $author_archive);
}

function buildSearchArray($search)
{
    if (!empty($search)) {
        $search_array = explode(' ', $search);
        $search_array = array_filter($search_array, function ($value) {
            return !is_null($value) && $value !== '';
        }
        );
        return $search_array;
    }
}

function full_text_search($haystack)
{
    global $search_array;
    $match = true;
    foreach ($search_array as $search) {
        if (stripos($haystack, $search) === false) {
            $match = false;
        }
    }
    return $match;
}

function buildFilterLinks($array, $values, $label, $separator_text, $htaccess_rewrite, $filter_name)
{
    $result = '';
    if (!empty($array)) {
        $result = $label;
        for ($i = 0, $count = count($array); $i < $count; $i++) {
            if ($htaccess_rewrite) {
                if ($i === 0) {
                    $result = $result . ' <a href="./' . $filter_name . '/' . $array[0] . '">' . htmlspecialchars($values[$i], ENT_QUOTES) . '</a>';
                } else {
                    $result = $result . $separator_text . '<a href="./' . $filter_name . '/' . $array[$i] . '">' . htmlspecialchars($values[$i], ENT_QUOTES) . '</a>';
                }
            } else {
                if ($i === 0) {
                    $result = $result . ' <a href="?' . $filter_name . '=' . $array[0] . '">' . htmlspecialchars($values[$i], ENT_QUOTES) . '</a>';
                } else {
                    $result = $result . $separator_text . '<a href="?' . $filter_name . '=' . $array[$i] . '">' . htmlspecialchars($values[$i], ENT_QUOTES) . '</a>';
                }
            }

        }
    }
    return $result;
}

function fixQuery($tempQuery)
{
    if (!empty($tempQuery)) {
        if (strpos($tempQuery, '&') === 0) {
            $tempQuery = '?' . substr($tempQuery, 1);
        }
    }
    return $tempQuery;
}

function getPostLink($slug, $htaccess_rewrite)
{
    global $query;
    if ($htaccess_rewrite) {
        return './' . $slug . fixQuery($query);
    } else {
        return './?post=' . $slug . $query;
    }
}

function getPostPermalink($slug, $htaccess_rewrite)
{
    $dirname = dirname($_SERVER['PHP_SELF']);
    if (!empty($dirname) && substr($dirname, -1) !== '/') {
        $dirname = $dirname . '/';
    }
    if ($htaccess_rewrite) {
        return 'http' . (isset($_SERVER['HTTPS']) ? 's' : '') . '://' . $_SERVER['HTTP_HOST'] . $dirname . $slug;
    } else {
        return 'http' . (isset($_SERVER['HTTPS']) ? 's' : '') . '://' . $_SERVER['HTTP_HOST'] . $dirname . "?post=" . $slug;
    }
}

function isPostInQuery($category_array, $date_str, $author_array, $text_array = null, $hide_future_items = 0)
{
    global $category, $date, $author, $search;
    if (is_int($date_str)) {
        $date_str = date("Y-m-d", $date_str);
    }
    if (!empty($category) && !in_array($category, $category_array)) {
        return false;
    }
    if (!empty($date) && (strpos($date_str, $date) !== 0)) {
        return false;
    }
    if (!empty($author) && !in_array($author, $author_array)) {
        return false;
    }
    if (!empty($search) && !empty($text_array) && empty(array_filter($text_array, "Volt\\full_text_search"))) {
        return false;
    }
    if ($hide_future_items && $date_str > NOW) {
        return false;
    }
    return true;
}

function replaceMetadataRssLink($page_buffer, $title)
{
    global $page_path;
    $page_buffer = str_replace('<he' . 'ad>', '<he' . 'ad>' . "\n" . '<link rel="alternate" type="application/rss+xml" title="' . $title . '"  href="' . $page_path . '?feed=rss">', $page_buffer);
    return $page_buffer;
}

function replaceMetadataTitle($page_buffer, $title)
{
    global $page_path;
    $page_buffer = preg_replace('/(<title)(.*?)(<\/title>)/i', '$1' . '$2' . ' - ' . htmlspecialchars($title, ENT_QUOTES) . '$3', $page_buffer);
    return $page_buffer;
}

function replaceMetadataDocumentBase($page_buffer)
{
    global $page_path;
    $page_buffer = str_replace('<he' . 'ad>', '<he' . 'ad>' . "\n" . '<base href="' . $page_path . '">', $page_buffer);
    return $page_buffer;
}

function replaceMetadataTag($buffer, $key, $value)
{
    $index = strpos($buffer['header'], $key);
    if ($index !== false) {
        $buffer['header'] = preg_replace('/(' . $key . ')(.*?)(">)/i', '$1' . '"' . $value . '$3', $buffer['header']);
    } else {
        $buffer['new_content'] = $buffer['new_content'] . $key . '"' . $value . '">' . "\n";
    }
    return $buffer;
}

function replaceMetadata($page_buffer, $detail_item, $rss_data)
{
    // global $metadata_fb_username, $metadata_twitter_username;
    // print_r($detail_item);
    $page_buffer = preg_replace('/(<title)(.*?)(<\/title>)/i', '$1' . '>' . $detail_item['title'] . '$3', $page_buffer);
    $index_header_start = strpos($page_buffer, '<he' . 'ad>');
    $index_header_end = strpos($page_buffer, '</he' . 'ad>');
    $header = substr($page_buffer, $index_header_start, $index_header_end - $index_header_start);
    $buffer = [
        'header' => $header,
        'new_content' => '',
    ];
    $buffer['new_content'] = $buffer['new_content'] . '<link rel="canonical" href="' . $detail_item['permalink'] . '" />' . "\n";
    $buffer = replaceMetadataTag($buffer, '<meta property="og:type" content=', 'article');
    $index = strpos($buffer['header'], '<meta name="twitter:card" content="summary">');
    if (!$index) {
        $buffer['new_content'] = $buffer['new_content'] . '<meta name="twitter:card" content="summary">' . "\n";
    }
    $buffer = replaceMetadataTag($buffer, '<meta property="og:title" content=', $detail_item['title']);
    $buffer = replaceMetadataTag($buffer, '<meta name="twitter:title" content=', $detail_item['title']);
    $buffer = replaceMetadataTag($buffer, '<meta property="og:site_name" content=', $detail_item['title']);
    // if ($metadata_fb_username) {
    //     $buffer = replaceMetadataTag($buffer, '<meta property="fb:admins" content=', $metadata_fb_username);
    // }
    // if ($metadata_twitter_username) {
    //     $buffer = replaceMetadataTag($buffer, '<meta name="twitter:site" content=', '@' . $metadata_twitter_username);
    // }
    $buffer = replaceMetadataTag($buffer, '<meta property="og:url" content=', $detail_item['permalink']);
    $buffer = replaceMetadataTag($buffer, '<meta name="twitter:url" content=', $detail_item['permalink']);

    $buffer = replaceMetadataTag($buffer, '<meta name="description" content=', $detail_item['description']);
    $buffer = replaceMetadataTag($buffer, '<meta property="og:description" content=', $detail_item['description']);
    $buffer = replaceMetadataTag($buffer, '<meta name="twitter:description" content=', $detail_item['description']);
    if ($detail_item['preview_image']) {
        $buffer = replaceMetadataTag($buffer, '<meta property="og:image" content=', $detail_item['preview_image']);
        $buffer = replaceMetadataTag($buffer, '<meta name="twitter:image" content=', $detail_item['preview_image']);
    }
    $buffer['new_content'] = $buffer['new_content'] . '<meta property="fb:app_id" content="966242223397117">' . "\n";
    $structured_data = [
        "@context" => "https://schema.org",
        "@type" => "NewsArticle",
        "mainEntityOfPage" => [
            "@type" => "WebPage",
            "@id" => $detail_item['permalink'],
        ],
        "headline" => $detail_item['title'],
        "image" => [
            $detail_item['preview_image'],
        ],
        "datePublished" => $detail_item['date'],
        "dateModified" => $detail_item['date'],
        "author" => [
            "@type" => "Person",
            "name" => $detail_item['author'],
        ],
        "publisher" => [
            "@type" => "Organization",
            "name" => $rss_data['rss_publisher'],
            "logo" => [
                "@type" => "ImageObject",
                "url" => $rss_data['rss_image'],
            ],
        ],
        "description" => $detail_item['description'],
    ];
    $json_structured_data = '<script type="application/ld+json">' . "\n";
    $json_structured_data = $json_structured_data . json_encode($structured_data);
    $json_structured_data = $json_structured_data . "\n" . '</script>';
    $buffer['new_content'] = $buffer['new_content'] . $json_structured_data;
    $header = $buffer['header'] . $buffer['new_content'] . "\n";
    return $page_buffer = substr($page_buffer, 0, $index_header_start) . $header . substr($page_buffer, $index_header_end);
}

function getImageSrc($html)
{
    $match = array();
    preg_match(IMAGE_REGEX, $html, $match);
    if (!empty($match[1])) {
        return $match[1];
    }
    return '';
}

function getLoginPage($html)
{
    $match = array();
    preg_match(LOGIN_REGEX, $html, $match);
    if (!empty($match[1])) {
        return $match[1];
    }
    return '';
}

function getForbiddenPage($html)
{
    $match = array();
    preg_match(FORBIDDEN_REGEX, $html, $match);
    if (!empty($match[1])) {
        return $match[1];
    }
    return '';
}

function getFullyQualifiedImageURL($src)
{
    global $fully_qualified_domain;
    if (!empty($src) && stripos($src, 'http') !== 0) {
        $src = $fully_qualified_domain . $src;
        // $src = str_replace('/./', '/', $src);
        $src = str_replace('//_cms', '/_cms', $src);
    }
    return $src;
}

function getDateMillis($date)
{
    if (is_int($date)) {
        $date_millis = $date;
    } else {
        if (strlen($date) > 19) {
            $date_millis = strtotime(substr($date, 0, 19));
        } else {
            $date_millis = strtotime($date);
        }
    }
    return $date_millis;
}

function generateSitemap($rss_items)
{
    header('Content-type: application/xml');
    $html = "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n";
    // $html = $html . "<urlset xmlns:xsi=\"https://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocation=\"https://www.sitemaps.org/schemas/sitemap/0.9/ https://www.sitemaps.org/schemas/sitemap/0.9/sitemap.xsd\" xmlns=\"https://www.sitemaps.org/schemas/sitemap/0.9/sitemap.xsd\" xmlns:image=\"https://www.google.com/schemas/sitemap-image/1.1/sitemap-image.xsd\" xmlns:video=\"https://www.google.com/schemas/sitemap-video/1.1/sitemap-video.xsd\">\n";
    $html = $html . "<urlset xmlns=\"http://www.sitemaps.org/schemas/sitemap/0.9\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocation=\"http://www.sitemaps.org/schemas/sitemap/0.9 http://www.sitemaps.org/schemas/sitemap/0.9/sitemap.xsd http://www.google.com/schemas/sitemap-image/1.1 http://www.google.com/schemas/sitemap-image/1.1/sitemap-image.xsd http://www.google.com/schemas/sitemap-video/1.1 http://www.google.com/schemas/sitemap-video/1.1/sitemap-video.xsd http://www.google.com/schemas/sitemap-news/0.9 http://www.google.com/schemas/sitemap-news/0.9/sitemap-news.xsd\" xmlns:image=\"http://www.google.com/schemas/sitemap-image/1.1\" xmlns:video=\"http://www.google.com/schemas/sitemap-video/1.1\" xmlns:news=\"http://www.google.com/schemas/sitemap-news/0.9\">\n";
    foreach ($rss_items as $key => $detail_item) {
        $html = $html . "<url><loc>" . $detail_item['permalink'] . "</loc>";
        $html = $html . "<lastmod>" . date("Y-m-d", getDateMillis($detail_item['date'])) . "</lastmod>";
        $html = $html . "<changefreq>monthly</changefreq><priority>0.5</priority>";
        if ($detail_item['preview_image']) {
            $html = $html . "<image:image><image:loc>" . $detail_item['preview_image'] . "</image:loc></image:image>";
        }
        $html = $html . "</url>\n";
    }
    $html = $html . "</urlset>";
    return $html;
}

function generateRssFeed($rss_items, $rss_data, $date_pattern)
{
    header('Content-type: application/rss+xml');
    $html = "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n";
    $html = $html . "<rss version=\"2.0\" xmlns:atom=\"http://www.w3.org/2005/Atom\" xmlns:content=\"http://purl.org/rss/1.0/modules/content/\" xmlns:dc=\"http://purl.org/dc/elements/1.1/\" xmlns:itunes=\"http://www.itunes.com/dtds/podcast-1.0.dtd\">\n";
    $html = $html . "<channel>\n";
    $html = $html . "<title>" . $rss_data['rss_title'] . "</title>\n";
    $html = $html . "<link>" . $rss_data['rss_url'] . "</link>\n";
    $feed = "/?feed=rss";
    if (substr($rss_data['rss_url'], -1) == '/') {
        $feed = "?feed=rss";
    }
    $html = $html . "<atom:link href=\"" . $rss_data['rss_url'] . $feed . "\" rel=\"self\" type=\"application/rss+xml\"/>\n";
    $html = $html . "<description>" . $rss_data['rss_description'] . "</description>\n";
    $html = $html . "<language>" . $rss_data['rss_language'] . "</language>\n";
    $html = $html . "<copyright>" . $rss_data['rss_copyright'] . "</copyright>\n";
    $html = $html . "<lastBuildDate>" . date('r', NOW_MILLIS) . "</lastBuildDate>\n";
    $html = $html . "<pubDate>" . date('r', NOW_MILLIS) . "</pubDate>\n";
    if ($rss_data['rss_image']) {
        $html = $html . "<image>\n";
        $html = $html . "<url>" . $rss_data['rss_image'] . "</url>\n";
        $html = $html . "<title>" . $rss_data['rss_title'] . "</title>\n";
        $html = $html . "<link>" . $rss_data['rss_url'] . "</link>\n";
        $html = $html . "</image>\n";
    }
    foreach ($rss_items as $key => $detail_item) {
        $html = $html . "<item>\n";
        $html = $html . "<title>" . $detail_item['title'] . "</title>\n";
        $html = $html . "<link>" . $detail_item['permalink'] . "</link>\n";
        $html = $html . "<description><![CDATA[" . $detail_item['description'] . "]]></description>\n";
        $html = $html . "<pubDate>" . date('r', getDateMillis($detail_item['date'])) . "</pubDate>\n";
        $html = $html . "<pubDateFormat>" . (new \DateTime($detail_item['date']))->format($date_pattern) . "</pubDateFormat>\n";
        $html = $html . "<guid isPermaLink=\"true\">" . $detail_item['permalink'] . "</guid>\n";
        if (!empty($detail_item['author'])) {
            $html = $html . "<dc:creator>" . $detail_item['author'] . "</dc:creator>\n";
        } else {
            $html = $html . "<dc:creator>" . $rss_data['rss_publisher'] . "</dc:creator>\n";
        }
        if ($detail_item['preview_image']) {
            $html = $html . "<content:encoded><![CDATA[<img src=\"" . $detail_item['preview_image'] . "\"/>]]></content:encoded>\n";
        }
        $html = $html . "</item>\n";
    }
    $html = $html . "</channel>\n";
    $html = $html . "</rss>";
    return $html;
}

function getBlogContent(
    $blog_id = 1,
    $items_per_page = 10,
    $layout = '1 Column',
    $date_pattern = 'd.m.Y H:i',
    $hide_future_items = 0,
    $author_text = 'Authors: ',
    $category_text = 'Categories: ',
    $tag_text = 'Tags: ',
    $separator_text = ', ',
    $readmore_text = 'Read More',
    $back_text = 'Back',
    $prev_post_text = 'Previous Post',
    $next_post_text = 'Next Post',
    $prev_page_text = 'Previous Page',
    $next_page_text = 'Next Page',
    $replace_metadata = 1,
    $htaccess_rewrite = 0,
    $rss_publisher = '',
    $rss_title = '',
    $rss_description = '',
    $rss_language = 'en-us',
    $rss_image = '',
    $heading_list = 'h2',
    $heading_detail = 'h1',
    $scroll_detail_top = true,
    $disqus_shorthand = '',
    $metadata_display = 'Below Title',
    $share_display = 'Above Body') {
    global $display_detail, $display_filter, $filter, $post, $page, $search, $sort_by, $feed, $query, $last_page;
    parseBlogURL();

    if (isset($feed) && (($feed == "rss") || ($feed == "sitemap"))) {
        $is_logged_in = false;
    } else {
        $is_logged_in = isLoggedIn();
    }
    if ($is_logged_in) {
        $hide_future_items = 0;
    }

    $page_buffer = ob_get_clean();
    $page_buffer = replaceMetadataRssLink($page_buffer, $rss_title);
    if ($display_filter && !$display_detail) {
        $page_buffer = replaceMetadataTitle($page_buffer, $filter);
    }
    if ($htaccess_rewrite) {
        $page_buffer = replaceMetadataDocumentBase($page_buffer);
    }
    ob_start();
    echo $page_buffer;
    $rss_data = [
        'rss_title' => htmlspecialchars($rss_title, ENT_QUOTES),
        'rss_description' => htmlspecialchars($rss_description, ENT_QUOTES),
        'rss_language' => $rss_language,
        'rss_copyright' => htmlspecialchars($rss_publisher, ENT_QUOTES),
        'rss_url' => 'http' . (isset($_SERVER['HTTPS']) ? 's' : '') . '://' . $_SERVER['HTTP_HOST'] . dirname($_SERVER['PHP_SELF']),
        'rss_image' => $rss_image,
        'rss_publisher' => htmlspecialchars($rss_publisher, ENT_QUOTES),
    ];
    $prev_slug = "";
    $next_slug = "";
    $prev_title = "";
    $next_title = "";
    $store_prev_slug = true;
    $store_next_slug = false;
    $css_wrapper = '';
    $css_item = '';
    switch ($layout) {
        case '1 Column':
            break;
        case 'Bootstrap 2 Columns':
            $css_wrapper = 'row';
            $css_item = 'col-12 col-md-6';
            break;
        case 'Bootstrap 3 Columns':
            $css_wrapper = 'row';
            $css_item = 'col-12 col-md-6 col-lg-4';
            break;
        case 'Bootstrap 4 Columns':
            $css_wrapper = 'row';
            $css_item = 'col-12 col-md-6 col-lg-4 col-xl-3';
            break;
        case 'Bootstrap 6 Columns':
            $css_wrapper = 'row';
            $css_item = 'col-12 col-md-6 col-lg-3 col-xl-2';
            break;
        case 'Masonry 2 Columns':
            $css_wrapper = 'volt-blog-layout-masonry-2';
            break;
        case 'Masonry 3 Columns':
            $css_wrapper = 'volt-blog-layout-masonry-3';
            break;
        case 'Masonry 4 Columns':
            $css_wrapper = 'volt-blog-layout-masonry-4';
            break;
        default:
            break;
    }
    $items_start = ($page - 1) * $items_per_page + 1;
    $items_end = ($page) * $items_per_page;
    $item = 0;
    $current_item = 0;
    $rss_items = [];
    $db = new \PragmaPHP\FileDB\FileDB(BASE_PATH_BLOG . $blog_id);
    $files = $db->readAll();

    if (empty($sort_by)) {
        $sort_by = "date_desc";
    }
    switch ($sort_by) {
        case 'date_asc':
            usort($files, function ($postFile1, $postFile2) {
                return ($postFile1['date'] <=> $postFile2['date']);
            });
            break;
        case 'date_desc':
            usort($files, function ($postFile1, $postFile2) {
                return ($postFile2['date'] <=> $postFile1['date']);
            });
            break;
        case 'title_asc':
            usort($files, function ($postFile1, $postFile2) {
                return ($postFile1['title'] <=> $postFile2['title']);
            });
            break;
        case 'title_desc':
            usort($files, function ($postFile1, $postFile2) {
                return ($postFile2['title'] <=> $postFile1['title']);
            });
            break;
        default:
            usort($files, function ($postFile1, $postFile2) {
                return ($postFile2['date'] <=> $postFile1['date']);
            });
            break;
    }

    $html = '';
    foreach ($files as $postFile) {
        if ($is_logged_in || !isset($postFile['draft']) || (isset($postFile['draft']) && $postFile['draft'] === 'false')) {
            $item_id = $postFile['_id'];
            $postFile['title'] = htmlspecialchars($postFile['title'], ENT_QUOTES);
            $title = $postFile['title'];
            if (isset($postFile['description'])) {
                $postFile['description'] = htmlspecialchars($postFile['description'], ENT_QUOTES);
                $description = $postFile['description'];
            }
            if (empty($description)) {
                $description = $title;
            }
            $category_raw = [];
            if (isset($postFile['categories'])) {
                $category_raw = array_map('trim', explode(',', $postFile['categories']));
            }
            $author_raw = [];
            if (isset($postFile['authors'])) {
                $author_raw = array_map('trim', explode(',', $postFile['authors']));
                if (!empty($author_raw)) {
                    $postFile['author'] = htmlspecialchars($author_raw[0]);
                } else {
                    $postFile['author'] = $rss_data['rss_publisher'];
                }
            }
            $date_raw = $postFile['date'];
            $author_array = buildAuthorArray($author_raw);
            $category_array = buildCategoryArray($category_raw);
            $date_array = buildDateArray($date_raw);
            $text_array = [$title, $description];
            if ($search != "") {
                if (isset($postFile['summary'])) {
                    $text_array[] = strip_tags($postFile['summary']);
                }
                if (isset($postFile['main'])) {
                    $text_array[] = strip_tags($postFile['main']);
                }
            }
            if (isPostInQuery($category_array, $date_raw, $author_array, $text_array, $hide_future_items)) {
                $last_page = floor($item / $items_per_page) + 1;
                $item++;
                if ($store_prev_slug && isset($slug)) {
                    $prev_slug = $slug;
                }
                if (isset($postFile['slug'])) {
                    $slug = htmlspecialchars($postFile['slug'], ENT_QUOTES);
                } else {
                    $slug = '';
                }
                if (empty($slug)) {
                    $slug = slugify($title);
                } else {
                    $slug = slugify($slug);
                }
                $postFile['slug'] = $slug;
                if ($store_next_slug) {
                    $next_slug = $slug;
                    $store_next_slug = false;
                }
                $link = getPostLink($slug, $htaccess_rewrite);
                $postFile['permalink'] = getPostPermalink($slug, $htaccess_rewrite);
                $permalink = $postFile['permalink'];
                if (isset($postFile['header'])) {
                    $header = $postFile['header'];
                } else {
                    $header = '';
                }
                $postFile['preview_image'] = getFullyQualifiedImageURL(getImageSrc($header));

                if (!empty($feed)) {
                    $rss_items[] = $postFile;
                    continue;
                }

                // display post content
                if ((!$display_detail && $items_start <= $item && $item <= $items_end) || ($display_detail && $post == $slug)) {
                    if ($display_detail) {
                        $store_prev_slug = false;
                        $store_next_slug = true;
                        $current_item = $item;
                    }
                    $date_format = (new \DateTime($date_raw))->format($date_pattern);
                    $author_links = buildFilterLinks($author_array, $author_raw, $author_text, $separator_text, $htaccess_rewrite, 'author');
                    $category_links = buildFilterLinks($category_array, $category_raw, $category_text, $separator_text, $htaccess_rewrite, 'category');
                    if (!empty($postFile['_readonly'])) {
                        $readonly = '1';
                    } else {
                        $readonly = '';
                    }
                    if (isset($postFile['summary'])) {
                        $summary = $postFile['summary'];
                    } else {
                        $summary = '';
                    }
                    if (isset($postFile['main'])) {
                        $main = $postFile['main'];
                    } else {
                        $main = '';
                    }
                    if (isset($postFile['gallery'])) {
                        $gallery = $postFile['gallery'];
                    } else {
                        $gallery = '';
                    }
                    if (isset($postFile['gallery-layout']) && !$is_logged_in) {
                        $galleryLayout = $postFile['gallery-layout'];
                    } else {
                        $galleryLayout = 'flexbox';
                    }
                    if ((isset($postFile['gallery-lightbox-captions']) && $postFile['gallery-lightbox-captions'] === 'true')) {
                        $galleryLightboxCaptions = 1;
                    } else {
                        $galleryLightboxCaptions = 0;
                    }
                    if (isset($postFile['gallery-carousel-interval'])) {
                        $galleryCarouselInterval = $postFile['gallery-carousel-interval'];
                    } else {
                        $galleryCarouselInterval = 5;
                    }
                    if ($display_detail) {
                        $backLink = '';
                        if ($last_page == 1) {
                            $backLink = './' . fixQuery($query);
                        } else {
                            $backLink = './?page=' . $last_page . $query;
                        }
                        $detail_item = $postFile;
                        // display detail
$html = $html . <<<EOT
<article class="volt-blog-item volt-blog-item-detail" data-item-id="$item_id" data-item-permalink="$permalink" data-item-title="$title" data-item-readonly="$readonly">
<section>
<div class="volt-blog-item-header volt-blog-item-header-detail">$header</div>
</section>
<section>
EOT;
                        if ($metadata_display == 'Above Title') {
$html = $html . <<<EOT
<div class="volt-blog-item-metadata volt-blog-item-metadata-detail">
<p>
<time datetime="$date_raw"><span class="volt-blog-item-date volt-blog-item-date-detail">$date_format</span></time>
<span class="volt-blog-item-category volt-blog-item-category-detail">$category_links</span>
<span class="volt-blog-item-author volt-blog-item-author-detail">$author_links</span>
</p>
</div>
EOT;
                        }
$html = $html . <<<EOT
<$heading_detail class="volt-blog-item-title volt-blog-item-title-detail mt-2">$title</$heading_detail>
EOT;
                        if ($metadata_display == 'Below Title') {
$html = $html . <<<EOT
<div class="volt-blog-item-metadata volt-blog-item-metadata-detail">
<p>
<time datetime="$date_raw"><span class="volt-blog-item-date volt-blog-item-date-detail">$date_format</span></time>
<span class="volt-blog-item-category volt-blog-item-category-detail">$category_links</span>
<span class="volt-blog-item-author volt-blog-item-author-detail">$author_links</span>
</p>
</div>
EOT;
                        }
                        if ($share_display == 'Above Body') {
$html = $html . <<<EOT
<div class="volt-blog-item-share volt-blog-item-share-detail"></div>
EOT;
                        }
$html = $html . <<<EOT
<div class="volt-blog-item-body volt-blog-item-body-detail">$main
EOT;
                        if (!empty($gallery)) {
$html = $html . <<<EOT
<div class="volt-gallery" data-gallery-layout="$galleryLayout" data-gallery-lightbox-captions="$galleryLightboxCaptions" data-gallery-carousel-interval="$galleryCarouselInterval">$gallery</div>
EOT;
                        }
$html = $html . <<<EOT
</div>
EOT;
                        if ($share_display == 'Below Body') {
$html = $html . <<<EOT
<div class="volt-blog-item-share volt-blog-item-share-detail"></div>
EOT;
                        }
$html = $html . <<<EOT
<div class="volt-blog-item-back mt-3"><a class="btn volt-blog-btn volt-blog-btn-back" href="$backLink">$back_text</a></div>
</section>
</article>
EOT;
                    } else {
                        // display list
$html = $html . <<<EOT
<div class="volt-blog-item-list-wrapper mb-5 $css_item">
<article class="volt-blog-item volt-blog-item-list" data-item-id="$item_id" data-item-permalink="$permalink" data-item-title="$title" data-item-readonly="$readonly">
<section>
<div class="volt-blog-item-header volt-blog-item-header-list"><a href="$link" class="volt-blog-item-header-link">$header</a></div>
</section>
<section>
EOT;
                        if ($metadata_display == 'Above Title') {
$html = $html . <<<EOT
<div class="volt-blog-item-metadata volt-blog-item-metadata-list">
<p>
<time datetime="$date_raw"><span class="volt-blog-item-date volt-blog-item-date-list">$date_format</span></time>
<span class="volt-blog-item-category volt-blog-item-category-list">$category_links</span>
<span class="volt-blog-item-author volt-blog-item-author-list">$author_links</span>
</p>
</div>
EOT;
                        }
$html = $html . <<<EOT
<$heading_list class="volt-blog-item-title volt-blog-item-title-list mt-2"><a href="$link" class="volt-blog-item-title-link">$title</a></$heading_list>
EOT;
                        if ($metadata_display == 'Below Title') {
$html = $html . <<<EOT
<div class="volt-blog-item-metadata volt-blog-item-metadata-list">
<p>
<time datetime="$date_raw"><span class="volt-blog-item-date volt-blog-item-date-list">$date_format</span></time>
<span class="volt-blog-item-category volt-blog-item-category-list">$category_links</span>
<span class="volt-blog-item-author volt-blog-item-author-list">$author_links</span>
</p>
</div>
EOT;
                        }
$html = $html . <<<EOT
<div class="volt-blog-item-body volt-blog-item-body-list">$summary</div>
<div class="volt-blog-item-readmore mt-3"><a class="btn volt-blog-btn volt-blog-btn-readmore" href="$link">$readmore_text</a></div>
</section>
</article>
</div>
EOT;
                    }
                }
            }
        }
    }
    if (isset($feed) && ($feed == "rss")) {
        ob_get_clean();
        ob_start();
        echo generateRssFeed($rss_items, $rss_data, $date_pattern);
        exit();
    }
    if (isset($feed) && ($feed == "sitemap")) {
        ob_get_clean();
        ob_start();
        echo generateSitemap($rss_items);
        exit();
    }
    if ($display_detail) {
        // display detail
        if (isset($detail_item)) {
            if ($replace_metadata) {
                $page_buffer = replaceMetadata(ob_get_clean(), $detail_item, $rss_data);
                ob_start();
                echo $page_buffer;
            }
            $html = $html . '<div class="volt-blog-nav-post text-center mt-5 row">';
            $html = $html . '<div class="volt-blog-nav-post-prev text-right col">';
            if (!empty($prev_slug)) {
                $html = $html . '<a class="btn volt-blog-btn volt-blog-btn-post-prev" href="' . getPostLink($prev_slug, $htaccess_rewrite) . '">' . $prev_post_text . '</a>';
            }
            $html = $html . '</div>';
            $html = $html . '<div class="volt-blog-nav-post-counter btn d-none d-md-block disabled text-center col">' . $current_item . ' / ' . $item . '</div>';
            $html = $html . '<div class="volt-blog-nav-post-next text-left col">';
            if (!empty($next_slug)) {
                $html = $html . '<a class="btn volt-blog-btn volt-blog-btn-post-next" href="' . getPostLink($next_slug, $htaccess_rewrite) . '">' . $next_post_text . '</a>';
            }
            $html = $html . '</div>';
            $html = $html . '</div>';
            $html = $html . '<style>.volt-blog-hide-in-detail {display: none !important;}</style>';
            if (!$is_logged_in && $scroll_detail_top) {
                $html = $html . "<script>document.addEventListener('DOMContentLoaded',function(event){var target = $('.volt-blog-item-detail').offset().top;if ($('.sticky-nav').length) {target -= $('.sticky-nav').outerHeight();}$('html,body').animate({scrollTop: target}, 0);});</script>";
            }
            if (!$is_logged_in && !empty($disqus_shorthand)) {
                $html = $html . '<div id="disqus_thread"></div><script>var disqus_config = function () {this.page.url = "' . $detail_item['permalink'] . '";this.page.identifier = "' . $detail_item['slug'] . '";this.page.title = "' . $detail_item['title'] . '";};(function() {var d = document, s = d.createElement("script");s.src = "//' . $disqus_shorthand . '.disqus.com/embed.js";s.setAttribute("data-timestamp", +new Date());(d.head || d.body).appendChild(s);})();</script>';
            }
        } else {
            http_response_code(404);
        }
    } else {
        // display list
        $html = '<div class="volt-blog-list ' . $css_wrapper . '">' . $html . '</div>';
        if ($last_page > 1) {
            $html = $html . '<div class="volt-blog-nav-page text-center mt-5 row">';
            $html = $html . '<div class="volt-blog-nav-page-prev text-right col">';
            if (1 < $page) {
                if ($page == 2) {
                    $html = $html . '<a class="btn volt-blog-btn volt-blog-btn-page-prev" href="./' . fixQuery($query) . '">' . $prev_page_text . '</a>';
                } else {
                    $html = $html . '<a class="btn volt-blog-btn volt-blog-btn-page-prev" href="./?page=' . ($page - 1) . $query . '">' . $prev_page_text . '</a>';
                }
            }
            $html = $html . '</div>';
            $html = $html . '<div class="volt-blog-nav-page-counter btn d-none d-md-block disabled text-center col">' . $page . ' / ' . $last_page . '</div>';
            $html = $html . '<div class="volt-blog-nav-page-next text-left col">';
            if ($page < $last_page) {
                $html = $html . '<a class="btn volt-blog-btn volt-blog-btn-page-next" href="./?page=' . ($page + 1) . $query . '">' . $next_page_text . '</a>';
            }
            $html = $html . '</div>';
            $html = $html . '</div>';
            $html = $html . '<style>.volt-blog-hide-in-list {display: none !important;}</style>';
        }
    }
    return $html;
}

//////////////////////////////////////////////////
// libraries
//////////////////////////////////////////////////
namespace PragmaPHP\Uid;

/**
* PHP Unique ID generator based on ULID (Universally Unique Lexicographically Sortable Identifier)
*/
class Uid {

    /**
    * @param    int     Time in milliseconds (optional). E.g. round(microtime(true) * 1000)
    * @return   string  Unique ID
    */
    public static function generate($millis = 0): string {
        if ($millis === 0) {
            $millis = round(microtime(true) * 1000);
        }
        $uid = self::encode($millis);
        if (strlen($uid) < 10) {
            $uid = '0' . $uid;
        }
        $uid = $uid . self::random_str() . self::random_str() . self::random_str() . self::random_str();
        if (strlen($uid) != 26) {
            throw new \Exception('Error in Uid generation.');
        }
        return $uid;
    }

    /**
    * @return   string  Random 4 characters
    */
    public static function random_str() {
        // For cryptographically secure value, use random_int instead of rand
        return self::encode(rand(100000, 999999));
    }

    /**
    * @return   string  Base 32 encoded string
    */
    public static function encode($str) {
        $str = strtoupper(base_convert($str, 10, 32));
        return strtr(
            $str,
            "ABCDEFGHIJKLMNOPQRSTUV",
            "ABCDEFGHJKMNPQRSTVWXYZ");
    }

    public static function decode($str) {
        $str = strtoupper($str);
        return base_convert(
            strtr($str, 
                "ABCDEFGHJKMNPQRSTVWXYZILO",
                "ABCDEFGHIJKLMNOPQRSTUV110"),
            32, 10);
    }

}

namespace PragmaPHP\FileDB;

use \PragmaPHP\Uid\Uid;

/**
 * Flat file DB based on JSON files
 */
class FileDB
{

    const ATTRIBUTE_ID = '_id';
    const ATTRIBUTE_CREATED = '_created';
    const ATTRIBUTE_MODIFIED = '_modified';
    const ATTRIBUTE_READONLY = '_readonly';
    const FILE_EXT_JSON = '.json';

    private $directory;

    /**
     * @param    string  Directory
     */
    public function __construct(string $directory)
    {
        // check if directory is existing
        if (!is_dir($directory)) {
            if (!mkdir($directory, 0777, true)) {
                throw new \Exception("Directory " . $directory . " cannot be created");
            }
        } else if (!is_writable($directory)) {
            throw new \Exception("Directory " . $directory . " is not writeable");
        }
        $this->directory = $directory;
    }

    /**
     * @param    array   Data
     */
    public function create(array $data): string
    {
        $time = microtime(true);
        $created = date(DATE_ATOM, round($time));
        $id = Uid::generate(round($time * 1000));
        $data = self::removePrivateFields($data);
        $data[self::ATTRIBUTE_ID] = $id;
        $data[self::ATTRIBUTE_CREATED] = $created;
        $this->writeFile($id, $data);
        return $id;
    }

    /**
     * @param    string  Unique ID
     * @param    array   Data
     */
    public function read(?string $id = null, ?array $search_data = null): array
    {
        $result = [];
        if (!empty($id)) {
            $files = [$this->directory . DIRECTORY_SEPARATOR . $id . self::FILE_EXT_JSON];
            return $this->readFiles($files);
        } else if (!empty($search_data)) {
            // TODO performance, multi search array, wildcard search
            $files = $this->readAll();
            foreach ($files as $file) {
                foreach ($search_data as $search_key => $search_value) {
                    $search_value = trim($search_value);
                    $search_key = trim($search_key);
                    if (array_key_exists($search_key, $file)) {
                        foreach ($file as $key => $value) {
                            if ($key == $search_key) {
                                if (self::startsWith($search_value, '*') && self::endsWith($search_value, '*') && strlen($search_value) > 3) {
                                    if (stripos($value, substr($search_value, 1, -1)) !== false) {
                                        $result[] = $file;
                                    }
                                } else {
                                    if (strcasecmp($value, $search_value) === 0) {
                                        $result[] = $file;
                                    }
                                }
                            }
                        }
                    }
                }
            }
        }
        return $result;
    }

    /**
     * @param    string  Unique ID
     * @param    array   Data
     */
    public function readAll(): array
    {
        $files = glob($this->directory . DIRECTORY_SEPARATOR . '*' . self::FILE_EXT_JSON);
        return $this->readFiles($files);
    }

    /**
     * @param    string  Unique ID
     * @param    array   Data
     */
    public function update(string $id, array $data): string
    {
        $file = $this->directory . DIRECTORY_SEPARATOR . $id . self::FILE_EXT_JSON;
        if (is_file($file)) {
            $file_data = $this->readFile($file);
            if (!self::isReadonly($file_data)) {
                $data = self::removePrivateFields($data);
                $data = array_merge($file_data, $data);
                $data[self::ATTRIBUTE_MODIFIED] = date(DATE_ATOM, round(microtime(true)));
                $this->writeFile($id, $data);
            }
        }
        return $id;
    }

    /**
     * @param    string  Unique ID
     */
    public function delete(string $id)
    {
        $files = [$this->directory . DIRECTORY_SEPARATOR . $id . self::FILE_EXT_JSON];
        $this->deleteFiles($files);
    }

    /**
     * @param    string  Unique ID
     */
    public function deleteAll()
    {
        $files = glob($this->directory . DIRECTORY_SEPARATOR . '*' . self::FILE_EXT_JSON);
        $this->deleteFiles($files);
    }

    /**
     * @param    string  Unique ID
     * @param    array   Data
     */
    public function setReadonly(string $id, bool $readonly): string
    {
        $file = $this->directory . DIRECTORY_SEPARATOR . $id . self::FILE_EXT_JSON;
        if (is_file($file)) {
            $data = $this->readFile($file);
            $data[self::ATTRIBUTE_READONLY] = $readonly;
            $data[self::ATTRIBUTE_MODIFIED] = date(DATE_ATOM, round(microtime(true)));
            $this->writeFile($id, $data);
        }
        return $id;
    }

    private function writeFile(string $id, array $data)
    {
        $file = $this->directory . DIRECTORY_SEPARATOR . $id . self::FILE_EXT_JSON;
        ksort($data);
        // todo trim array
        array_walk_recursive($data, function (&$v) {$v = trim($v);});
        file_put_contents($file, json_encode($data, JSON_PRETTY_PRINT));
    }

    private function readFiles(array $files): array
    {
        $result = [];
        foreach ($files as $file) {
            if (is_file($file)) {
                $result[] = $this->readFile($file);
            }
        }
        return $result;
    }

    private function readFile($file): array
    {
        return json_decode(file_get_contents($file), true);
    }

    private function deleteFiles(array $files)
    {
        foreach ($files as $file) {
            if (is_file($file)) {
                $original_data = $this->readFile($file);
                if (!self::isReadonly($original_data)) {
                    unlink($file);
                }
            }
        }
    }

    private static function startsWith($haystack, $needle)
    {
        return substr_compare($haystack, $needle, 0, strlen($needle)) === 0;
    }

    private static function endsWith($haystack, $needle)
    {
        return substr_compare($haystack, $needle, -strlen($needle)) === 0;
    }

    private static function isReadonly($data)
    {
        if (array_key_exists('_readonly', $data) && $data['_readonly']) {
            return true;
        }
        return false;
    }

    private static function removePrivateFields(array $data): array
    {
        foreach ($data as $key => $value) {
            if (strpos($key, '_') === 0) {
                unset($data[$key]);
            }
        }
        return $data;
    }

}