// Copyright: Copyright (C) 2002-2013 Pristine Communications
// This file is in UTF-8 萬國碼
// Set the include path
if (!defined("INCPATH_SET")) {
require_once dirname(__FILE__) . "/incpath.inc.php";
}
// Referenced subroutines
require_once "monica/a2html.inc.php";
require_once "monica/actlog.inc.php";
require_once "monica/addget.inc.php";
require_once "monica/addslash.inc.php";
require_once "monica/chkfunc.inc.php";
require_once "monica/commtext.inc.php";
require_once "monica/echoform.inc.php";
require_once "monica/errmsg.inc.php";
require_once "monica/formfunc.inc.php";
require_once "monica/getlang.inc.php";
require_once "monica/gettext.inc.php";
require_once "monica/htmlchar.inc.php";
require_once "monica/listpref.inc.php";
require_once "monica/lninfo.inc.php";
require_once "monica/login.inc.php";
require_once "monica/markabbr.inc.php";
require_once "monica/pagefunc.inc.php";
require_once "monica/pic.inc.php";
require_once "monica/requri.inc.php";
require_once "monica/runcmd.inc.php";
require_once "monica/sql.inc.php";
require_once "monica/sqlconst.inc.php";
require_once "monica/userpref.inc.php";
// _list_cmp_range: Compare the abstract ranges for usort()
function _list_cmp_range($a, $b)
{
if ($a["start"] != $a["end"]) {
return $a["start"] - $b["start"];
} else {
return $a["end"] - $b["end"];
}
}
// _list_cmp_query: Compare the query phrases for usort()
function _list_cmp_query($a, $b)
{
return mb_strlen($b) - mb_strlen($a);
}
// list_type: Return the list name
function list_type($type = null)
{
// Cache the result
static $cache;
// Set the list type
if (!is_null($type)) {
$cache = $type;
}
// Return the cache
if (isset($cache)) {
return $cache;
}
// Obtain the current form
$FORM =& curform();
// Form type specified in arguments
if (array_key_exists("list", $FORM)) {
$cache = $FORM["list"];
// No form source is found
} else {
$cache = null;
}
return $cache;
}
// parse_query: Parse the query string into multiple search phrases
function parse_query($query)
{
// Cache the result
static $cache = array();
// Return the cache
if (array_key_exists($query, $cache)) {
return $cache[$query];
}
$phrases = array();
$query = trim($query);
while ($query != "") {
// Non-quoted word
if (preg_match("/^([^\s\"']+)\s*(.*?)$/", $query, $m)) {
$phrases[] = $m[1];
$query = $m[2];
// Double-quoted string
} elseif (preg_match("/^(?:\"((?:[^\\\\\"]|\\\\.)+)\"?)\s*(.*?)$/", $query, $m)) {
$m[1] = str_replace("$", "\\$", $m[1]);
eval("\$phrases[] = \"$m[1]\";");
$query = $m[2];
// Single-quoted string
} elseif (preg_match("/^(?:'((?:[^\\\\']|\\\\.)+)'?)\s*(.*?)$/", $query, $m)) {
eval("\$phrases[] = '$m[1]';");
$query = $m[2];
// Empty quoted strings
} elseif (preg_match("/^(?:\"\"?|''?)\s*(.*?)$/", $query, $m)) {
$query = $m[1];
}
}
sort($phrases);
// Cache it
$cache[$query] = $phrases;
return $phrases;
}
// urlcheck: Perform URL. checks on the current list
function urlcheck(&$current)
{
// Check the URL. format and gather those to check the availibility.
$tocheck = array();
$stdin = "";
for ($i = 0; $i < count($current); $i++) {
if (is_null($current[$i]["_urlcheck"])) {
$current[$i]["_urlcheck"] = t_notset();
} elseif (!is_url_wellformed($current[$i]["_urlcheck"])) {
$current[$i]["_urlcheck"] = C_("Malformed");
} else {
$tocheck[count($tocheck)] =& $current[$i];
$stdin .= $current[$i]["_urlcheck"] . "\n";
}
}
// Nothing to check further
if (count($tocheck) == 0) {
return;
}
// Preserve the original timeout
$timeout = ini_get("max_execution_time");
ini_set("max_execution_time", 0);
// Run the command and obtain the output
$cmd = array(dirname(__FILE__) . "/urlcheck");
$out = xruncmd($cmd, $stdin);
// Restore the timeout
ini_set("max_execution_time", $timeout);
// Save the result
$result = explode("\n", $out);
for ($i = 0; $i < count($tocheck); $i++) {
$tocheck[$i]["_urlcheck"] = $result[$i]?
C_("OK"): C_("Unreachable");
}
return;
}
/////////////////////////
// New Object-Oriented List Handler
// By imacat 2004-05-30
/////////////////////////
// BaseList: Base list handler class
class BaseList
{
// Parameters to be set by the user
// The default number of rows per page when user preference is unavailable.
// Don't set to null.
// This is not to set the number of rows. Set $_pagesize instead.
protected $_DEFAULT_LIST_SIZE = 10;
protected $_DEFAULT_LIST_COLS = array("sn");
// The default sort order -- for non-VIEW lists only
protected $_DEFAULT_SORTBY = null;
// The list brief size
protected $_DEFAULT_BRIEF_LEN = 15;
// The query abstract span from the found phrase
protected $_QABS_SPAN = 30;
// The maximum query abstract length
protected $_QABS_MAXLEN = 200;
// The picture thumbnail size
protected $_PIC_THUMBNAIL_SIZE = 70;
// Default settings
// Known columns that should not be displayed (has a special purpose)
protected $_COLS_NO_DISPLAY = array("_viewurl", "_sel", "_selurl", "_statord");
// Known columns that should never be searched against
protected $_COLS_NO_SEARCH = array("pic", "att", "pdf", "_urlcheck", "_viewurl", "_sel", "_selurl", "_statord");
// Known columns that should not be sorted with
protected $_COLS_NO_SORT_BY = array("_urlcheck", "_viewurl", "_sel", "_selurl", "_statord");
// Columns that should display its brief instead
protected $_COLS_BRIEF = array();
// The page title for the current list
public $title = null;
// The error status after fetching the list
public $error = null;
// The switch for different lists
public $lists_switch = null;
// The number of total/matching records
public $total = null;
// Whether we have fetched the list
protected $_fetched = false;
// Whether we need mass deletion checkers
protected $_massdel = false;
// Whether we need preceding numbering
protected $_nonumber = false;
// Whether we need selection links
protected $_noselect = false;
// Whether we need sorting links
protected $_nosortby = false;
// Whether the list should be in a reversed order
protected $_reverse = false;
// Whether these are static pages
protected $_static = false;
// The file name pattern of the static pages
protected $_static_filepat = "%04d.html";
protected $_static_lastfile = "last.html";
// Query arguments
protected $_form = null;
protected $_query = null;
protected $_pageno = null;
protected $_sortby = null;
protected $_limit = null;
// Select parameters
protected $_is_called_form = null;
protected $_caller = null;
protected $_cformid = null;
// The language to use
protected $_lang = null;
// If this is a multi-lingual table
protected $_is_ml_table = null;
// Column labels to be used in _html_list() and _html_listprefform()
protected $_col_labels = array();
// This shall be set when initializing the handler
protected $_useview;
protected $_seltext;
protected $_selurl_tmpl;
// The parsing results
protected $_table = null;
protected $_view = null;
protected $_select = null;
protected $_select_total = null;
protected $_current = null;
protected $_cols = null;
protected $_lastpage = null;
// The number of rows per page. Set to false to disable paging.
protected $_pagesize = null;
protected $_startno = null;
protected $_endno = null;
// Intermediate parsing results when fetching a list
protected $_coldefs = array();
protected $_sortkeys = array();
protected $_query_phrases = array();
protected $_listcols = array();
// __construct: Initialize the handler
// We only set up the environment, but do not really initialize
// the object here. This way we can configure the environment
// in the beginning before we pass its arguments in the middle
// of the script
function __construct($FORM, $table)
{
// The default value
if (is_null($FORM)) {
$FORM = curform();
}
// Set the environment
if ($GLOBALS["SQL_DBTYPE"] != SQL_NONE) {
$this->_useview = sql_support(SQL_FEATURE_VIEW);
}
// Set the parameters
$this->_form =& $FORM;
$this->_table = $table;
$this->_pageno = array_key_exists("pageno", $this->_form)?
$this->_form["pageno"]: null;
$this->_sortby = array_key_exists("sortby", $this->_form)?
$this->_form["sortby"]: null;
$this->_query = array_key_exists("query", $this->_form)?
$this->_form["query"]: null;
if ($this->_query == C_("(query phrase)")) {
$this->_query = "";
}
$this->_lang = array_key_exists("lang", $this->_form)
&& in_array($this->_form["lang"], $GLOBALS["ALL_LINGUAS"])?
$this->_form["lang"]: getlang();
// The default column labels
$this->_col_labels(array(
// Common labels shared by all list handlers
"sn" => C_("S/N"),
"created" => C_("Created"),
"createdby" => C_("Created by"),
"updated" => C_("Updated"),
"updatedby" => C_("Updated by"),
// Other commonly-seen column labels
"body" => C_("Content"),
"cat" => C_("Category"),
"cov" => C_("Coverage"),
"date" => C_("Date"),
"disabled" => C_("Disabled?"),
"dsc" => C_("Description"),
"email" => C_("E-mail"),
"hid" => C_("Hidden?"),
"html" => C_("HTML?"),
"id" => C_("ID."),
"kw" => C_("Keywords"),
"name" => C_("Name"),
"ord" => C_("Order"),
"path" => C_("Page path"),
"pic" => C_("Picture"),
"picratio" => C_("Pic. ratio"),
"piccap" => C_("Pic. caption"),
"picpos" => C_("Pic. position"),
"subject" => C_("Subject"),
"title" => C_("Title"),
"url" => C_("URL."),
"_urlcheck" => C_("Status (slow)"),
));
// Parameters for the called forms
$this->_is_called_form = array_key_exists("caller", $this->_form)
&& array_key_exists("cformid", $this->_form);
if ($this->_is_called_form) {
$this->_caller = $this->_form["caller"];
$this->_cformid = $this->_form["cformid"];
$this->_seltext = C_("Select");
$this->_selurl_tmpl = $this->_caller
. "?formid=" . $this->_cformid . "&selsn=%d";
$this->_title = C_("Select a Data Record");
} else {
$this->_caller = null;
$this->_cformid = null;
$this->_seltext = C_("Edit");
$this->_selurl_tmpl = REQUEST_FILE . "?form=cur&sn=%d";
$this->_title = C_("Manage Data");
}
// The default of the multi-lingual status
$this->_is_ml_table = null;
}
// fetch: Fetch the current list.
// We dispatch here for different query engines
function fetch()
{
// Fetched before
if ($this->_fetched) {
return $this->error;
}
$this->_fetched = true;
// Initialize the error status
$this->error = null;
switch ($GLOBALS["SQL_DBTYPE"]) {
case SQL_MYSQL:
return $this->_fetch_offset();
case SQL_POSTGRESQL:
return $this->_fetch_once();
}
}
// page_param: Obtain page parameters
function page_param()
{
// Fetch the current list if not fetched yet
if (!$this->_fetched) {
$this->fetch();
}
// Don't show the list
if (is_null($this->total)) {
return null;
}
// No record to be listed
if ($this->total == 0) {
return null;
}
$args = array();
$baseurl = rem_get_arg(REQUEST_FILEQS, "pageno");
// The first page -- only meaningful when there is more than one page
if ($this->_lastpage > 1) {
if ($this->_static) {
$args["first"] = sprintf($this->_static_filepat, 1);
} elseif ($this->_reverse) {
$args["first"] = add_get_arg($baseurl, "pageno", 1, DUP_OK);
} else {
$args["first"] = $baseurl;
}
}
// The previous page
if ($this->_pageno > 1) {
if ($this->_static) {
$args["prev"] = sprintf($this->_static_filepat, $this->_pageno - 1);
} elseif ($this->_reverse || $this->_pageno - 1 != 1) {
$args["prev"] = add_get_arg($baseurl, "pageno", $this->_pageno - 1, DUP_OK);
} else {
$args["prev"] = $baseurl;
}
}
// The next page
if ($this->_pageno < $this->_lastpage) {
if ($this->_static) {
if ( !is_null($this->_static_lastfile)
&& $this->_pageno + 1 == $this->_lastpage) {
$args["next"] = $this->_static_lastfile;
} else {
$args["next"] = sprintf($this->_static_filepat, $this->_pageno + 1);
}
} elseif (!$this->_reverse || $this->_pageno + 1 != $this->_lastpage) {
$args["next"] = add_get_arg($baseurl, "pageno", $this->_pageno + 1, DUP_OK);
} else {
$args["next"] = $baseurl;
}
}
// The last page -- only meaningful when there is more than one page
if ($this->_lastpage > 1) {
if ($this->_static) {
if (!is_null($this->_static_lastfile)) {
$args["last"] = $this->_static_lastfile;
} else {
$args["last"] = sprintf($this->_static_filepat, $this->_lastpage);
}
} elseif (!$this->_reverse) {
$args["last"] = add_get_arg($baseurl, "pageno", $this->_lastpage, DUP_OK);
} else {
$args["last"] = $baseurl;
}
}
return $args;
}
// html: Output the list
function html()
{
// Fetch the current list if not fetched yet
if (!$this->_fetched) {
$this->fetch();
}
// Display the title
$this->_html_title();
// Display the error message
$this->_html_errmsg();
// Display the switch for different lists
$this->_html_lists_switch();
// Display a link to add a new item
$this->_html_newlink();
// Display the search box
$this->_html_search();
// Display the list status message
$this->_html_liststat();
// Display the page bar at the beginning
$this->_html_pagebar();
// List the items
$this->_html_list();
// Display the page bar at the end
$this->_html_pagebar();
// Display a form to change the list preference
$this->_html_listprefform();
return;
}
// set_listpref: Set the list preference
function set_listpref()
{
$handler = new ListPreference($this->_form);
$handler->main();
}
/////////////////////////
// Methods belows are private. Do not call them directly.
// Override them when needed.
/////////////////////////
// _fetch_once: Fetch the current list in once.
// Fetching the list in one query: Query everything, fetch
// the total and only the wanted portion.
// This is faster in PostgreSQL but slower in MySQL.
function _fetch_once()
{
// See if we need to use views or not.
// Views make things much faster and easier, but some DBMS has no views.
if ($this->_useview) {
$r = $this->_select_with_view();
} else {
$r = $this->_select_without_view();
}
$this->_select = sprintf("SELECT %s FROM %s%s%s%s;\n",
$r["cols"], $r["table"], $r["where"], $r["orderby"], $r["limit"]);
$result = sql_query($this->_select);
$this->total = sql_num_rows($result);
// The number of rows per page
if (is_null($this->_pagesize)) {
$this->_pagesize = userpref("listsize", get_class($this));
if (is_null($this->_pagesize)) {
$this->_pagesize = $this->_DEFAULT_LIST_SIZE;
}
}
// Paging not in use
if ($this->_pagesize === false) {
$this->_lastpage = 1;
$this->_startno = 0;
$this->_endno = $this->total - 1;
// If endno is -1 (when total is 0), set to 0
if ($this->_endno < 0) {
$this->_endno = 0;
}
// Fetch everything
for ($i = 0, $this->_current = array(); $i < $this->total; $i++) {
$this->_current[] = sql_fetch_assoc($result);
}
// Set the columns to be displayed
$this->_check_listcols();
// Done
return $this->error;
}
$this->_lastpage = floor(($this->total - 1) / $this->_pagesize) + 1;
// The output type of floor() is float
settype($this->_lastpage, "integer");
// If last page is 0 (when total is 0), set to page 1
if ($this->_lastpage < 1) {
$this->_lastpage = 1;
}
// Check the page number
$error = $this->_check_pageno();
if (!is_null($error) && is_null($this->error)) {
$this->error = $error;
}
// Calculate the start and end record number
$this->_startno = ($this->_pageno - 1) * $this->_pagesize;
$this->_endno = $this->_pageno * $this->_pagesize - 1;
// If there are not enough remaining records, set to the last one
if ($this->_endno > $this->total - 1) {
$this->_endno = $this->total - 1;
}
// If the last record is -1 (when total is 0), set to 0
if ($this->_endno < 0) {
$this->_endno = 0;
}
// Go to that page
$this->_current = array();
// If not empty
if ($this->total > 0) {
// Move to startno
sql_seek($result, $this->_startno);
// Fetch until endno
for ($i = $this->_startno; $i <= $this->_endno; $i++) {
$this->_current[] = sql_fetch_assoc($result);
}
}
// Set the columns to be displayed
$this->_check_listcols();
// Done
return $this->error;
}
// _fetch_offset: Fetch the current list with LIMIT and OFFSET.
// Fetching the list in 2 queries: First query the total with count(*)
// and then query only the wanted portion with LIMIT and OFFSET.
// This is faster in MySQL but slower in PostgreSQL.
function _fetch_offset()
{
// See if we need to use views or not.
// Views make things much faster and easier, but some DBMS has no views.
if ($this->_useview) {
$r = $this->_select_with_view();
} else {
$r = $this->_select_without_view();
}
// The number of rows per page
if (is_null($this->_pagesize)) {
$this->_pagesize = userpref("listsize", get_class($this));
if (is_null($this->_pagesize)) {
$this->_pagesize = $this->_DEFAULT_LIST_SIZE;
}
}
// Paging not in use
if ($this->_pagesize === false) {
$this->_select = sprintf("SELECT %s FROM %s%s%s%s;\n",
$r["cols"], $r["table"], $r["where"], $r["orderby"], $r["limit"]);
$result = sql_query($this->_select);
// Fetch everything
for ($i = 0, $this->_current = array(); $i < $this->total; $i++) {
$this->_current[] = sql_fetch_assoc($result);
}
$this->total = sql_num_rows($result);
$this->_lastpage = 1;
$this->_startno = 0;
$this->_endno = $this->total - 1;
// If endno is -1 (when total is 0), set to 0
if ($this->_endno < 0) {
$this->_endno = 0;
}
// Set the columns to be displayed
$this->_check_listcols();
// Done
return $this->error;
}
// Obtain the total number
$this->_select_total = sprintf("SELECT count(*) AS count FROM %s%s%s;\n",
$r["table"], $r["where"], $r["limit"]);
$result = sql_query($this->_select_total);
$row = sql_fetch_assoc($result);
$this->total = $row["count"];
$this->_lastpage = floor(($this->total - 1) / $this->_pagesize) + 1;
// The output type of floor() is float
settype($this->_lastpage, "integer");
// If last page is 0 (when total is 0), set to page 1
if ($this->_lastpage < 1) {
$this->_lastpage = 1;
}
// Check the page number
$error = $this->_check_pageno();
if (!is_null($error) && is_null($this->error)) {
$this->error = $error;
}
// Calculate the start and end record number
$this->_startno = ($this->_pageno - 1) * $this->_pagesize;
$this->_endno = $this->_pageno * $this->_pagesize - 1;
// If there are not enough remaining records, set to the last one
if ($this->_endno > $this->total - 1) {
$this->_endno = $this->total - 1;
}
// If the last record is -1 (when total is 0), set to 0
if ($this->_endno < 0) {
$this->_endno = 0;
}
// Obtain everything in this page
$this->_current = array();
if (!$this->_reverse) {
$this->_select = sprintf("SELECT %s FROM %s%s%s LIMIT %d OFFSET %d;\n",
$r["cols"], $r["table"], $r["where"], $r["orderby"],
$this->_endno - $this->_startno + 1,
$this->_startno);
} else {
$this->_select = sprintf("SELECT %s FROM %s%s%s LIMIT %d OFFSET %d;\n",
$r["cols"], $r["table"], $r["where"], $r["orderby"],
$this->_endno - $this->_startno + 1,
$this->total - $this->_endno - 1);
}
// If not empty
if ($this->total > 0) {
$result = sql_query($this->_select);
$count = sql_num_rows($result);
for ($i = 0; $i < $count; $i++) {
$this->_current[] = sql_fetch_assoc($result);
}
}
// Set the columns to be displayed
$this->_check_listcols();
// Done
return $this->error;
}
// _sql_cols: Obtain the SQL columns list phase
function _sql_cols()
{
// Obtain the columns to list
for ( $i = 0, $this->_coldefs = array(), $cols = array();
$i < count($this->_cols); $i++) {
$def = $this->_coldef($this->_cols[$i]);
$this->_coldefs[] = array(
"def" => $def,
"alias" => $this->_cols[$i]);
$cols[] = $def . " AS " . $this->_cols[$i];
}
return implode(", ", $cols);
}
// _coldef: Column definition for non-view usage
function _coldef($col)
{
// Obtain the column definition
switch ($col) {
case "createdby":
case "updatedby":
return $col . ".name";
default:
// Multi-lingual columns
if (in_array($col, sql_cols_ml($this->_table))) {
// Default language
if ($this->_lang == DEFAULT_LANG) {
return $this->_table . "." . $col . "_"
. ln($this->_lang, LN_DATABASE);
// Fall back to the default language
} else {
$thiscol = $this->_table . "." . $col . "_"
. ln($this->_lang, LN_DATABASE);
$defcol = $this->_table . "." . $col . "_"
. ln(DEFAULT_LANG, LN_DATABASE);
return "COALESCE(" . $thiscol . ", " . $defcol . ")";
}
// Ordinary columns
} else {
return $this->_table . "." . $col;
}
}
}
// _sql_join: Get the SQL JOIN phase
function _sql_join()
{
$join = "";
$cols = sql_cols_nl($this->_table);
if (in_array("createdby", $cols)) {
$join .= " LEFT JOIN users AS createdby ON "
. $this->_table . ".createdby=createdby.sn";
}
if (in_array("updatedby", $cols)) {
$join .= " LEFT JOIN users AS updatedby ON "
. $this->_table . ".updatedby=updatedby.sn";
}
return $join;
}
// _liststat_message: Return the current list statistics message
function _liststat_message()
{
// No record to list
if ($this->total == 0) {
// Empty comes from a query
if (!is_null($this->_query)) {
return C_("Nothing found. Please try another query.");
// Empty database
} else {
return C_("The database is empty.");
}
// Fit in one page
} elseif ($this->_pagesize === false || $this->total <= $this->_pagesize) {
// Result comes from a query
if (!is_null($this->_query)) {
return sprintf(dngettext(COMMONDOMAIN,
"Your query found %s record.",
"Your query found %s records.",
$this->total),
number_format($this->total));
// List result
} else {
return sprintf(dngettext(COMMONDOMAIN,
"%s record.",
"%s records.",
$this->total),
number_format($this->total));
}
// More than one page
} else {
if (!is_null($this->_query)) {
return sprintf(dngettext(COMMONDOMAIN,
"Your query found %s record, listing %s to %s.",
"Your query found %s records, listing %s to %s.",
$this->total),
number_format($this->total),
number_format($this->_startno+1),
number_format($this->_endno+1));
// List result
} else {
return sprintf(dngettext(COMMONDOMAIN,
"%s record, listing %s to %s.",
"%s records, listing %s to %s.",
$this->total),
number_format($this->total),
number_format($this->_startno+1),
number_format($this->_endno+1));
}
}
}
// _echo_colval: Output a list column value
function _echo_colval($col, &$row)
{
// Null/no value
if (is_null($row[$col])) {
echo h_abbr(t_notset());
// A brief should be displayed instead
} elseif ( in_array($col, $this->_COLS_BRIEF)
&& mb_strlen($row[$col]) > $this->_DEFAULT_BRIEF_LEN) {
// Strip the HTML tags
$text = $row[$col];
if ( array_key_exists("html", $row)
&& $row["html"]) {
$text = strip_tags($text);
}
echo h(mb_substr($text, 0, $this->_DEFAULT_BRIEF_LEN)) . "…";
// Always display "pic" column as a thumbnail preview
} elseif ($col == "pic") {
$alt = C_("Picture preview");
$picid = readpic_content($row[$col],
array("max" => $this->_PIC_THUMBNAIL_SIZE),
$row["sn"], $this->_table);
$PICS =& pic_deposit();
$pic = $PICS[$picid];
echopic_thumbnail($pic, $alt);
// Always display "att" and "pdf" columns as their sizes
} elseif ($col == "att" || $col == "pdf") {
echo h_abbr(report_size(strlen($row[$col])));
// Always display "_urlcheck" column as URL. checking status
} elseif ($col == "_urlcheck") {
echo h($row[$col]);
// Ordinary columns
} else {
echo h($row[$col]);
}
return;
}
/////////////////////////
// Methods belows are private. Do not call them directly.
// Do not override them, either.
/////////////////////////
// _check_pageno: Check the page number
function _check_pageno()
{
// Save it elsewhere and replace with default value temporarily
$pageno = $this->_pageno;
$this->_pageno = !$this->_reverse? 1: $this->_lastpage;
// Page number not specified
if (is_null($pageno)) {
return null;
}
// Text string
if (is_string($pageno)) {
// If it is too long or too short
if (strlen($pageno) > 9) {
return array("msg"=>NC_("Page number (%s) invalid. Please specify a valid page number."),
"margs"=>array($pageno));
}
// If it is all-digits
if (!preg_match("/^\d+$/", $pageno)) {
return array("msg"=>NC_("Page number (%s) invalid. Please specify a valid page number."),
"margs"=>array($pageno));
}
// If it is all-digits
if (preg_match("/^0+$/", $pageno)) {
return array("msg"=>NC_("Page number (%s) invalid. Please specify a valid page number."),
"margs"=>array($pageno));
}
// Convert its type to integer
settype($pageno, "integer");
}
// Not an integer still
if (!is_int($pageno)) {
return array("msg"=>NC_("Page number (%s) invalid. Please specify a valid page number."),
"margs"=>array($pageno));
}
// Out of range
if (!is_null($this->_lastpage) && $pageno > $this->_lastpage) {
return array("msg"=>NC_("Page number (%d) out of range. Please specify a number between 1 and %d."),
"margs"=>array($pageno, $this->_lastpage));
}
// OK
$this->_pageno = $pageno;
return null;
}
// _select_with_view: Obtain the SQL statement with views
// This makes life easier *^_^*
function _select_with_view()
{
// Obtain the view name
if (is_null($this->_view)) {
$this->_view = $this->_table . "_list";
if (count($GLOBALS["ALL_LINGUAS"]) > 1) {
$this->_view .= "_" . ln($this->_lang, LN_DATABASE);
}
}
// Obtain the available columns
if (is_null($this->_cols)) {
$this->_cols = sql_cols($this->_view);
}
// Obtain the SQL WHERE phase
$where = $this->_sql_filter();
// Obtain the SQL ORDER BY phase
$orderby = $this->_sql_orderby();
// Obtain the LIMIT phase
$limit = !is_null($this->_limit)? " LIMIT " . $this->_limit: "";
// Compose the SQL query
return array(
"cols" => "*",
"table" => $this->_view,
"where" => $where,
"orderby" => $orderby,
"limit" => $limit,
);
}
// _select_without_view: Obtain the SQL statement manually without views
// SQL DBMS without views are to be cursed -- old MySQL does
function _select_without_view()
{
// Obtain the available columns
$this->_cols = sql_cols_nl($this->_table);
$this->_cols = array_diff($this->_cols, array("mtime"));
// _sql_cols() set the column definitions and may alter
// $this->_cols to to get the final columns list
$cols = $this->_sql_cols();
// Obtain the SQL JOIN phase
$join = $this->_sql_join();
// Obtain the SQL WHERE phase
$where = $this->_sql_filter();
// Obtain the SQL ORDER BY phase
$orderby = $this->_sql_orderby();
// Obtain the LIMIT phase
$limit = !is_null($this->_limit)? " LIMIT " . $this->_limit: "";
// Compose the SQL query
return array(
"cols" => $cols,
"table" => $this->_table . $join,
"where" => $where,
"orderby" => $orderby,
"limit" => $limit,
);
}
// _sql_filter: Get the SQL WHERE phase
// The returned SQL phrase is always in UTF-8
function _sql_filter()
{
// No query, return empty string
if (is_null($this->_query)) {
if ( method_exists($this, "_pre_filter")
&& !is_null($this->_pre_filter())) {
return " WHERE " . $this->_pre_filter();
}
return "";
}
// Regularize it
$this->_query = trim($this->_query);
// Check if it is filled
if ($this->_query == "") {
$this->error = array("msg"=>NC_("Please fill in your query."));
if ( method_exists($this, "_pre_filter")
&& !is_null($this->_pre_filter())) {
return " WHERE " . $this->_pre_filter();
}
return "";
}
$this->_query_phrases = parse_query($this->_query);
// Bounce if nothing to query
if (count($this->_query_phrases) == 0) {
if ( method_exists($this, "_pre_filter")
&& !is_null($this->_pre_filter())) {
return " WHERE " . $this->_pre_filter();
}
return "";
}
// Obtain the columns to query
if ($this->_useview) {
$cols = array_diff($this->_cols, $this->_COLS_NO_SEARCH);
// Use the column definition kept so far
} else {
for ($i = 0, $cols = array(); $i < count($this->_coldefs); $i++) {
// Columns that should not be searched against
if (in_array($this->_coldefs[$i]["alias"], $this->_COLS_NO_SEARCH)) {
continue;
}
$cols[] = $this->_coldefs[$i]["def"];
}
}
// Compose the query condition
$conds = array();
// Obtain each phase
foreach ($this->_query_phrases as $phrase) {
$subconds = array();
foreach ($cols as $col) {
if ($GLOBALS["SQL_DBTYPE"] == SQL_POSTGRESQL) {
$subconds[] = "cast($col AS text) ILIKE '%" . sql_esclike($phrase) . "%'";
} else {
$subconds[] = "cast($col AS text) LIKE '%" . sql_esclike($phrase) . "%'";
}
}
$conds[] = implode(" OR ", $subconds);
}
// Append the the pre-defined filter
if ( method_exists($this, "_pre_filter")
&& !is_null($this->_pre_filter())) {
$conds[] = $this->_pre_filter();
}
// Compose the statement
if (count($conds) == 1) {
$sql = $conds[0];
} else {
$conds0 = array();
foreach ($conds as $cond) {
$conds0[] = "($cond)";
}
$sql = implode(" AND ", $conds0);
}
// Append WHERE
return " WHERE " . $sql;
}
// _compose_query_key: Compose the query key from the query phrases
function _compose_query_key()
{
// Bounce if there is no query phrases
if (count($this->_query_phrases) == 0) {
return "";
}
for ($i = 0, $phrases = array(); $i < count($this->_query_phrases); $i++) {
$phreses[] = "\"" . addslashes($this->_query_phrases[$i]) . "\"";
}
return implode(",", $phreses);
}
// _sql_orderby: Get the SQL ORDER BY phase
function _sql_orderby()
{
// Parse the "sortby" argument
$this->_parse_sortby($this->_sortby);
// Check the sort keys, and empty them if invalid
$error = $this->_check_sortkeys();
if (!is_null($error)) {
if (is_null($this->error)) {
$this->error = $error;
}
$this->_sortkeys = array();
}
// Apply _DEFAULT_SORTBY if not in a view
if ( count($this->_sortkeys) == 0
&& !$this->_useview
&& !is_null($this->_DEFAULT_SORTBY)) {
// Parse the _DEFAULT_SORTBY argument
$this->_parse_sortby($this->_DEFAULT_SORTBY);
// Check the sort keys, and empty them if invalid
$error = $this->_check_sortkeys();
if (!is_null($error)) {
if (is_null($this->error)) {
$this->error = $error;
}
$this->_sortkeys = array();
}
}
// Set the "sortby" attribute
$this->_compose_sortby();
// Bounce if there is no sorting key
if (count($this->_sortkeys) == 0) {
return "";
}
// Obtain the corresponding SQL phrase
for ($i = 0, $phrases = array(); $i < count($this->_sortkeys); $i++) {
$phrases[] = $this->_sortkeys[$i]["sql"];
}
$sql = " ORDER BY " . implode(", ", $phrases);
return $sql;
}
// _parse_sortby: Parse the "sortby" argument
// $sortby argument should be specified as "key1,-key2,...",
// where initial minus (-) before the key means decreasing.
function _parse_sortby($sortby)
{
$this->_sortkeys = array();
// Bounce for nothing
if (is_null($sortby)) {
return;
}
$sortby = trim($sortby);
// Bounce if $sortby is empty
if ($sortby == "") {
return;
}
// Split by comma
$phrases = explode(",", $sortby);
for ($i = 0; $i < count($phrases); $i++) {
// Compose the sort key
$key = trim($phrases[$i]);
$desc = false;
$sql = $key;
// Check the decreasing flag with the initial "-" sign
if (substr($key, 0, 1) == "-") {
$key = trim(substr($key, 1));
$desc = true;
$sql = "$key DESC";
}
// Add this sort key
$this->_sortkeys[] = array(
"key" => $key,
"desc" => $desc,
"sql" => $sql,
);
}
return;
}
// _check_sortkeys: Check if the sorting keys are valid
function _check_sortkeys()
{
// Skip if nothing to check
if (count($this->_sortkeys) == 0) {
return null;
}
// Obtain the valid sorting keys
if ($this->_useview) {
$validkeys = array_diff($this->_cols, $this->_COLS_NO_SORT_BY);
// Check each candidate
for ($i = 0; $i < count($this->_sortkeys); $i++) {
if (!in_array($this->_sortkeys[$i]["key"], $validkeys)) {
return array("msg"=>NC_("You cannot sort by \"%s\"."),
"margs"=>array($this->_sortkeys[$i]["key"]));
}
}
// Use the column definition kept so far
} else {
// Turn to associative array
for ($i = 0, $coldefs = array(); $i < count($this->_coldefs); $i++) {
$coldefs[$this->_coldefs[$i]["alias"]] = $this->_coldefs[$i]["def"];
}
// Check each candidate
for ($i = 0; $i < count($this->_sortkeys); $i++) {
if (!array_key_exists($this->_sortkeys[$i]["key"], $coldefs)) {
return array("msg"=>NC_("You cannot sort by \"%s\"."),
"margs"=>array($this->_sortkeys[$i]["key"]));
}
// Reset the SQL according to the column definition
$this->_sortkeys[$i]["sql"] = $coldefs[$this->_sortkeys[$i]["key"]];
if ($this->_sortkeys[$i]["desc"]) {
$this->_sortkeys[$i]["sql"] .= " DESC";
}
}
}
// OK
return null;
}
// _compose_sortby: Compose the "sortby" argument from sorting keys
function _compose_sortby()
{
// Bounce if there is no sorting keys
if (count($this->_sortkeys) == 0) {
$this->_sortby = "";
return;
}
for ($i = 0, $sortbys = array(); $i < count($this->_sortkeys); $i++) {
if ($this->_sortkeys[$i]["desc"]) {
$sortbys[] = "-" . $this->_sortkeys[$i]["key"];
} else {
$sortbys[] = $this->_sortkeys[$i]["key"];
}
}
$this->_sortby = implode(",", $sortbys);
return;
}
// _cols: Obtain the list columns
// To be removed. Use $this->_cols instead.
function _cols()
{
// Obtain the available column list when not set yet
if (is_null($this->_cols)) {
$this->_cols = sql_cols_nl($this->_table);
}
return $this->_cols;
}
// _col_labels: Set the column labels
function _col_labels($labels)
{
$this->_col_labels = array_merge($this->_col_labels, $labels);
}
// _check_listcols: Set the columns to be displayed
function _check_listcols()
{
// The columns to be displayed
$userpref = userpref("listcols", get_class($this));
if (!is_null($userpref)) {
$listcols = explode(" ", $userpref);
} else {
$listcols = $this->_DEFAULT_LIST_COLS;
}
// Obtain the columns to list
$validcols = array_diff($this->_cols, $this->_COLS_NO_DISPLAY);
$this->_listcols = array_intersect($listcols, $validcols);
return;
}
// _query_abstract: Get the abstract regarding to the query phrase
// It always work on the "body" column
function _query_abstract($body, $html = false, $queries = null)
{
// Default to the current query phrases
if (is_null($queries)) {
$queries = $this->_query_phrases;
}
// A single query phrase is provided
if (!is_array($queries)) {
$queries = array($queries);
}
// Return nothing if body is empty
if (is_null($body)) {
return null;
}
// Strip the HTML tags
if ($html) {
$body = str_replace("", "\"", $body);
$body = str_replace("
", "\"", $body);
$body = strip_tags($body);
$body = dh($body);
}
// Trim excess spaces
$body = preg_replace("/\s+/", " ", $body);
// Sort the query phrases by their lengths, shortest first
usort($queries, "_list_cmp_query");
// Gather the abstract of each query phrase
$bodylen = mb_strlen($body);
for ($i = 0, $ranges = array(); $i < count($queries); $i++) {
$len = mb_strlen($queries[$i]);
$base = 0;
$query = addslashes_re_php($queries[$i]);
// Gather each match
while (preg_match("/^(.*?)$query/i",
mb_substr($body, $base), $m)) {
$pos = $base + mb_strlen($m[1]);
$start = $pos - $this->_QABS_SPAN;
if ($start < 0) {
$start = 0;
}
$end = $pos + $len + $this->_QABS_SPAN;
if ($end >= $bodylen) {
$end = $bodylen - 1;
}
$ranges[] = array(
"start" => $start,
"end" => $end,
);
$base = $pos + $len;
}
}
// Sanity check
if (count($ranges) == 0) {
return null;
}
// Sort the ranges
usort($ranges, "_list_cmp_range");
// Get the union of the ranges
$union = array();
$i = 0;
$start = $ranges[0]["start"];
$end = $ranges[0]["end"];
while (true) {
// Find the next segment that exceeds the current segment
for ( ; $i < count($ranges) && $ranges[$i]["end"] <= $end; $i++) {};
// Meet the last entry
if ($i == count($ranges)) {
// Save the last segment
$union[] = array(
"start" => $start,
"end" => $end,
"len" => $end - $start,
"text" => mb_substr($body, $start, $end - $start),
);
break;
}
// A new segment seperated from the current segment
if ($ranges[$i]["start"] > $end) {
// Save the last segment
$union[] = array(
"start" => $start,
"end" => $end,
"len" => $end - $start,
"text" => mb_substr($body, $start, $end - $start),
);
// Start a new segment
$start = $ranges[$i]["start"];
$end = $ranges[$i]["end"];
continue;
}
// Expend the current segment
$end = $ranges[$i]["end"];
continue;
}
// Trim the union
$len_andsoon = 1;
$len = 0;
if ($union[0]["start"] != 0) {
$len += $len_andsoon;
}
$reached_maximum = false;
for ($i = 0; $i < count($union) - 1; $i++) {
$needlen = $union[$i]["len"] + $len_andsoon;
// Not even enough for an abstract section
if ($len + $len_andsoon > $this->_QABS_MAXLEN) {
$reached_maximum = true;
// Discard the rest sections
while (count($union) > $i) {
array_pop($union);
}
break;
// Reached the maximum
} elseif ($len + $needlen > $this->_QABS_MAXLEN) {
$reached_maximum = true;
$union[$i]["len"] = $this->_QABS_MAXLEN - $len - $len_andsoon;
$union[$i]["end"] = $union[$i]["start"] + $union[$i]["len"];
$union[$i]["text"] = mb_substr($body, $union[$i]["start"],
$union[$i]["len"]);
// Discard the rest sections
while (count($union) > $i + 1) {
array_pop($union);
}
break;
}
// Not reached the maximum yet
$len += $union[$i]["len"] + $len_andsoon;
}
// Not reached the maximum yet - check the last section
if (!$reached_maximum) {
$i = count($union) - 1;
$needlen = $union[$i]["len"];
if ($union[$i]["end"] != $bodylen) {
$needlen += $len_andsoon;
}
// Not even enough for an abstract section
if ($len + $len_andsoon > $this->_QABS_MAXLEN) {
// Forget it. We can do nothing now.
array_pop($union);
// Reached the maximum
} elseif ($len + $needlen > $this->_QABS_MAXLEN) {
$union[$i]["len"] = $this->_QABS_MAXLEN - $len - $len_andsoon;
$union[$i]["end"] = $union[$i]["start"] + $union[$i]["len"];
$union[$i]["text"] = mb_substr($body, $union[$i]["start"],
$union[$i]["len"]);
}
// Not reached the maximum yet
}
// Mark the query phrases
for ($i = 0, $union_text = array(); $i < count($union); $i++) {
$pieces = array(
array(
"text" => $union[$i]["text"],
"is_match" => false,
),
);
// Mark each query phrase
for ($j = 0; $j < count($queries); $j++) {
$query = addslashes_re_php($queries[$j]);
for ($k = 0; $k < count($pieces); $k++) {
// Skip matches of other query phrases
if ($pieces[$k]["is_match"]) {
continue;
}
// Skip if not matched
if (!preg_match("/^(.*?)($query)(.*)$/i",
$pieces[$k]["text"], $m)) {
continue;
}
$pieces = array_merge(
array_slice($pieces, 0, $k),
array(
array(
"text" => $m[1],
"is_match" => false,
),
array(
"text" => $m[2],
"is_match" => true,
),
array(
"text" => $m[3],
"is_match" => false,
),
),
array_slice($pieces, $k + 1)
);
$k++;
}
}
for ($j = 0, $text = ""; $j < count($pieces); $j++) {
if ($pieces[$j]["is_match"]) {
$text .= "" . h($pieces[$j]["text"]) . "";
} else {
$text .= h($pieces[$j]["text"]);
}
}
$union_text[] = $text;
}
// Join these segments
$abstract = implode("…", $union_text);
if ($union[0]["start"] != 0) {
$abstract = "…" . $abstract;
}
if ($union[count($union)-1]["end"] != $bodylen) {
$abstract .= "…";
}
return $abstract;
}
// _html_title: Display the title
// Make it a null function
function _html_title()
{
return;
}
// _html_errmsg: Display the error message
function _html_errmsg()
{
if (is_null($this->error)) {
return;
}
$message = err2msg($this->error);
?>