MAX_STORY_BYTES; } function display_stories($id = NULL) { global $db, $is_admin, $editable, $action; if (is_null($id)) $id = get_id(); $search = get_search_args(); $page = ''; $nolink = NULL; $posted = FALSE; if ($action == 'preview') { global $file_src; $date = 'Awaiting posting'; $result = get_post_data(TRUE); $nolink = TRUE; $posted = TRUE; } else if ($action == 'submit') { $date = 'Posted '.gmdate("Y-m-d H:i"); if (!$is_admin) { $result = get_post_data(TRUE); $nolink = TRUE; $posted = TRUE; } else { $result = get_query($id); } } else { $result = get_query($id); } if (count($result) == 0) { $page .= ''."\n"; if ($search) { $page .= '
'."\n"; $page .= '

No articles found.

Try searching again, or return to the main page. The search is case-insensitive, and searches the title, author, date, and the text of each article.

'."\n"; $page .= '
'."\n"; } else { $page .= '
'."\n"; $page .= '

No articles found.

'; $page .= '
'."\n"; } $page .= ''."\n"; return $page; } $page .= ''."\n"; if (count($result) > 1) $page .= get_pager(); return $page; } /* * Return number of entries matching our request */ function get_count() { global $db, $is_admin; $search = get_search_args(); $queue = get_queue(); $table = which_table(); $params = array(); // if search= is defined, search.... if ($search) { $count_query = "SELECT COUNT(*) FROM $table WHERE". " (title LIKE ? OR author LIKE ? OR date LIKE ? OR story LIKE ?)"; $params = array_fill(0, 4, "%$search%"); // Otherwise, fall back to default view } else { $count_query = "SELECT COUNT(*) FROM ".$table." WHERE 1"; } if (!$is_admin) $count_query .= " AND hidden IS NOT TRUE"; //echo $count_query."
"; $row_count = NULL; if ($count_query) { if ($result = $db->execute_query($count_query, $params)) { $row_count = $result->fetch_row(); $result->close(); } else { db_fail(); } } return $row_count[0]; } /* * Return array of results, correctly keyed to column names. */ function get_query($id = NULL) { global $db, $is_admin; $search = get_search_args(); $sort = get_sort_arg(); $order = get_order(); $start = get_start_num(); $show = get_show_num(); $queue = get_queue(); $table = which_table(); $results = array(); $params = array(); $limit = " LIMIT ".(int)$start." , ".(int)$show; // If id= is defined, show the corresponding application only if (is_numeric($id)) { if (!$is_admin) $query = "SELECT * FROM $table WHERE 1 AND hidden IS NOT TRUE AND id = ".(int)$id." LIMIT 0, 1"; else $query = "SELECT * FROM $table WHERE 1 AND id = ".(int)$id." LIMIT 0, 1"; // if search= is defined, search.... } else if ($search) { $query = "SELECT * FROM $table WHERE". " (title LIKE ? OR author LIKE ? OR date LIKE ? OR story LIKE ?)"; $params = array_fill(0, 4, "%$search%"); if (!$is_admin) $query .= " AND hidden IS NOT TRUE"; $query .= " ORDER BY ".$sort." ".$order.$limit; // Otherwise, fall back to default view } else { $query = "SELECT * FROM ".$table." WHERE 1"; if (!$is_admin) $query .= " AND hidden IS NOT TRUE"; $query .= " ORDER BY ".$sort." ".$order.$limit; } //echo $query; if ($result = $db->execute_query($query, $params)) { while ($row = $result->fetch_assoc()) { $row['queue_url'] = $queue ? 1 : 0; $results[] = $row; } $result->close(); } else { db_fail(); } return $results; } /* * Return array of the POST variables we may care about * (safer than extract($_POST)) * * If $stripslashes is non-null, turn every value into a string: * NULL (or an array) becomes string(""), int(0) becomes string("0"). * (It used to run stripslashes, but magic_quotes_gpc no longer exists.) * * If $html is non-null, run the output through htmlspecialchars, * to translate HTML-unsafe characters. This should only be needed * if you're outputting back to an HTML form. */ function get_post_data($stripslashes = NULL, $html = NULL) { $queue = get_queue(); $results = array(); $entry['id'] = isset($_POST['id']) ? $_POST['id'] : NULL; $entry['title'] = isset($_POST['title']) ? $_POST['title'] : NULL; $entry['author'] = isset($_POST['author']) ? $_POST['author'] : NULL; $entry['email'] = isset($_POST['email']) ? $_POST['email'] : NULL; $entry['upload'] = isset($_POST['upload']) ? $_POST['upload'] : NULL; $entry['story'] = isset($_POST['story']) ? $_POST['story'] : NULL; $entry['comments'] = isset($_POST['comments']) ? $_POST['comments'] : NULL; $entry['date'] = isset($_POST['date']) ? $_POST['date'] : NULL; $entry['hidden'] = isset($_POST['hidden']) ? $_POST['hidden'] : NULL; $entry['uploaded'] = isset($_POST['uploaded']) ? $_POST['uploaded'] : NULL; $entry['editor'] = isset($_POST['editor']) ? $_POST['editor'] : NULL; $entry['remove_img'] = isset($_POST['remove_img']) ? ($_POST['remove_img'] ? 1 : 0) : NULL; $entry['queue_url'] = $queue ? 1 : 0; if ($stripslashes) { $entry = array_map('map_string', $entry); } if ($html) { $entry = array_map('map_html', $entry); } $results[] = $entry; return $results; } function map_string($value) { return is_scalar($value) ? (string)$value : ''; } function map_html($string) { return htmlspecialchars(map_string($string), ENT_QUOTES, 'UTF-8', FALSE); } function unmap_html($string) { return htmlspecialchars_decode($string, ENT_QUOTES); } /* * Utility functions to sanitise data */ function get_id() { global $db, $id; if (is_numeric($id)) { return $id; } else if (isset($_REQUEST['id']) && is_numeric($_REQUEST['id'])) { $id = $db->real_escape_string($_REQUEST['id']); return $id; } else { return NULL; } } function get_search_args() { global $db; if (isset($_GET['search']) && is_string($_GET['search'])) { return $_GET['search']; } } function get_order() { global $db; $order = NULL; if (isset($_GET['order']) && is_string($_GET['order'])) { $order = $db->real_escape_string($_GET['order']); } // Sort ascending by default if ($order != "ASC" && $order != "DESC" && $order != "asc" && $order != "desc") if (get_sort_arg() == "date") { $order = "desc"; } else $order = "asc"; return $order; } function get_sort_arg() { global $db; $sort = NULL; if (isset($_GET['sort']) && is_string($_GET['sort'])) { $sort = $db->real_escape_string($_GET['sort']); } if ($sort != "author" && $sort != "date" && $sort != "title") $sort = "date"; return $sort; } // The most stories that one page can show define('MAX_SHOW', 50); // Return a whole number from the query string, limited to $min..$max, // or NULL if the value is missing or is not a whole number function get_int_arg($name, $min, $max = PHP_INT_MAX) { if (!isset($_GET[$name]) || !is_string($_GET[$name])) return NULL; $value = filter_var(trim($_GET[$name]), FILTER_VALIDATE_INT); if ($value === FALSE) return NULL; return max($min, min($max, $value)); } function get_show_num() { global $num_stories; $show = get_int_arg('show', 1, MAX_SHOW); if ($show !== NULL) return $show; else if ($num_stories) return (int)$num_stories; else // provide fallback return 1; } function get_start_num() { $start = get_int_arg('start', 0); return $start === NULL ? 0 : $start; } /* * Return the queue */ function get_queue() { global $action, $queue; if ($action == 'save' || $action == 'submit') $queue = TRUE; else if (isset($_REQUEST['queue'])) $queue = ($_REQUEST['queue'] === '1') ? TRUE : FALSE; else if (!is_bool($queue)) $queue = FALSE; return $queue; } /* * Returns the table name */ function which_table($move = FALSE, $action = 'read') { global $is_admin; $queue = get_queue(); // Editing: a visitor can never write to the live table if (!$is_admin && in_array($action, array('post', 'save', 'submit', 'delete'))) { return 'queue'; } else if ($action == 'delete' && $move && $is_admin) { return 'queue'; } else if ($action == 'post' && $is_admin) { return 'xxx'; } else if ($action == 'save' || $action == 'submit') { return 'queue'; // Viewing } else if ($queue && $is_admin) { return 'queue'; } else { return 'xxx'; } } /* * Uploaded images wait in a directory outside the web root until the * story is saved. The session holds the random file name, so the client * never sends a file path. */ function upload_dir() { $dir = rtrim(config('UPLOAD_DIR') ?: sys_get_temp_dir() . '/xxx-uploads', '/'); if (!is_dir($dir)) mkdir($dir, 0700, TRUE); return $dir; } /* * A size in megabytes from the configuration, in bytes. An empty, missing * or bad value, or one that is not above zero, gives $default_mb. */ function config_megabytes($key, $default_mb) { $value = trim((string)config($key, '')); if (!is_numeric($value) || (float)$value <= 0) $value = $default_mb; return (int)((float)$value * 1024 * 1024); } /* * The largest image a story can have, in bytes, from UPLOAD_MAX_MB (1 MB * by default). NULL means no limit, which UPLOAD_MAX_MB=none gives. */ function upload_max_bytes() { if (strtolower(trim((string)config('UPLOAD_MAX_MB', ''))) === 'none') return NULL; return config_megabytes('UPLOAD_MAX_MB', 1); } // The most that the upload directory holds, in bytes, from UPLOAD_DIR_LIMIT_MB (100 MB by default) function upload_dir_limit_bytes() { return config_megabytes('UPLOAD_DIR_LIMIT_MB', 100); } // The same in megabytes for a message, such as "1" or "2.5" function upload_max_mb() { return rtrim(rtrim(number_format(upload_max_bytes() / 1048576, 2, '.', ''), '0'), '.'); } function is_upload_name($name) { return is_string($name) && preg_match('/^[0-9a-f]{32}\z/', $name); } // Upload files older than this are abandoned previews define('UPLOAD_MAX_AGE', 86400); /* * Move a file from $_FILES into the upload directory, and remember it * in the session. Returns the new path, or FALSE. A session keeps one * upload: a new one replaces the last. Abandoned uploads are removed * first, and nothing is kept if the directory would grow past * UPLOAD_DIR_LIMIT_MB (100 MB by default). */ function store_upload($tmp_file) { prune_uploads(); remove_pending_upload(); if (upload_dir_size() + (int)@filesize($tmp_file) > upload_dir_limit_bytes()) return FALSE; $name = bin2hex(random_bytes(16)); $file = upload_dir() . '/' . $name; if (!move_uploaded_file($tmp_file, $file)) return FALSE; $_SESSION['pending_upload'] = $name; return $file; } // The upload files, name => path; nothing else in the directory counts function upload_files() { $files = array(); foreach (glob(upload_dir() . '/*') ?: array() as $file) { if (is_upload_name(basename($file)) && is_file($file)) $files[basename($file)] = $file; } return $files; } function upload_dir_size() { return array_sum(array_map(function ($file) { return (int)@filesize($file); }, upload_files())); } // Remove the uploads that are older than UPLOAD_MAX_AGE function prune_uploads() { foreach (upload_files() as $file) { if (@filemtime($file) < time() - UPLOAD_MAX_AGE) @unlink($file); } } // Remove the file of this session's upload, and forget it function remove_pending_upload() { if ($file = pending_upload()) @unlink($file); unset($_SESSION['pending_upload']); } /* * Return the path of the image this session uploaded earlier, or FALSE. */ function pending_upload() { $name = $_SESSION['pending_upload'] ?? NULL; if (!is_upload_name($name)) return FALSE; $file = upload_dir() . '/' . $name; return is_file($file) ? $file : FALSE; } /* * URL that shows the pending image; the name part stops browsers from * showing an older upload from their cache. */ function pending_upload_url() { return '/display?pending=' . substr($_SESSION['pending_upload'] ?? '', 0, 8); }