You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

6577 lines
332 KiB

<?php
// Project: Web Reference Database (refbase) <http://www.refbase.net>
// Copyright: Matthias Steffens <mailto:refbase@extracts.de> and the file's
// original author(s).
//
// This code is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY. Please see the GNU General Public
// License for more details.
//
// File: ./includes/include.inc.php
// Repository: $HeadURL: file:///svn/p/refbase/code/branches/bleeding-edge/includes/include.inc.php $
// Author(s): Matthias Steffens <mailto:refbase@extracts.de>
//
// Created: 16-Apr-02, 10:54
// Modified: $Date: 2018-02-16 21:28:11 +0000 (Fri, 16 Feb 2018) $
// $Author: karnesky $
// $Revision: 1420 $
// This file contains important
// functions that are shared
// between all scripts.
// Incorporate some include files:
include 'initialize/db.inc.php'; // 'db.inc.php' is included to hide username and password
include 'initialize/ini.inc.php'; // include common variables
// include transliteration tables:
include 'includes/transtab_unicode_ascii.inc.php'; // include unicode -> ascii transliteration table
include 'includes/transtab_latin1_ascii.inc.php'; // include latin1 -> ascii transliteration table
include 'includes/transtab_unicode_latin1.inc.php'; // include unicode -> latin1 transliteration table
include 'includes/transtab_unicode_refbase.inc.php'; // include unicode -> refbase transliteration table
if ($contentTypeCharset == "UTF-8") // variable '$contentTypeCharset' is defined in 'ini.inc.php'
include_once 'includes/transtab_unicode_charset.inc.php'; // include unicode character case conversion tables
else // we assume "ISO-8859-1" by default
include_once 'includes/transtab_latin1_charset.inc.php'; // include latin1 character case conversion tables
// --------------------------------------------------------------------
// Untaint user data:
function clean($input, $maxlength)
{
$input = substr($input, 0, $maxlength);
$input = EscapeShellCmd($input);
return ($input);
}
// --------------------------------------------------------------------
// Start a session:
function start_session($updateUserFormatsStylesTypesPermissions)
{
global $databaseBaseURL; // these variables are defined in 'ini.inc.php'
global $defaultMainFields;
global $filesBaseDir;
global $filesBaseURL;
global $loginEmail;
global $loginUserID;
global $loginFirstName;
global $loginLastName;
global $abbrevInstitution;
global $lastLogin;
global $referer; // '$referer' is made globally available from within this function
global $connection;
// Initialize the session:
if (!isset($_SESSION["sessionID"]))
{
// Ensure that cookies are enabled:
if (ini_get('session.use_cookies') == 0) // if 'session.use_cookies' is OFF for the current directory
ini_set('session.use_cookies', 1); // enable storage of sessions within cookies
session_start();
$sessionID = session_id(); // get the current session ID
if (!empty($sessionID))
saveSessionVariable("sessionID", $sessionID);
}
// Set the system's locale information:
list($systemLocaleCollate, $systemLocaleCType) = setSystemLocale();
// Set the default timezone used by all date/time functions
// Note: The 'date_default_timezone_set/date_default_timezone_get' functions are available since PHP 5.1.0
if (function_exists("date_default_timezone_set") && function_exists("date_default_timezone_get"))
@date_default_timezone_set(@date_default_timezone_get());
// NOTE: Upon first connection to the MySQL server, function 'connectToMySQLDatabase()' will query the
// MySQL server for the MySQL version and save it to a session variable
// Extract session variables (only necessary if register globals is OFF!):
if (isset($_SESSION['loginEmail']))
{
$loginEmail = $_SESSION['loginEmail'];
$loginUserID = $_SESSION['loginUserID'];
$loginFirstName = $_SESSION['loginFirstName'];
$loginLastName = $_SESSION['loginLastName'];
$abbrevInstitution = $_SESSION['abbrevInstitution'];
$lastLogin = $_SESSION['lastLogin'];
}
elseif ($updateUserFormatsStylesTypesPermissions)
{
// If the user isn't logged in we set the available export formats, citation styles, document types and permissions to
// the defaults which are specified in the 'formats', 'styles', 'types' and 'user_permissions' tables for 'user_id = 0'.
// (a 'user_id' of zero is used within these tables to indicate the default settings if the user isn't logged in)
// NOTE: As an exception, for anyone who isn't logged in, we don't load the default number of records from option
// 'records_per_page' in table 'user_options', but instead use the value given in variable '$defaultNumberOfRecords'
// in 'ini.inc.php'. Similarly, if the user isn't logged in, the list of "main fields" is taken from variable
// '$defaultMainFields' in 'ini.inc.php' and not from option 'main_fields' in table 'user_options. Same holds true
// for variable '$autoCompleteUserInput' vs. option 'show_auto_completions'.
// Get all export formats that were selected by the admin to be visible if a user isn't logged in
// and (if some formats were found) save them as semicolon-delimited string to the session variable 'user_export_formats':
getVisibleUserFormatsStylesTypes(0, "format", "export");
// Get all citation formats that were selected by the admin to be visible if a user isn't logged in
// and (if some formats were found) save them as semicolon-delimited string to the session variable 'user_cite_formats':
getVisibleUserFormatsStylesTypes(0, "format", "cite");
// Get all citation styles that were selected by the admin to be visible if a user isn't logged in
// and (if some styles were found) save them as semicolon-delimited string to the session variable 'user_styles':
getVisibleUserFormatsStylesTypes(0, "style", "");
// Get all document types that were selected by the admin to be visible if a user isn't logged in
// and (if some types were found) save them as semicolon-delimited string to the session variable 'user_types':
getVisibleUserFormatsStylesTypes(0, "type", "");
// Get the user permissions for the current user
// and save all allowed user actions as semicolon-delimited string to the session variable 'user_permissions':
getPermissions(0, "user", true);
// Get the default view for the current user
// and save it to the session variable 'userDefaultView':
getDefaultView(0);
// Get the default number of records per page preferred by the current user
// and save it to the session variable 'userRecordsPerPage':
getDefaultNumberOfRecords(0);
// Get the user's preference for displaying auto-completions
// and save it to the session variable 'userAutoCompletions':
getPrefAutoCompletions(0);
// Get the list of "main fields" for the current user
// and save the list of fields as comma-delimited string to the session variable 'userMainFields':
getMainFields(0);
}
else // if ($updateUserFormatsStylesTypesPermissions == false)
{
// The scripts 'error.php', 'install.php' & 'update.php' use 'start_session(false);' so that they execute without errors
// when there isn't any database yet. However, function 'buildQuickSearchElements()' (which builds the "Quick Search" form
// in the page header) requires the session variable 'userMainFields' to be present. So we take the list of "main fields"
// directly from the global variable '$defaultMainFields' and save it as session variable (we cannot use function
// 'getMainFields()' here since this would require database access):
if (!isset($_SESSION['userMainFields']))
saveSessionVariable("userMainFields", $defaultMainFields);
}
// Set the referrer:
if (isset($_REQUEST['referer']) AND !empty($_REQUEST['referer']) AND strpos($_REQUEST['referer'], '://')===false)
$referer = $_REQUEST['referer']; // get the referring URL from the superglobal '$_REQUEST' variable (if any)
elseif (isset($_SESSION['referer']) AND !empty($_SESSION['referer']))
{
$referer = $_SESSION['referer']; // get the referring URL from the superglobal '$_SESSION' variable (if any)
deleteSessionVariable("referer");
}
elseif (isset($_SERVER['HTTP_REFERER']) AND !empty($_SERVER['HTTP_REFERER']))
$referer = $_SERVER['HTTP_REFERER']; // get the referring URL from the superglobal '$_SERVER' variable (if any)
else // as an example, the referrer won't be set if a user clicked on a URL of type 'show.php?record=12345' within an email announcement
$referer = "index.php"; // if all other attempts fail, we'll re-direct to the main page
// Verify important variables from 'ini.inc.php':
// - Ensure that the given paths/URLs end with a slash:
$databaseBaseURL = checkPath($databaseBaseURL, "URL");
$filesBaseDir = checkPath($filesBaseDir);
$filesBaseURL = checkPath($filesBaseURL, "URL");
}
// --------------------------------------------------------------------
// Create a new session variable:
function saveSessionVariable($sessionVariableName, $sessionVariableContents)
{
// since PHP 4.1.0 or greater, adding variables directly to the '$_SESSION' variable
// will register a session variable regardless whether register globals is ON or OFF!
$_SESSION[$sessionVariableName] = $sessionVariableContents;
}
// --------------------------------------------------------------------
// Remove a session variable:
function deleteSessionVariable($sessionVariableName)
{
if (ini_get('register_globals') == 1) // register globals is ON for the current directory
session_unregister($sessionVariableName); // clear the specified session variable
else // register globals is OFF for the current directory
unset($_SESSION[$sessionVariableName]); // clear the specified session variable
}
// --------------------------------------------------------------------
// Connect to the MySQL database:
// TODO: I18n
function connectToMySQLDatabase()
{
global $hostName; // these variables are specified in 'db.inc.php'
global $username;
global $password;
global $databaseName;
global $contentTypeCharset; // defined in 'ini.inc.php'
global $connection;
// If a connection parameter is not available, then use our own connection to avoid any locking problems
if (!isset($connection))
{
// (1) OPEN the database connection:
// (variables are set by include file 'db.inc.php'!)
if (!($connection = @ mysqli_connect($hostName, $username, $password, $databaseName)))
if (mysqli_errno($connection) != 0) // this works around a stupid(?) behaviour of the Roxen webserver that returns 'errno: 0' on success! ?:-(
showErrorMsg("The following error occurred while trying to connect to the host:");
// Get the MySQL version and save it to a session variable:
if (!isset($_SESSION['mysqlVersion']))
saveSessionVariable("mysqlVersion", getMySQLversion());
// (2) Set the connection character set (if connected to MySQL 4.1.x or greater):
// more info at <http://dev.mysql.com/doc/refman/5.1/en/charset-connection.html>
if (isset($_SESSION['mysqlVersion']) AND ($_SESSION['mysqlVersion'] >= 4.1))
{
// NOTE: "SET NAMES ..." requires MySQL 4.1 or greater
if ($contentTypeCharset == "UTF-8")
queryMySQLDatabase("SET NAMES utf8"); // set the character set for this connection to 'utf8'
else
queryMySQLDatabase("SET NAMES latin1"); // by default, we establish a 'latin1' connection
}
}
}
// --------------------------------------------------------------------
// Query the MySQL database:
// NOTE: This function requires an established MySQL $connection
// TODO: I18n
function queryMySQLDatabase($query)
{
global $connection;
global $client;
// (3) RUN the query on the database through the connection:
if (!($result = @ mysqli_query($connection, $query)))
if (mysqli_errno($connection) != 0) // this works around a stupid(?) behaviour of the Roxen webserver that returns 'errno: 0' on success! ?:-(
{
if (isset($client) AND preg_match("/^cli/i", $client)) // if the query originated from a command line client such as the "refbase" CLI client ("cli-refbase-1.0")
showErrorMsg("Your query: " . encodeHTML($query) . "\n\ncaused the following error:");
else
showErrorMsg("Your query: '" . $query . "' caused the following error:");
}
return $result;
}
// --------------------------------------------------------------------
// Disconnect from the MySQL database:
// TODO: I18n
function disconnectFromMySQLDatabase()
{
global $connection;
if (isset($connection))
// (5) CLOSE the database connection:
if (!(mysqli_close($connection)))
if (mysqli_errno($connection) != 0) // this works around a stupid(?) behaviour of the Roxen webserver that returns 'errno: 0' on success! ?:-(
showErrorMsg("The following error occurred while trying to disconnect from the database:");
}
// --------------------------------------------------------------------
// Get MySQL version:
// NOTE: This function requires an established MySQL $connection
function getMySQLversion()
{
// CONSTRUCT SQL QUERY:
$query = "SELECT VERSION()";
$result = queryMySQLDatabase($query); // RUN the query on the database through the connection
$row = mysqli_fetch_row($result); // fetch the current row into the array $row (it'll be always *one* row, but anyhow)
$mysqlVersionString = $row[0]; // extract the contents of the first (and only) row (returned version string will be something like "4.0.20-standard" etc.)
$mysqlVersion = preg_replace("/^(\d+\.\d+).+/", "\\1", $mysqlVersionString); // extract main version number (e.g. "4.0") from version string
return $mysqlVersion;
}
// --------------------------------------------------------------------
// Get MySQL field info:
// (i.e. fetch field (column) information from a given result resource; returns the
// field property given in '$propertyName', else an array of all field properties;
// see <http://php.net/manual/en/mysqli-result.fetch-field-direct.php>)
function getMySQLFieldInfo($result, $fieldOffset, $propertyName = "")
{
$fieldInfoArray = array();
// Get field (column) metadata:
$fieldInfo = mysqli_fetch_field_direct($result, (int)$fieldOffset); // returns an object containing the field information
// Copy object properties to an array:
$fieldInfoArray["name"] = $fieldInfo->name; // column name
$fieldInfoArray["table"] = $fieldInfo->table; // name of the table the column belongs to
$fieldInfoArray["type"] = $fieldInfo->type; // the type of the column
$fieldInfoArray["def"] = $fieldInfo->def; // default value of the column
$fieldInfoArray["max_length"] = $fieldInfo->max_length; // maximum length of the column
$fieldInfoArray["max_length"] = $fieldInfo->max_length; // maximum length of the column
$fieldInfoArray["primary_key"] = $fieldInfo->primary_key; // 1 if the column is a primary key
$fieldInfoArray["unique_key"] = $fieldInfo->unique_key; // 1 if the column is a unique key
$fieldInfoArray["multiple_key"] = $fieldInfo->multiple_key; // 1 if the column is a non-unique key
$fieldInfoArray["numeric"] = $fieldInfo->numeric; // 1 if the column is numeric
$fieldInfoArray["blob"] = $fieldInfo->blob; // 1 if the column is a BLOB
$fieldInfoArray["unsigned"] = $fieldInfo->unsigned; // 1 if the column is unsigned
$fieldInfoArray["zerofill"] = $fieldInfo->zerofill; // 1 if the column is zero-filled
if (!empty($propertyName) AND isset($fieldInfoArray[$propertyName]))
return $fieldInfoArray[$propertyName];
else
return $fieldInfoArray;
}
// --------------------------------------------------------------------
// Find out how many rows are available and (if there were rows found) seek to the current offset:
// Note that this function will also (re-)assign values to the variables '$rowOffset', '$showRows',
// '$rowsFound', '$previousOffset', '$nextOffset' and '$showMaxRow'.
function seekInMySQLResultsToOffset($result, $rowOffset, $showRows, $displayType, $citeType)
{
// Find out how many rows are available:
$rowsFound = @ mysqli_num_rows($result);
if ($rowsFound > 0) // If there were rows found ...
{
// ... setup variables in order to facilitate "previous" & "next" browsing:
// a) Set '$rowOffset' to zero if not previously defined, or if a wrong number (<=0) was given
if (empty($rowOffset) || ($rowOffset <= 0) || ((($displayType != "Export") AND !($displayType == "Cite" AND (!preg_match("/^html$/i", $citeType)))) && ($showRows >= $rowsFound))) // the third condition is only necessary if '$rowOffset' gets embedded within the 'displayOptions' form (see function 'buildDisplayOptionsElements()' in 'include.inc.php')
$rowOffset = 0;
// Adjust the '$showRows' value if not previously defined, or if a wrong number (<=0 or float) was given
if (empty($showRows) || ($showRows <= 0) || !preg_match("/^[0-9]+$/", $showRows))
$showRows = $_SESSION['userRecordsPerPage']; // get the default number of records per page preferred by the current user
// Adjust '$rowOffset' if it's value exceeds the number of rows found:
if ($rowOffset > ($rowsFound - 1))
{
if ($rowsFound > $showRows)
$rowOffset = ($rowsFound - $showRows); // start display at first record of last page to be displayed
else
$rowOffset = 0; // start display at the very first record
}
if (($displayType != "Export") AND !($displayType == "Cite" AND (!preg_match("/^html$/i", $citeType)))) // we have to exclude '$displayType=Export' here since, for export, '$rowOffset' must always point to the first row number in the result set that should be returned
{
// NOTE: The current value of '$rowOffset' is embedded as hidden tag within the 'displayOptions' form. By this, the current row offset can be re-applied
// after the user pressed the 'Show'/'Hide' button within the 'displayOptions' form. But then, to avoid that browse links don't behave as expected,
// we need to adjust the actual value of '$rowOffset' to an exact multiple of '$showRows':
$offsetRatio = ($rowOffset / $showRows);
if (!is_integer($offsetRatio)) // check whether the value of the '$offsetRatio' variable is not an integer
{ // if '$offsetRatio' is a float:
$offsetCorrectionFactor = floor($offsetRatio); // get it's next lower integer
if ($offsetCorrectionFactor != 0)
$rowOffset = ($offsetCorrectionFactor * $showRows); // correct the current row offset to the closest multiple of '$showRows' *below* the current row offset
else
$rowOffset = 0;
}
}
// b) The "Previous" page begins at the current offset LESS the number of rows per page
$previousOffset = $rowOffset - $showRows;
// c) The "Next" page begins at the current offset PLUS the number of rows per page
$nextOffset = $rowOffset + $showRows;
// d) Seek to the current offset
mysqli_data_seek($result, $rowOffset); // move internal result pointer to the row number given in '$rowOffset'
}
else // set variables to zero in order to prevent 'Undefined variable...' messages when nothing was found ('$rowsFound = 0'):
{
$rowOffset = 0;
$previousOffset = 0;
$nextOffset = 0;
}
// Calculate the maximum result number on each page ('$showMaxRow' is required as parameter to the 'displayDetails()' function)
if (($rowOffset + $showRows) < $rowsFound)
$showMaxRow = ($rowOffset + $showRows); // maximum result number on each page
else
$showMaxRow = $rowsFound; // for the last results page, correct the maximum result number if necessary
return array($result, $rowOffset, $showRows, $rowsFound, $previousOffset, $nextOffset, $showMaxRow);
}
// --------------------------------------------------------------------
// Show error (prepares error output and redirects it to 'error.php' which displays the error message):
// TODO: I18n
function showErrorMsg($headerMsg)
{
global $client;
global $connection;
$errorNo = mysqli_errno($connection);
$errorMsg = mysqli_error($connection);
if (preg_match("/^cli/i", $client)) // if the query originated from a command line client such as the "refbase" CLI client ("cli-refbase-1.0")
// note that we also HTML encode the '$errorMsg' for CLI clients since a malicious user could use the client parameter to perform a cross-site scripting (XSS) attack
echo $headerMsg . "\n\nError " . $errorNo . ": " . encodeHTML($errorMsg) . "\n\n";
else
// in case of regular HTML output, '$errorMsg' gets HTML encoded in 'error.php'
header("Location: error.php?errorNo=" . $errorNo . "&errorMsg=" . rawurlencode($errorMsg) . "&headerMsg=" . rawurlencode($headerMsg));
exit;
}
// --------------------------------------------------------------------
// Generate and return a message, and optionally save the message to a session variable:
// Following optional variables can be passed with the '$message' and will be used for non-CLI clients:
// - '$class' defines the name of the CSS class of the span element that encloses the message
// - '$flavour' is the (valid!) name of an HTML phrase element (such as "strong" or "em") that's wrapped around the message
// - '$sessionVariable' is the name of the session variable (such as "HeaderString") to which the message shall be saved
// - '$prefix' is a string that's added at the beginning of the generated message string
// - '$suffix' is a string that's appended at the end of the generated message string
function returnMsg($message, $class = "", $flavour = "", $sessionVariable = "", $prefix = "", $suffix = "")
{
global $client;
// Because we now sanitize the header message, we no longer output it as
// HTML
/*
if (preg_match("/^cli/i", $client)) // if the query originated from a command line client such as the "refbase" CLI client ("cli-refbase-1.0")
{
*/
$fullMsg = $message . "\n\n"; // for CLI clients, we just echo the message text
echo $fullMsg;
/* }
else // return an HTML-formatted message:
{
$fullMsg = $prefix;
if (!empty($flavour))
$fullMsg .= '<' . $flavour . '>';
if (!empty($class))
$fullMsg .= '<span class="' . $class . '">' . $message . '</span>';
else
$fullMsg .= $message;
if (!empty($flavour))
$fullMsg .= '</' . $flavour . '>';
$fullMsg .= $suffix;
if (!empty($sessionVariable))
saveSessionVariable($sessionVariable, $fullMsg); // write message to session variable
}
*/
return $fullMsg;
}
// --------------------------------------------------------------------
// Show whether the user is logged in or not:
// TODO: I18n
function showLogin()
{
global $loginEmail;
global $loginWelcomeMsg;
global $loginFirstName;
global $loginLastName;
global $abbrevInstitution;
global $loginUserID;
global $loginStatus;
global $loginLinks;
global $adminLoginEmail; // ('$adminLoginEmail' is specified in 'ini.inc.php')
global $loc; // '$loc' is made globally available in 'core.php'
// $referer = $_SERVER["REQUEST_URI"]; // 'REQUEST_URI' does only seem to work for GET requests (but not for POST requests!) ?:-/
// so, as a workaround, we build an appropriate query string from scratch (which will also work for POST requests):
// --- BEGIN WORKAROUND ---
global $formType;
global $displayType;
global $queryURL;
global $showQuery;
global $showLinks;
global $showRows;
global $rowOffset;
global $citeStyle;
global $citeOrder;
global $orderBy;
global $recordAction;
global $serialNo;
global $headerMsg;
global $errorNo;
global $errorMsg;
// Get the path to the currently executing script, relative to the document root:
$scriptURL = scriptURL();
// Extract checkbox variable values from the request:
if (isset($_REQUEST['marked']))
$recordSerialsArray = $_REQUEST['marked']; // extract the values of all checked checkboxes (i.e., the serials of all selected records)
else
$recordSerialsArray = "";
$recordSerialsString = ""; // initialize variable
// join array elements:
if (!empty($recordSerialsArray)) // the user did check some checkboxes
$recordSerialsString = implode("&marked[]=", $recordSerialsArray); // prefix each record serial (except the first one) with "&marked[]="
$recordSerialsString = "&marked[]=" . $recordSerialsString; // prefix also the very first record serial with "&marked[]="
// based on the refering script we adjust the parameters that get included in the link:
if (preg_match("#/(index|install|update|simple_search|advanced_search|sql_search|library_search|duplicate_manager|duplicate_search|opensearch|query_history|extract|users|user_details|user_receipt)\.php#i", $scriptURL))
$referer = $scriptURL; // we don't need to provide any parameters if the user clicked login/logout on the main page, the install/update page or any of the search pages (we just need
// to re-locate back to these pages after successful login/logout). Logout on 'install.php', 'users.php', 'user_details.php' or 'user_receipt.php' will redirect to 'index.php'.
elseif (preg_match("#/user_options\.php#i", $scriptURL))
$referer = $scriptURL . "?" . "userID=" . $loginUserID;
elseif (preg_match("#/(record|receipt)\.php#i", $scriptURL))
$referer = $scriptURL . "?" . "recordAction=" . $recordAction . "&serialNo=" . $serialNo . "&headerMsg=" . rawurlencode($headerMsg);
elseif (preg_match("#/error\.php#i", $scriptURL))
$referer = $scriptURL . "?" . "errorNo=" . $errorNo . "&errorMsg=" . rawurlencode($errorMsg) . "&headerMsg=" . rawurlencode($headerMsg);
else
$referer = $scriptURL . "?" . "formType=" . "sqlSearch" . "&submit=" . $displayType . "&headerMsg=" . rawurlencode($headerMsg) . "&sqlQuery=" . $queryURL . "&showQuery=" . $showQuery . "&showLinks=" . $showLinks . "&showRows=" . $showRows . "&rowOffset=" . $rowOffset . $recordSerialsString . "&citeStyle=" . rawurlencode($citeStyle) . "&citeOrder=" . $citeOrder . "&orderBy=" . rawurlencode($orderBy);
// --- END WORKAROUND -----
// Is the user logged in?
if (isset($_SESSION['loginEmail']))
{
$loginStatus = $loc["Welcome"];
$loginWelcomeMsg = "<em>" . encodeHTML($loginFirstName) . " " . encodeHTML($loginLastName) . "</em>!";
if ($loginEmail == $adminLoginEmail)
$loginStatus .= " <span class=\"warning\">" . $loc["Admin"] . "</span>";
$loginLinks = "";
if ($loginEmail == $adminLoginEmail) // if the admin is logged in, add the 'Add User' & 'Manage Users' links:
{
$loginLinks .= "<a href=\"user_details.php\" title=\"add a user to the database\">Add User</a>&nbsp;&nbsp;|&nbsp;&nbsp;";
$loginLinks .= "<a href=\"users.php\" title=\"manage user data\">Manage Users</a>&nbsp;&nbsp;|&nbsp;&nbsp;";
}
else // if a normal user is logged in, we add the 'My Refs' and 'Options' links instead:
{
$loginLinks .= "<a href=\"search.php?formType=myRefsSearch&amp;showQuery=0&amp;showLinks=1&amp;myRefsRadio=1\"" . addAccessKey("attribute", "my_refs") . " title=\"" . $loc["LinkTitle_MyRefs"] . addAccessKey("title", "my_refs") . "\">" . $loc["MyRefs"] . "</a>&nbsp;&nbsp;|&nbsp;&nbsp;";
if (isset($_SESSION['user_permissions']) AND preg_match("/allow_modify_options/", $_SESSION['user_permissions'])) // if the 'user_permissions' session variable contains 'allow_modify_options'...
// ... include a link to 'user_receipt.php':
$loginLinks .= "<a href=\"user_receipt.php?userID=" . $loginUserID . "\"" . addAccessKey("attribute", "my_opt") . " title=\"" . $loc["LinkTitle_Options"] . addAccessKey("title", "my_opt") . "\">" . $loc["Options"] . "</a>&nbsp;&nbsp;|&nbsp;&nbsp;";
}
$loginLinks .= "<a href=\"user_logout.php?referer=" . rawurlencode($referer) . "\"" . addAccessKey("attribute", "login") . " title=\"" . $loc["LinkTitle_Logout"] . addAccessKey("title", "login") . "\">" . $loc["Logout"] . "</a>";
}
else
{
if (preg_match("#.*(record|import[^.]*)\.php#i", $scriptURL))
$loginStatus = "<span class=\"warning\">" . $loc["Warning_LoginToSubmitForm"] . "!</span>";
else
$loginStatus = "";
$loginWelcomeMsg = "";
$loginLinks = "<a href=\"user_login.php?referer=" . rawurlencode($referer) . "\"" . addAccessKey("attribute", "login") . " title=\"" . $loc["LinkTitle_Login"] . addAccessKey("title", "login") . "\">" . $loc["Login"] . "</a>";
}
// Although the '$referer' variable gets included as GET parameter above, we'll also save the variable as session variable:
// (this should help re-directing to the correct page if a user called 'user_login/logout.php' manually, i.e., without parameters)
saveSessionVariable("referer", $referer);
}
// --------------------------------------------------------------------
// Enable 'accesskey' attribute for the specified link/form element:
// '$type' must be either "attribute" or "title", and '$key' must be the
// name of an array key from variable '$accessKeys' (in 'ini.inc.php')
function addAccessKey($type, $key)
{
global $accessKeys; // defined in 'ini.inc.php'
$accessKeyString = "";
if (isset($accessKeys) AND (!empty($accessKeys[$key]) OR ($accessKeys[$key] == "0")))
{
if ($type == "attribute") // add 'accesskey' attribute (like ' accesskey="h"') to the specified link or form element
$accessKeyString = " accesskey=\"" . $accessKeys[$key] . "\"";
elseif ($type == "title") // add access key hint (like ' [ctrl-h]') to the title attribute value of the specified link or form element
$accessKeyString = " [ctrl-" . $accessKeys[$key] . "]";
}
return $accessKeyString;
}
// --------------------------------------------------------------------
// Get the 'user_id' for the record entry in table 'auth' whose email matches that in '$emailAddress':
function getUserID($emailAddress)
{
global $tableAuth; // defined in 'db.inc.php'
connectToMySQLDatabase();
// CONSTRUCT SQL QUERY:
$query = "SELECT user_id FROM $tableAuth WHERE email = " . quote_smart($emailAddress);
$result = queryMySQLDatabase($query); // RUN the query on the database through the connection
$row = mysqli_fetch_array($result);
return($row["user_id"]);
}
// --------------------------------------------------------------------
// ADD RECORDS
// Adds record(s) to the database (i.e., add one or more row entries to MySQL table 'refs'):
// Notes: - the function will return the serial number(s) of all newly created record(s) in an array structure
// - structure of '$importDataArray' (sample values given in "..."):
// array(
// ['type'] => "refbase" // mandatory; indicates the array format of the 'records' element (currently only "refbase" is recognized, a standardized "bibliophile" format may be provided later on)
// ['version'] => "1.0" // mandatory; the version of the given array structure
// ['records'] => array( // mandatory; array of arrays containing the records & field data that should be imported; the sub-array element key must correspond to a refbase database field name from table 'refs'
// [0] => array( // first record
// [author] => "..." // - contents of 'author' field
// [title] => "..." // - contents of 'title' field
// ...
// )
// [1] => array( // second record
// [author] => "..." // - contents of 'author' field
// [title] => "..." // - contents of 'title' field
// ...
// )
// ...
// )
// ['creator'] => "http://refbase.net" // optional; the (preferably unique) name of the calling script/importer, use an URI if possible
// ['author'] => "Matthias Steffens" // optional; the name of the person who developed the script/importer and/or who can be contacted in case of problems
// ['contact'] => "refbase@extracts.de" // optional; the contact address of the person specified under 'author', use an email address if possible
// ['options'] => array( // optional; array with settings that control the behaviour of the 'addRecords()' function, currently there's only one option:
// [prefix_call_number] => "true" // if "true", any 'call_number' string will be prefixed with the correct call number prefix of the currently logged-in user (e.g. 'IPÖ @ msteffens @ ')
// )
// )
function addRecords($importDataArray)
{
global $loginUserID;
global $tableRefs, $tableUserData; // defined in 'db.inc.php'
global $connection;
connectToMySQLDatabase();
$recognizedArrayFormatsAndVersions = array('refbase' => array("1.0")); // for each recognized format, this array lists its format identifier as element key and an array of known versions as element value
$serialNumbersArray = array(); // initialize array variable which will hold the serial numbers of all imported records
// Verify the structure of the '$importDataArray':
if (!empty($importDataArray['type']) AND !empty($importDataArray['version']) AND !empty($importDataArray['records'])) // the array elements 'type', 'version' and 'records' are mandatory and must not be empty
{
// Currently, we only support the default "refbase" array structure in its initial version ("1.0") (support for other more generalized array formats may come later)
if (($importDataArray['type'] == "refbase") AND (in_array($importDataArray['version'], $recognizedArrayFormatsAndVersions['refbase'])))
{
$recordsArray = $importDataArray['records']; // get the array of records that shall be imported
// First, setup some required variables:
// Get the current date (e.g. '2003-12-31'), time (e.g. '23:59:49') and user name & email address (e.g. 'Matthias Steffens (refbase@extracts.de)'):
// note that we use the same time stamp for ALL imported records (so that users can easily identify all records belonging to one import action)
list ($currentDate, $currentTime, $currentUser) = getCurrentDateTimeUser();
// LOOP OVER EACH RECORD:
foreach ($recordsArray as $recordData) // for each record...
{
// Initialize some variables (in order to avoid "undefined index" messages when the particular array elements are not available):
if (isset($recordData['author']))
$author = $recordData['author'];
else
$author = "";
if (isset($recordData['pages']))
$pages = $recordData['pages'];
else
$pages = "";
if (isset($recordData['volume']))
$volume = $recordData['volume'];
else
$volume = "";
if (isset($recordData['series_volume']))
$seriesVolume = $recordData['series_volume'];
else
$seriesVolume = "";
// Assign correct values to the calculation fields 'first_author', 'author_count', 'first_page', 'volume_numeric' and 'series_volume_numeric':
list ($firstAuthor, $authorCount, $firstPage, $volumeNumeric, $seriesVolumeNumeric) = generateCalculationFieldContent($author, $pages, $volume, $seriesVolume);
// CONSTRUCT SQL QUERY:
// INSERT - construct a query to add data as new record
$queryRefs = ""; // note: we'll prefix "INSERT INTO $tableRefs SET " *after* we've parsed all array elements to trap the case that none of the array elements did contain any data
if (!empty($recordData['author']))
$queryRefs .= "author = " . quote_smart($recordData['author']) . ", ";
if (!empty($firstAuthor))
$queryRefs .= "first_author = " . quote_smart($firstAuthor) . ", ";
if (!empty($authorCount))
$queryRefs .= "author_count = " . quote_smart($authorCount) . ", ";
if (!empty($recordData['title']))
$queryRefs .= "title = " . quote_smart($recordData['title']) . ", ";
if (!empty($recordData['year']))
$queryRefs .= "year = " . quote_smart($recordData['year']) . ", ";
if (!empty($recordData['publication']))
$queryRefs .= "publication = " . quote_smart($recordData['publication']) . ", ";
if (!empty($recordData['abbrev_journal']))
$queryRefs .= "abbrev_journal = " . quote_smart($recordData['abbrev_journal']) . ", ";
if (!empty($recordData['volume']))
$queryRefs .= "volume = " . quote_smart($recordData['volume']) . ", ";
if (!empty($volumeNumeric))
$queryRefs .= "volume_numeric = " . quote_smart($volumeNumeric) . ", ";
if (!empty($recordData['issue']))
$queryRefs .= "issue = " . quote_smart($recordData['issue']) . ", ";
if (!empty($recordData['pages']))
$queryRefs .= "pages = " . quote_smart($recordData['pages']) . ", ";
if (!empty($firstPage))
$queryRefs .= "first_page = " . quote_smart($firstPage) . ", ";
if (!empty($recordData['address']))
$queryRefs .= "address = " . quote_smart($recordData['address']) . ", ";
if (!empty($recordData['corporate_author']))
$queryRefs .= "corporate_author = " . quote_smart($recordData['corporate_author']) . ", ";
if (!empty($recordData['keywords']))
$queryRefs .= "keywords = " . quote_smart($recordData['keywords']) . ", ";
if (!empty($recordData['abstract']))
$queryRefs .= "abstract = " . quote_smart($recordData['abstract']) . ", ";
if (!empty($recordData['publisher']))
$queryRefs .= "publisher = " . quote_smart($recordData['publisher']) . ", ";
if (!empty($recordData['place']))
$queryRefs .= "place = " . quote_smart($recordData['place']) . ", ";
if (!empty($recordData['editor']))
$queryRefs .= "editor = " . quote_smart($recordData['editor']) . ", ";
if (!empty($recordData['language']))
$queryRefs .= "language = " . quote_smart($recordData['language']) . ", ";
if (!empty($recordData['summary_language']))
$queryRefs .= "summary_language = " . quote_smart($recordData['summary_language']) . ", ";
if (!empty($recordData['orig_title']))
$queryRefs .= "orig_title = " . quote_smart($recordData['orig_title']) . ", ";
if (!empty($recordData['series_editor']))
$queryRefs .= "series_editor = " . quote_smart($recordData['series_editor']) . ", ";
if (!empty($recordData['series_title']))
$queryRefs .= "series_title = " . quote_smart($recordData['series_title']) . ", ";
if (!empty($recordData['abbrev_series_title']))
$queryRefs .= "abbrev_series_title = " . quote_smart($recordData['abbrev_series_title']) . ", ";
if (!empty($recordData['series_volume']))
$queryRefs .= "series_volume = " . quote_smart($recordData['series_volume']) . ", ";
if (!empty($seriesVolumeNumeric))
$queryRefs .= "series_volume_numeric = " . quote_smart($seriesVolumeNumeric) . ", ";
if (!empty($recordData['series_issue']))
$queryRefs .= "series_issue = " . quote_smart($recordData['series_issue']) . ", ";
if (!empty($recordData['edition']))
$queryRefs .= "edition = " . quote_smart($recordData['edition']) . ", ";
if (!empty($recordData['issn']))
$queryRefs .= "issn = " . quote_smart($recordData['issn']) . ", ";
if (!empty($recordData['isbn']))
$queryRefs .= "isbn = " . quote_smart($recordData['isbn']) . ", ";
if (!empty($recordData['medium']))
$queryRefs .= "medium = " . quote_smart($recordData['medium']) . ", ";
if (!empty($recordData['area']))
$queryRefs .= "area = " . quote_smart($recordData['area']) . ", ";
if (!empty($recordData['expedition']))
$queryRefs .= "expedition = " . quote_smart($recordData['expedition']) . ", ";
if (!empty($recordData['conference']))
$queryRefs .= "conference = " . quote_smart($recordData['conference']) . ", ";
// the 'location' and 'call_number' fields are handled below
if (!empty($recordData['approved']))
$queryRefs .= "approved = " . quote_smart($recordData['approved']) . ", ";
if (!empty($recordData['file']))
$queryRefs .= "file = " . quote_smart($recordData['file']) . ", ";
// the 'serial' field is handled below
if (!empty($recordData['orig_record']))
$queryRefs .= "orig_record = " . quote_smart($recordData['orig_record']) . ", ";
if (!empty($recordData['type']))
$queryRefs .= "type = " . quote_smart($recordData['type']) . ", ";
if (!empty($recordData['thesis']))
$queryRefs .= "thesis = " . quote_smart($recordData['thesis']) . ", ";
if (!empty($recordData['notes']))
$queryRefs .= "notes = " . quote_smart($recordData['notes']) . ", ";
if (!empty($recordData['url']))
$queryRefs .= "url = " . quote_smart($recordData['url']) . ", ";
if (!empty($recordData['doi']))
$queryRefs .= "doi = " . quote_smart($recordData['doi']) . ", ";
if (!empty($recordData['contribution_id']))
$queryRefs .= "contribution_id = " . quote_smart($recordData['contribution_id']) . ", ";
if (!empty($recordData['online_publication']))
$queryRefs .= "online_publication = " . quote_smart($recordData['online_publication']) . ", ";
if (!empty($recordData['online_citation']))
$queryRefs .= "online_citation = " . quote_smart($recordData['online_citation']) . ", ";
if (!empty($queryRefs)) // go ahead, if some array elements did contain data
{
// we only honour the 'call_number' string if some other record data were passed as well:
//
// if the 'prefix_call_number' option is set to "true", any 'call_number' string will be prefixed with
// the correct call number prefix of the currently logged-in user (e.g. 'IPÖ @ msteffens @ '):
if ((isset($_SESSION['loginEmail'])) AND (isset($importDataArray['options']['prefix_call_number'])) AND ($importDataArray['options']['prefix_call_number'] == "true"))
{
$callNumberPrefix = getCallNumberPrefix(); // build a correct call number prefix for the currently logged-in user (e.g. 'IPÖ @ msteffens')
if (!empty($recordData['call_number']))
$queryRefs .= "call_number = " . quote_smart($callNumberPrefix . " @ " . $recordData['call_number']) . ", "; // add call number prefix to 'call_number' string
else
$queryRefs .= "call_number = " . quote_smart($callNumberPrefix . " @ ") . ", "; // similar to the GUI behaviour, we'll also add a call number prefix if the 'call_number' string is empty
}
else
{
if (!empty($recordData['call_number']))
$queryRefs .= "call_number = " . quote_smart($recordData['call_number']) . ", ";
}
// if no specific cite key exists in '$recordData', any existing 'call_number' string gets also copied to the
// user-specific 'cite_key' field (which will ensure that this original call number/cite key is retained as
// cite key upon export); however, note that (depending on the user's settings) the cite key may get modified
// or regenerated by function 'generateCiteKey()' below
if (isset($_SESSION['loginEmail']) AND !empty($recordData['call_number']) AND empty($recordData['cite_key']))
$recordData['cite_key'] = $recordData['call_number'];
// for the 'location' field, we accept input from the '$recordData',
// but if no data were given, we'll add the currently logged-in user to the 'location' field:
if (!empty($recordData['location']))
$queryRefs .= "location = " . quote_smart($recordData['location']) . ", ";
elseif (isset($_SESSION['loginEmail']))
$queryRefs .= "location = " . quote_smart($currentUser) . ", ";
$queryRefs .= "serial = NULL, "; // inserting 'NULL' into an auto_increment PRIMARY KEY attribute allocates the next available key value
// we accept custom values for the *date/*time/*by fields if they are in correct format (*date: 'YYYY-MM-DD'; *time: 'HH:MM:SS'; *by: 'string'),
// otherwise we'll use the current date & time as well as the currently logged-in user name & email address:
if (!empty($recordData['created_by']))
$queryRefs .= "created_by = " . quote_smart($recordData['created_by']) . ", ";
elseif (isset($_SESSION['loginEmail']))
$queryRefs .= "created_by = " . quote_smart($currentUser) . ", ";
if (!empty($recordData['created_date']) AND preg_match("/^\d{4}-\d{2}-\d{2}$/", $recordData['created_date']))
$queryRefs .= "created_date = " . quote_smart($recordData['created_date']) . ", ";
else
$queryRefs .= "created_date = " . quote_smart($currentDate) . ", ";
if (!empty($recordData['created_time']) AND preg_match("/^\d{2}:\d{2}:\d{2}$/", $recordData['created_time']))
$queryRefs .= "created_time = " . quote_smart($recordData['created_time']) . ", ";
else
$queryRefs .= "created_time = " . quote_smart($currentTime) . ", ";
if (!empty($recordData['modified_by']))
$queryRefs .= "modified_by = " . quote_smart($recordData['modified_by']) . ", ";
elseif (isset($_SESSION['loginEmail']))
$queryRefs .= "modified_by = " . quote_smart($currentUser) . ", ";
if (!empty($recordData['modified_date']) AND preg_match("/^\d{4}-\d{2}-\d{2}$/", $recordData['modified_date']))
$queryRefs .= "modified_date = " . quote_smart($recordData['modified_date']) . ", ";
else
$queryRefs .= "modified_date = " . quote_smart($currentDate) . ", ";
if (!empty($recordData['modified_time']) AND preg_match("/^\d{2}:\d{2}:\d{2}$/", $recordData['modified_time']))
$queryRefs .= "modified_time = " . quote_smart($recordData['modified_time']) . "";
else
$queryRefs .= "modified_time = " . quote_smart($currentTime);
$queryRefs = "INSERT INTO $tableRefs SET " . $queryRefs; // finalize the query by prefixing it with the actual MySQL command
// ADD RECORD:
// RUN the query on the database through the connection:
$result = queryMySQLDatabase($queryRefs);
// Get the record id that was created:
$serialNo = @ mysqli_insert_id($connection); // find out the unique ID number of the newly created record (Note: this function should be called immediately after the
// SQL INSERT statement! After any subsequent query it won't be possible to retrieve the auto_increment identifier value for THIS record!)
// ADD USER DATA:
if (isset($_SESSION['loginEmail']))
{
// Note: At the moment, the record in table 'user_data' will be always created for the currently logged-in user,
// i.e. we don't try to match any custom data given in the 'location' field with users from table 'users'
// in order to set the 'user_id' in table 'user_data' accordingly
// This is a stupid hack that maps the names of the '$recordData' array keys to those used
// by the '$formVars' array (which is required by function 'generateCiteKey()')
// (eventually, the '$formVars' array should use the MySQL field names as names for its array keys)
$formVars = buildFormVarsArray($recordData);
// Generate or extract the cite key for this record:
$citeKey = generateCiteKey($formVars);
// Construct SQL query:
$queryUserData = "INSERT INTO $tableUserData SET ";
if (!empty($recordData['marked']) AND preg_match("/^(no|yes)$/", $recordData['marked']))
$queryUserData .= "marked = " . quote_smart($recordData['marked']) . ", ";
if (!empty($recordData['copy']) AND preg_match("/^(false|true|ordered|fetch)$/", $recordData['copy']))
$queryUserData .= "copy = " . quote_smart($recordData['copy']) . ", ";
else
$queryUserData .= "copy = 'true', "; // by default, 'false' would get inserted if omitted; we insert 'true' here in order to be consistent with manual record additions
if (!empty($recordData['selected']) AND preg_match("/^(no|yes)$/", $recordData['selected']))
$queryUserData .= "selected = " . quote_smart($recordData['selected']) . ", ";
if (!empty($recordData['user_keys']))
$queryUserData .= "user_keys = " . quote_smart($recordData['user_keys']) . ", ";
if (!empty($recordData['user_notes']))
$queryUserData .= "user_notes = " . quote_smart($recordData['user_notes']) . ", ";
if (!empty($recordData['user_file']))
$queryUserData .= "user_file = " . quote_smart($recordData['user_file']) . ", ";
if (!empty($recordData['user_groups']))
$queryUserData .= "user_groups = " . quote_smart($recordData['user_groups']) . ", ";
$queryUserData .= "cite_key = " . quote_smart($citeKey) . ", ";
if (!empty($recordData['related']))
$queryUserData .= "related = " . quote_smart($recordData['related']) . ", ";
$queryUserData .= "record_id = " . quote_smart($serialNo) . ", "
. "user_id = " . quote_smart($loginUserID) . ", " // '$loginUserID' is provided as session variable
. "data_id = NULL"; // inserting 'NULL' into an auto_increment PRIMARY KEY attribute allocates the next available key value
// RUN the query on the database through the connection:
$result = queryMySQLDatabase($queryUserData);
}
// Append this record's serial number to the array of imported record serials:
$serialNumbersArray[] = $serialNo;
}
// else: '$recordData' did not contain any data, so we skip this record
}
// (END LOOP OVER EACH RECORD)
}
// else: unknown array structure, return an empty '$serialNumbersArray'
}
// else: couldn't verify structure of '$importDataArray', return an empty '$serialNumbersArray'
return $serialNumbersArray; // return list of serial numbers of all imported records
}
// --------------------------------------------------------------------
// Assign correct values to the calculation fields 'first_author', 'author_count', 'first_page', 'volume_numeric' and 'series_volume_numeric':
function generateCalculationFieldContent($author, $pages, $volume, $seriesVolume)
{
if (!empty($author))
{
// Standardize contents of the author field (which will ensure correct sorting upon Citation output):
// - shorten author's full given name(s) to initial(s)
// - remove any delimiters (such as dots and/or whitespace) from author's initials
// Call the 'reArrangeAuthorContents()' function (defined in 'include.inc.php') in order to re-order contents of the author field. Required Parameters:
// 1. input: contents of the author field
// 2. input: boolean value that specifies whether the author's family name comes first (within one author) in the source string
// ('true' means that the family name is followed by the given name (or initials), 'false' if it's the other way around)
//
// 3. input: pattern describing old delimiter that separates different authors
// 4. output: for all authors except the last author: new delimiter that separates different authors
// 5. output: for the last author: new delimiter that separates the last author from all other authors
//
// 6. input: pattern describing old delimiter that separates author name & initials (within one author)
// 7. output: for the first author: new delimiter that separates author name & initials (within one author)
// 8. output: for all authors except the first author: new delimiter that separates author name & initials (within one author)
// 9. output: new delimiter that separates multiple initials (within one author)
// 10. output: for the first author: boolean value that specifies if initials go *before* the author's name ['true'], or *after* the author's name ['false'] (which is the default in the db)
// 11. output: for all authors except the first author: boolean value that specifies if initials go *before* the author's name ['true'], or *after* the author's name ['false'] (which is the default in the db)
// 12. output: boolean value that specifies whether an author's full given name(s) shall be shortened to initial(s)
//
// 13. output: if the total number of authors is greater than the given number (integer >= 1), only the number of authors given in (14) will be included in the citation along with the string given in (15); keep empty if all authors shall be returned
// 14. output: number of authors (integer >= 1) that is included in the citation if the total number of authors is greater than the number given in (13); keep empty if not applicable
// 15. output: string that's appended to the number of authors given in (14) if the total number of authors is greater than the number given in (13); the actual number of authors can be printed by including '__NUMBER_OF_AUTHORS__' (without quotes) within the string
//
// 16. output: boolean value that specifies whether the re-ordered string shall be returned with higher ASCII chars HTML encoded
$author = reArrangeAuthorContents($author, // 1.
true, // 2.
"/ *; */", // 3.
"; ", // 4.
"; ", // 5.
"/ *, */", // 6.
", ", // 7.
", ", // 8.
"", // 9.
false, // 10.
false, // 11.
true, // 12.
"", // 13.
"", // 14.
"", // 15.
false); // 16.
// 'first_author' field:
$firstAuthor = preg_replace("/^([^;]+).*/i", "\\1", $author); // extract first author from 'author' field
$firstAuthor = trim($firstAuthor); // remove leading & trailing whitespace (if any)
$firstAuthor = preg_replace("/ *\(eds?\)$/i", "", $firstAuthor); // remove any existing editor info from the 'first_author' string, i.e., kill any trailing " (ed)" or " (eds)"
// 'author_count' field:
if (!preg_match("/;/", $author)) // if the 'author' field does NOT contain a ';' (which would delimit multiple authors) => single author
$authorCount = "1"; // indicates a single author
elseif (preg_match("/^[^;]+;[^;]+$/", $author)) // the 'author' field does contain exactly one ';' => two authors
$authorCount = "2"; // indicates two authors
elseif (preg_match("/^[^;]+;[^;]+;[^;]+/", $author)) // the 'author' field does contain at least two ';' => more than two authors
$authorCount = "3"; // indicates three (or more) authors
}
else
{
$firstAuthor = "";
$authorCount = "";
}
// 'first_page' field:
if (!empty($pages))
{
if (preg_match("/([0-9]+)/", $pages)) // if the 'pages' field contains any numeric value(s)
$firstPage = preg_replace("/^[^0-9]*([0-9]+).*/i", "\\1", $pages); // extract first page from 'pages' field
else
$firstPage = "";
}
else
$firstPage = "";
// 'volume_numeric' field:
if (!empty($volume))
{
if (preg_match("/([0-9]+)/", $volume)) // if the 'volume' field contains any numeric value(s)
$volumeNumeric = preg_replace("/^[^0-9]*([0-9]+).*/i", "\\1", $volume); // extract first number from 'volume' field
else
$volumeNumeric = "";
}
else
$volumeNumeric = "";
// 'series_volume_numeric' field:
if (!empty($seriesVolume))
{
if (preg_match("/([0-9]+)/", $seriesVolume)) // if the 'series_volume' field contains any numeric value(s)
$seriesVolumeNumeric = preg_replace("/^[^0-9]*([0-9]+).*/i", "\\1", $seriesVolume); // extract first number from 'series_volume' field
else
$seriesVolumeNumeric = "";
}
else
$seriesVolumeNumeric = "";
return array($firstAuthor, $authorCount, $firstPage, $volumeNumeric, $seriesVolumeNumeric);
}
// --------------------------------------------------------------------
// Generic function that provides email sending capability:
function sendEmail($emailRecipient, $emailSubject, $emailBody)
{
global $adminLoginEmail; // these variables are specified in 'ini.inc.php'
global $contentTypeCharset;
// Setup some additional headers:
$emailHeaders = "From: " . $adminLoginEmail . "\n"
. "Return-Path: " . $adminLoginEmail . "\n"
. "X-Sender: " . $adminLoginEmail . "\n"
. "X-Mailer: PHP\n"
. "X-Priority: 3\n"
. "Content-Type: text/plain; charset=" . $contentTypeCharset;
// Send the email:
mail($emailRecipient, $emailSubject, $emailBody, $emailHeaders);
}
// --------------------------------------------------------------------
// Map MySQL field names to their localized names:
//
// TODO: - ensure that the names for field 'user_groups' in tables 'refs' and 'users' are
// set correctly (user-specific groups of references vs. admin groups of users)
// - add "DropDownFieldName_*" entries for unique field names of table 'users'
function mapFieldNames($isDropDown = false)
{
global $loc; // '$loc' is made globally available in 'core.php'
if ($isDropDown) // field names intended for inclusion into a dropdown form element:
{
$fieldNamesArray = array("author" => $loc["DropDownFieldName_Author"],
// "author_count" => $loc[""],
// "first_author" => $loc[""],
"address" => $loc["DropDownFieldName_Address"],
"corporate_author" => $loc["DropDownFieldName_CorporateAuthor"],
"thesis" => $loc["DropDownFieldName_Thesis"],
"title" => $loc["DropDownFieldName_Title"],
"orig_title" => $loc["DropDownFieldName_OrigTitle"],
"year" => $loc["DropDownFieldName_Year"],
"publication" => $loc["DropDownFieldName_Publication"],
"abbrev_journal" => $loc["DropDownFieldName_AbbrevJournal"],
"editor" => $loc["DropDownFieldName_Editor"],
"volume" => $loc["DropDownFieldName_Volume"],
// "volume_numeric" => $loc[""],
"issue" => $loc["DropDownFieldName_Issue"],
"pages" => $loc["DropDownFieldName_Pages"],
// "first_page" => $loc[""],
"series_title" => $loc["DropDownFieldName_SeriesTitle"],
"abbrev_series_title" => $loc["DropDownFieldName_AbbrevSeriesTitle"],
"series_editor" => $loc["DropDownFieldName_SeriesEditor"],
"series_volume" => $loc["DropDownFieldName_SeriesVolume"],
// "series_volume_numeric" => $loc[""],
"series_issue" => $loc["DropDownFieldName_SeriesIssue"],
"publisher" => $loc["DropDownFieldName_Publisher"],
"place" => $loc["DropDownFieldName_Place"],
"edition" => $loc["DropDownFieldName_Edition"],
"medium" => $loc["DropDownFieldName_Medium"],
"issn" => $loc["DropDownFieldName_Issn"],
"isbn" => $loc["DropDownFieldName_Isbn"],
"language" => $loc["DropDownFieldName_Language"],
"summary_language" => $loc["DropDownFieldName_SummaryLanguage"],
"keywords" => $loc["DropDownFieldName_Keywords"],
"abstract" => $loc["DropDownFieldName_Abstract"],
"area" => $loc["DropDownFieldName_Area"],
"expedition" => $loc["DropDownFieldName_Expedition"],
"conference" => $loc["DropDownFieldName_Conference"],
"doi" => $loc["DropDownFieldName_Doi"],
"url" => $loc["DropDownFieldName_Url"],
"file" => $loc["DropDownFieldName_File"],
"notes" => $loc["DropDownFieldName_Notes"],
"location" => $loc["DropDownFieldName_Location"],
"call_number" => $loc["DropDownFieldName_CallNumber"],
"serial" => $loc["DropDownFieldName_Serial"],
"type" => $loc["DropDownFieldName_Type"],
"approved" => $loc["DropDownFieldName_Approved"],
"created_date" => $loc["DropDownFieldName_CreatedDate"],
"created_time" => $loc["DropDownFieldName_CreatedTime"],
"created_by" => $loc["DropDownFieldName_CreatedBy"],
"modified_date" => $loc["DropDownFieldName_ModifiedDate"],
"modified_time" => $loc["DropDownFieldName_ModifiedTime"],
"modified_by" => $loc["DropDownFieldName_ModifiedBy"],
"marked" => $loc["DropDownFieldName_Marked"],
"copy" => $loc["DropDownFieldName_Copy"],
"selected" => $loc["DropDownFieldName_Selected"],
"user_keys" => $loc["DropDownFieldName_UserKeys"],
"user_notes" => $loc["DropDownFieldName_UserNotes"],
"user_file" => $loc["DropDownFieldName_UserFile"],
"user_groups" => $loc["DropDownFieldName_UserGroups"],
"cite_key" => $loc["DropDownFieldName_CiteKey"],
// "related" => $loc[""]
);
}
else // field names intended as title word or column heading:
{
$fieldNamesArray = array("author" => $loc["Author"],
"author_count" => $loc["AuthorCount"],
"first_author" => $loc["AuthorFirst"],
"address" => $loc["Address"],
"corporate_author" => $loc["CorporateAuthor"],
"thesis" => $loc["Thesis"],
"title" => $loc["Title"],
"orig_title" => $loc["TitleOriginal"],
"year" => $loc["Year"],
"publication" => $loc["Publication"],
"abbrev_journal" => $loc["JournalAbbr"],
"editor" => $loc["Editor"],
"volume" => $loc["Volume"],
"volume_numeric" => $loc["VolumeNumeric"],
"issue" => $loc["Issue"],
"pages" => $loc["Pages"],
"first_page" => $loc["PagesFirst"],
"series_title" => $loc["TitleSeries"],
"abbrev_series_title" => $loc["TitleSeriesAbbr"],
"series_editor" => $loc["SeriesEditor"],
"series_volume" => $loc["SeriesVolume"],
"series_volume_numeric" => $loc["SeriesVolumeNumeric"],
"series_issue" => $loc["SeriesIssue"],
"publisher" => $loc["Publisher"],
"place" => $loc["PublisherPlace"],
"edition" => $loc["Edition"],
"medium" => $loc["Medium"],
"issn" => $loc["ISSN"],
"isbn" => $loc["ISBN"],
"language" => $loc["Language"],
"summary_language" => $loc["LanguageSummary"],
"keywords" => $loc["Keywords"],
"abstract" => $loc["Abstract"],
"area" => $loc["Area"],
"expedition" => $loc["Expedition"],
"conference" => $loc["Conference"],
"doi" => $loc["DOI"],
"url" => $loc["URL"],
"file" => $loc["File"],
"notes" => $loc["Notes"],
"location" => $loc["Location"],
"call_number" => $loc["CallNumber"],
"serial" => $loc["Serial"],
"type" => $loc["Type"],
"approved" => $loc["Approved"],
"created_date" => $loc["CreationDate"],
"created_time" => $loc["CreationTime"],
"created_by" => $loc["Creator"],
"modified_date" => $loc["ModifiedDate"],
"modified_time" => $loc["ModifiedTime"],
"modified_by" => $loc["Modifier"],
"marked" => $loc["Marked"],
"copy" => $loc["Copy"],
"selected" => $loc["Selected"],
"user_keys" => $loc["UserKeys"],
"user_notes" => $loc["UserNotes"],
"user_file" => $loc["UserFile"],
"user_groups" => $loc["UserGroups"], // see TODO note above
"cite_key" => $loc["CiteKey"],
"related" => $loc["Related"],
// field names from table 'users' (that aren't covered by any of the above):
"first_name" => $loc["FirstName"],
"last_name" => $loc["LastName"],
"institution" => $loc["Institution"],
"abbrev_institution" => $loc["InstitutionAbbr"],
"corporate_institution" => $loc["CorporateInstitution"],
"address_line_1" => $loc["AddressLine1"],
"address_line_2" => $loc["AddressLine2"],
"address_line_3" => $loc["AddressLine3"],
"zip_code" => $loc["ZipCode"],
"city" => $loc["City"],
"state" => $loc["State"],
"country" => $loc["Country"],
"phone" => $loc["Phone"],
"email" => $loc["Email"],
"last_login" => $loc["LastLogin"],
"logins" => $loc["Logins"],
"user_id" => $loc["UserID"],
// "user_groups" => $loc["UserGroups"], // see TODO note above
);
}
return $fieldNamesArray;
}
// --------------------------------------------------------------------
// BUILD FIELD NAME LINKS
// (i.e., build clickable column headers for each available column)
// TODO: I18n
function buildFieldNameLinks($href, $query, $newORDER, $result, $i, $showQuery, $showLinks, $rowOffset, $showRows, $wrapResults, $citeStyle, $HTMLbeforeLink, $HTMLafterLink, $formType, $submitType, $linkName, $orig_fieldname, $headerMsg, $viewType)
{
global $databaseBaseURL; // defined in 'ini.inc.php'
global $loc; // '$loc' is made globally available in 'core.php'
global $client;
// Setup the base URL:
if (preg_match("/^(cli|inc)/i", $client) OR ($wrapResults == "0")) // we use absolute links for CLI clients, for include mechanisms, or when returning only a partial document structure
$baseURL = $databaseBaseURL;
else
$baseURL = "";
// Map MySQL field names to localized column names:
$fieldNamesArray = mapFieldNames();
// Get all field properties of the current MySQL field:
$fieldInfoArray = getMySQLFieldInfo($result, $i);
if (empty($orig_fieldname)) // if there's no fixed original fieldname specified (as is the case for all fields but the 'Links' column)
{
// Get the attribute name:
$orig_fieldname = $fieldInfoArray["name"];
}
if (empty($linkName)) // if there's no fixed link name specified (as is the case for all fields but the 'Links' column)...
{
if (isset($fieldNamesArray[$orig_fieldname]))
{
$linkName = $fieldNamesArray[$orig_fieldname]; // ...use the attribute's localized name as link name
}
else // ...use MySQL field name as fall back:
{
// Replace substrings with spaces:
$linkName = str_replace("_"," ",$orig_fieldname);
// Form words (i.e., make the first char of a word uppercase):
$linkName = ucwords($linkName);
}
}
// Setup some variables (in order to enable sorting by clicking on column titles)
// NOTE: Column sorting with any queries that include the 'LIMIT'... parameter
// will (technically) work. However, every new query will limit the selection to a *different* list of records!! ?:-/
if (empty($newORDER)) // if there's no fixed ORDER BY string specified (as is the case for all fields but the 'Links' column)
{
if ($fieldInfoArray["numeric"] == "1") // Check if the field's data type is numeric (if so we'll append " DESC" to the ORDER clause)
$newORDER = ("ORDER BY " . $orig_fieldname . " DESC"); // Build the appropriate ORDER BY clause (sort numeric fields in DESCENDING order)
else
$newORDER = ("ORDER BY " . $orig_fieldname); // Build the appropriate ORDER BY clause
}
if ($orig_fieldname == "pages") // when original field name = 'pages' then...
{
$newORDER = preg_replace("/ORDER BY pages/i", "ORDER BY first_page DESC", $newORDER); // ...sort by 'first_page' instead
$orig_fieldname = "first_page"; // adjust '$orig_fieldname' variable accordingly
}
if ($orig_fieldname == "volume") // when original field name = 'volume' then...
{
$newORDER = preg_replace("/ORDER BY volume/i", "ORDER BY volume_numeric DESC", $newORDER); // ...sort by 'volume_numeric' instead
$orig_fieldname = "volume_numeric"; // adjust '$orig_fieldname' variable accordingly
}
if ($orig_fieldname == "series_volume") // when original field name = 'series_volume' then...
{
$newORDER = preg_replace("/ORDER BY series_volume/i", "ORDER BY series_volume_numeric DESC", $newORDER); // ...sort by 'series_volume_numeric' instead
$orig_fieldname = "series_volume_numeric"; // adjust '$orig_fieldname' variable accordingly
}
if ($orig_fieldname == "marked") // when original field name = 'marked' then...
$newORDER = preg_replace("/ORDER BY marked/i", "ORDER BY marked DESC", $newORDER); // ...sort 'marked' column in DESCENDING order (so that 'yes' sorts before 'no')
if ($orig_fieldname == "last_login") // when original field name = 'last_login' (defined in 'users' table) then...
$newORDER = preg_replace("/ORDER BY last_login/i", "ORDER BY last_login DESC", $newORDER); // ...sort 'last_login' column in DESCENDING order (so that latest date+time sorts first)
$orderBy = preg_replace("/ORDER BY /i", "", $newORDER); // remove 'ORDER BY ' phrase in order to store just the 'ORDER BY' field spec within the 'orderBy' variable
// call the 'newORDERclause()' function to replace the ORDER clause:
$queryURLNewOrder = newORDERclause($newORDER, $query);
// in the link title, we'll report the field that is actually used for sorting:
if (isset($fieldNamesArray[$orig_fieldname]))
$linkTitleFieldName = $fieldNamesArray[$orig_fieldname];
else
$linkTitleFieldName = $linkName;
// figure out if clicking on the current field name will sort in ascending or descending order:
// (note that for 1st-level sort attributes, this value will be modified again below)
if (preg_match("/ORDER BY [^ ]+ DESC/i", $newORDER)) // if 1st-level sort is in descending order...
$linkTitleSortOrder = $loc["descendingOrder"]; // ...sorting will be conducted in DESCending order
else
$linkTitleSortOrder = $loc["ascendingOrder"]; // ...sorting will be conducted in ASCending order
// toggle sort order for the 1st-level sort attribute:
if (preg_match("/ORDER BY $orig_fieldname(?! DESC)/i", $query)) // if 1st-level sort is by this attribute (in ASCending order)...
{
$queryURLNewOrder = preg_replace("/(ORDER%20BY%20$orig_fieldname)(?!%20DESC)/i", "\\1%20DESC", $queryURLNewOrder); // ...change sort order to DESCending
$linkTitleSortOrder = $loc["descendingOrder"]; // adjust the link title attribute's sort info accordingly
}
elseif (preg_match("/ORDER BY $orig_fieldname DESC/i", $query)) // if 1st-level sort is by this attribute (in DESCending order)...
{
$queryURLNewOrder = preg_replace("/(ORDER%20BY%20$orig_fieldname)%20DESC/i", "\\1", $queryURLNewOrder); // ...change sort order to ASCending
$linkTitleSortOrder = $loc["ascendingOrder"]; // adjust the link title attribute's sort info accordingly
}
// build an informative string that get's displayed when a user mouses over a link:
$linkTitle = "\"" . $loc["LinkTitle_SortByField_Prefix"] . $linkTitleFieldName . $loc["LinkTitle_SortByField_Suffix"] . ", " . $linkTitleSortOrder . "\"";
// start the table header tag & print the attribute name as link:
$tableHeaderLink = $HTMLbeforeLink
. "<a href=\"" . $baseURL . $href
. "?sqlQuery=" . $queryURLNewOrder
. "&amp;submit=" . $submitType
. "&amp;citeStyle=" . rawurlencode($citeStyle)
. "&amp;orderBy=" . rawurlencode($orderBy)
. "&amp;headerMsg=" . rawurlencode($headerMsg)
. "&amp;showQuery=" . $showQuery
. "&amp;showLinks=" . $showLinks
. "&amp;formType=" . $formType
. "&amp;showRows=" . $showRows
. "&amp;rowOffset=" . $rowOffset
. "&amp;client=" . rawurlencode($client)
. "&amp;viewType=" . $viewType
. "\" title=" . $linkTitle . ">" . $linkName . "</a>";
// append sort indicator after the 1st-level sort attribute:
if (preg_match("/ORDER BY $orig_fieldname(?! DESC)(?=,| LIMIT|$)/i", $query)) // if 1st-level sort is by this attribute (in ASCending order)...
$tableHeaderLink .= "&nbsp;<img src=\"" . $baseURL . "img/sort_asc.gif\" alt=\"(up)\" title=\"" . $loc["LinkTitle_SortedByField_Prefix"] . $linkTitleFieldName . $loc["LinkTitle_SortedByField_Suffix"] . ", " . $loc["ascendingOrder"] . "\" width=\"8\" height=\"10\" hspace=\"0\" border=\"0\">"; // ...append an upward arrow image
elseif (preg_match("/ORDER BY $orig_fieldname DESC/i", $query)) // if 1st-level sort is by this attribute (in DESCending order)...
$tableHeaderLink .= "&nbsp;<img src=\"" . $baseURL . "img/sort_desc.gif\" alt=\"(down)\" title=\"" . $loc["LinkTitle_SortedByField_Prefix"] . $linkTitleFieldName . $loc["LinkTitle_SortedByField_Suffix"] . ", " . $loc["descendingOrder"] . "\" width=\"8\" height=\"10\" hspace=\"0\" border=\"0\">"; // ...append a downward arrow image
$tableHeaderLink .= $HTMLafterLink; // append any necessary HTML
return $tableHeaderLink;
}
// --------------------------------------------------------------------
// Build SELECT clause:
// (if given, '$additionalFields' & '$customSELECTclause' must contain
// a string of comma-separated field names)
// TODO: add support for 'users.php' SELECT clauses
function buildSELECTclause($displayType, $showLinks, $additionalFields = "", $addUserSpecificFields = true, $addRequiredFields = true, $customSELECTclause = "", $browseByField = "")
{
global $defaultFieldsListViewMajor; // these variables are specified in 'ini.inc.php'
global $defaultFieldsListViewMinor;
global $additionalFieldsCitationView;
global $showAdditionalFieldsDetailsViewDefault;
global $showUserSpecificFieldsDetailsViewDefault;
if (empty($displayType))
$displayType = $_SESSION['userDefaultView']; // get the default view for the current user
$querySELECTclause = "SELECT ";
if (!empty($customSELECTclause)) // if given, honour any custom SQL SELECT clause:
{
$querySELECTclause .= $customSELECTclause;
}
else // build a new SELECT clause that's suitable for the given '$displayType':
{
// Details view:
if (preg_match("/^(Display)$/i", $displayType)) // select all fields required to display record details:
{
if ($showAdditionalFieldsDetailsViewDefault == "no") // omit additional fields:
{
$querySELECTclause .= "author, title, type, year, publication, abbrev_journal, volume, issue, pages, keywords, abstract";
}
else // display all fields:
{
$querySELECTclause .= "author, title, type, year, publication, abbrev_journal, volume, issue, pages, keywords, abstract, address, corporate_author, thesis, publisher, place, editor, language, summary_language, orig_title, series_editor, series_title, abbrev_series_title, series_volume, series_issue, edition, issn, isbn, medium, area, expedition, conference, notes, approved";
if (isset($_SESSION['loginEmail']))
$querySELECTclause .= ", location"; // we only add the 'location' field if the user is logged in
}
$querySELECTclause .= ", call_number, serial";
if ($showUserSpecificFieldsDetailsViewDefault == "no")
$addUserSpecificFields = false;
}
// Edit mode & Export:
elseif (preg_match("/^(Edit|Export)$/i", $displayType)) // select all fields required to display record details (in edit mode) or to export a record:
{
$querySELECTclause .= "author, title, type, year, publication, abbrev_journal, volume, issue, pages, keywords, abstract, address, corporate_author, thesis, publisher, place, editor, language, summary_language, orig_title, series_editor, series_title, abbrev_series_title, series_volume, series_issue, edition, issn, isbn, medium, area, expedition, conference, notes, approved";
if (isset($_SESSION['loginEmail']))
$querySELECTclause .= ", location"; // we only add the 'location' field if the user is logged in
$querySELECTclause .= ", contribution_id, online_publication, online_citation, created_date, created_time, created_by, modified_date, modified_time, modified_by, call_number, serial";
}
// Citation view & RSS output:
elseif (preg_match("/^(Cite|RSS)$/i", $displayType)) // select all fields required to build proper record citations:
{
$querySELECTclause .= "author, title, type, year, publication, abbrev_journal, volume, issue, pages, keywords, abstract, thesis, editor, publisher, place, abbrev_series_title, series_title, series_editor, series_volume, series_issue, edition, language, author_count, online_publication, online_citation, doi, serial";
if ($displayType == "RSS") // for RSS output, we add some additional fields:
$querySELECTclause .= ", created_date, created_time, created_by, modified_date, modified_time, modified_by";
if (!empty($additionalFieldsCitationView)) // append all fields from '$additionalFieldsCitationView' that aren't yet included in the SELECT clause
foreach ($additionalFieldsCitationView as $field)
if (!preg_match("/\b" . $field . "\b/", $querySELECTclause))
{
if (preg_match("/^(marked|copy|selected|user_keys|user_notes|user_file|user_groups|cite_key|related)$/", $field)) // if '$field' is one of the user-specific fields, we'll add all of them below
$addUserSpecificFields = true;
else // append field:
$querySELECTclause .= ", " . $field;
}
}
// Browse view:
elseif (preg_match("/^Browse$/i", $displayType))
{
$querySELECTclause .= escapeSQL($browseByField) . ", COUNT(*) AS records";
}
// List view:
else // produce the default columnar output style:
{
$querySELECTclause .= $defaultFieldsListViewMajor;
if (!empty($defaultFieldsListViewMinor))
{
$querySELECTclause .= ", " . $defaultFieldsListViewMinor;
}
}
}
// All views (except Browse view):
if (!preg_match("/^Browse$/i", $displayType))
{
if (!empty($additionalFields))
{
if ($querySELECTclause != "SELECT ")
$querySELECTclause .= ", "; // add a comma as field separator, if other fields have already been added to the SELECT clause
$querySELECTclause .= $additionalFields;
}
// NOTE: Functions 'displayColumns()' and 'displayDetails()' (in 'search.php') apply some logic that prevents some or all of the
// below fields from getting displayed. This means that you must adopt these functions if you add or remove fields below.
if ($addUserSpecificFields)
{
if (isset($_SESSION['loginEmail'])) // if a user is logged in...
$querySELECTclause .= ", marked, copy, selected, user_keys, user_notes, user_file, user_groups, cite_key, related"; // add user-specific fields
}
if ($addRequiredFields)
{
// NOTE: Although it won't be visible the 'orig_record' & 'serial' columns get included in every search query
// (that's executed directly and not included into HTML as a web link or routed again thru other scripts).
// The 'orig_record' column is required in order to present visual feedback on duplicate records, and
// the 'serial' column is required in order to obtain unique checkbox names. For SQL queries passed to
// 'search.php' directly, function 'verifySQLQuery()' in 'include.inc.php' will add these columns.
$querySELECTclause .= ", orig_record, serial"; // add 'orig_record' and 'serial' columns
if ($showLinks == "1" OR (preg_match("/^(Edit|Export)$/i", $displayType)))
$querySELECTclause .= ", file, url, doi"; // add 'file', 'url' & 'doi' columns
if ($showLinks == "1" AND (!preg_match("/^(Edit|Export)$/i", $displayType)))
$querySELECTclause .= ", isbn, type"; // add 'isbn' & 'type columns (for export and edit mode, these columns have already been added above)
}
}
return $querySELECTclause;
}
// --------------------------------------------------------------------
// REPLACE ORDER CLAUSE IN SQL QUERY
function newORDERclause($newOrderBy, $query, $encodeQuery = true)
{
// replace any existing ORDER BY clause with the new one given in '$newOrderBy':
$newQuery = preg_replace("/ORDER BY .+?(?=LIMIT.*|GROUP BY.*|HAVING.*|PROCEDURE.*|FOR UPDATE.*|LOCK IN.*|$)/i", $newOrderBy, $query);
if ($encodeQuery)
$newQuery = rawurlencode($newQuery); // URL encode query
return $newQuery;
}
// --------------------------------------------------------------------
// REPLACE SELECT CLAUSE IN SQL QUERY
function newSELECTclause($newSelectClause, $query, $encodeQuery = true)
{
// replace any existing SELECT clause with the new one given in '$newSelectClause':
$newQuery = preg_replace("/SELECT .+?(?= FROM)/i", $newSelectClause, $query);
if ($encodeQuery)
$newQuery = rawurlencode($newQuery); // URL encode query
return $newQuery;
}
// --------------------------------------------------------------------
// BUILD BROWSE LINKS
// (i.e., build a TABLE row with links for "previous" & "next" browsing, as well as links to intermediate pages)
// TODO: - use divs + CSS styling (instead of a table-based layout) for _all_ output (not only for 'viewType=Mobile')
// - use function 'generateURL()' to build the link URLs
function buildBrowseLinks($href, $query, $NoColumns, $rowsFound, $showQuery, $showLinks, $showRows, $rowOffset, $previousOffset, $nextOffset, $wrapResults, $maxPageNo, $formType, $displayType, $citeStyle, $citeOrder, $orderBy, $headerMsg, $viewType)
{
global $databaseBaseURL; // these variables are defined in 'ini.inc.php'
global $displayResultsHeaderDefault;
global $displayResultsFooterDefault;
global $loc; // '$loc' is made globally available in 'core.php'
global $client;
// First, calculate the offset page number:
$pageOffset = ($rowOffset / $showRows);
// workaround for always rounding upward (since I don't know better! :-/):
if (preg_match("/[0-9]+\.[0-9+]/",$pageOffset)) // if the result number is not an integer..
$pageOffset = (int) $pageOffset + 1; // we convert the number into an integer and add 1
// set the offset page number to a multiple of $maxPageNo:
$pageOffset = $maxPageNo * (int) ($pageOffset / $maxPageNo);
// Plus, calculate the maximum number of pages needed:
$lastPage = ($rowsFound / $showRows);
// workaround for always rounding upward (since I don't know better! :-/):
if (preg_match("/[0-9]+\.[0-9+]/",$lastPage)) // if the result number is not an integer..
$lastPage = (int) $lastPage + 1; // we convert the number into an integer and add 1
// Setup the base URL:
if (preg_match("/^(cli|inc)/i", $client) OR ($wrapResults == "0")) // we use absolute links for CLI clients, for include mechanisms, or when returning only a partial document structure
$baseURL = $databaseBaseURL;
else
$baseURL = "";
if (preg_match("/^Mobile$/i", $viewType))
{
$BrowseLinks = "\n<div class=\"resultnav\">";
}
else
{
// Start a <TABLE>:
$BrowseLinks = "\n<table class=\"resultnav\" align=\"center\" border=\"0\" cellpadding=\"0\" cellspacing=\"10\" width=\"95%\" summary=\"This table holds browse links that link to the results pages of your query\">";
// Start a <TABLE> row:
$BrowseLinks .= "\n<tr>";
}
if (preg_match("/^Mobile$/i", $viewType))
$BrowseLinks .= "\n\t<div class=\"mainnav\"><a href=\"" . $baseURL . "index.php\"" . addAccessKey("attribute", "home") . " title=\"" . $loc["LinkTitle_Home"] . addAccessKey("title", "home") . "\">" . $loc["Home"] . "</a></div>";
elseif (preg_match("/^Print$/i", $viewType) OR preg_match("/^cli/i", $client))
$BrowseLinks .= "\n\t<td class=\"mainnav\" align=\"left\" valign=\"bottom\" width=\"225\"><a href=\"" . $baseURL . "index.php\"" . addAccessKey("attribute", "home") . " title=\"" . $loc["LinkTitle_Home"] . addAccessKey("title", "home") . "\">" . $loc["Home"] . "</a></td>";
elseif (($href == "users.php") OR !isset($displayResultsFooterDefault[$displayType]) OR (isset($displayResultsFooterDefault[$displayType]) AND ($displayResultsFooterDefault[$displayType] != "hidden")))
{
$BrowseLinks .= "\n\t<td class=\"small\" align=\"left\" valign=\"bottom\" width=\"225\">"
. "\n\t\t<a href=\"JavaScript:checkall(true,'marked%5B%5D')\" title=\"" . $loc["LinkTitle_SelectAll"] . "\">" . $loc["SelectAll"] . "</a>&nbsp;&nbsp;&nbsp;"
. "\n\t\t<a href=\"JavaScript:checkall(false,'marked%5B%5D')\" title=\"" . $loc["LinkTitle_DeselectAll"] . "\">" . $loc["DeselectAll"] . "</a>"
. "\n\t</td>";
}
else // don't show the select/deselect links when the results footer is hidden
{
$BrowseLinks .= "\n\t<td class=\"small\" align=\"left\" valign=\"bottom\" width=\"225\">&nbsp;</td>";
}
if (preg_match("/^Mobile$/i", $viewType))
$BrowseLinks .= "\n\t<div class=\"pagenav\">";
else
$BrowseLinks .= "\n\t<td class=\"pagenav\" align=\"center\" valign=\"bottom\">";
// a) If there's a page range below the one currently shown,
// create a "[xx-xx]" link (linking directly to the previous range of pages):
if ($pageOffset > "0")
{
$previousRangeFirstPage = ($pageOffset - $maxPageNo + 1); // calculate the first page of the previous page range
$previousRangeLastPage = ($previousRangeFirstPage + $maxPageNo - 1); // calculate the last page of the previous page range
$BrowseLinks .= "\n\t\t<a href=\"" . $baseURL . $href
. "?sqlQuery=" . rawurlencode($query)
. "&amp;submit=" . $displayType
. "&amp;citeStyle=" . rawurlencode($citeStyle)
. "&amp;citeOrder=" . $citeOrder
. "&amp;orderBy=" . rawurlencode($orderBy)
. "&amp;headerMsg=" . rawurlencode($headerMsg)
. "&amp;showQuery=" . $showQuery
. "&amp;showLinks=" . $showLinks
. "&amp;formType=" . $formType
. "&amp;showRows=" . $showRows
. "&amp;rowOffset=" . (($pageOffset - $maxPageNo) * $showRows)
. "&amp;client=" . rawurlencode($client)
. "&amp;viewType=" . $viewType
. "\" title=\"" . $loc["LinkTitle_DisplayResultsPage"] . " " . $previousRangeFirstPage . " " . $loc["LinkTitle_DisplayLinksToResultsPages"] . " " . $previousRangeFirstPage . "&#8211;" . $previousRangeLastPage . "\">[" . $previousRangeFirstPage . "&#8211;" . $previousRangeLastPage . "] </a>";
}
// b) Are there any previous pages?
if ($rowOffset > 0)
// Yes, so create a previous link
$BrowseLinks .= "\n\t\t<a href=\"" . $baseURL . $href
. "?sqlQuery=" . rawurlencode($query)
. "&amp;submit=" . $displayType
. "&amp;citeStyle=" . rawurlencode($citeStyle)
. "&amp;citeOrder=" . $citeOrder
. "&amp;orderBy=" . rawurlencode($orderBy)
. "&amp;headerMsg=" . rawurlencode($headerMsg)
. "&amp;showQuery=" . $showQuery
. "&amp;showLinks=" . $showLinks
. "&amp;formType=" . $formType
. "&amp;showRows=" . $showRows
. "&amp;rowOffset=" . $previousOffset
. "&amp;client=" . rawurlencode($client)
. "&amp;viewType=" . $viewType
. "\"" . addAccessKey("attribute", "previous") . " title=\"" . $loc["LinkTitle_DisplayPreviousResultsPage"] . addAccessKey("title", "previous") . "\">&lt;&lt;</a>";
else
// No, there is no previous page so don't print a link
$BrowseLinks .= "\n\t\t&lt;&lt;";
// c) Output the page numbers as links:
// Count through the number of pages in the results:
for($x=($pageOffset * $showRows), $page=($pageOffset + 1);
$x<$rowsFound && $page <= ($pageOffset + $maxPageNo);
$x+=$showRows, $page++)
// Is this the current page?
if ($x < $rowOffset ||
$x > ($rowOffset + $showRows - 1))
// No, so print out a link
$BrowseLinks .= " \n\t\t<a href=\"" . $baseURL . $href
. "?sqlQuery=" . rawurlencode($query)
. "&amp;submit=" . $displayType
. "&amp;citeStyle=" . rawurlencode($citeStyle)
. "&amp;citeOrder=" . $citeOrder
. "&amp;orderBy=" . rawurlencode($orderBy)
. "&amp;headerMsg=" . rawurlencode($headerMsg)
. "&amp;showQuery=" . $showQuery
. "&amp;showLinks=" . $showLinks
. "&amp;formType=" . $formType
. "&amp;showRows=" . $showRows
. "&amp;rowOffset=" . $x
. "&amp;client=" . rawurlencode($client)
. "&amp;viewType=" . $viewType
. "\" title=\"" . $loc["LinkTitle_DisplayResultsPage"] . " " . $page . "\">" . $page . "</a>";
else
// Yes, so don't print a link
$BrowseLinks .= " \n\t\t<b>$page</b>"; // current page is set in <b>BOLD</b>
$BrowseLinks .= " ";
// d) Are there any Next pages?
if ($rowsFound > $nextOffset)
// Yes, so create a next link
$BrowseLinks .= "\n\t\t<a href=\"" . $baseURL . $href
. "?sqlQuery=" . rawurlencode($query)
. "&amp;submit=" . $displayType
. "&amp;citeStyle=" . rawurlencode($citeStyle)
. "&amp;citeOrder=" . $citeOrder
. "&amp;orderBy=" . rawurlencode($orderBy)
. "&amp;headerMsg=" . rawurlencode($headerMsg)
. "&amp;showQuery=" . $showQuery
. "&amp;showLinks=" . $showLinks
. "&amp;formType=" . $formType
. "&amp;showRows=" . $showRows
. "&amp;rowOffset=" . $nextOffset
. "&amp;client=" . rawurlencode($client)
. "&amp;viewType=" . $viewType
. "\"" . addAccessKey("attribute", "next") . " title=\"" . $loc["LinkTitle_DisplayNextResultsPage"] . addAccessKey("title", "next") . "\">&gt;&gt;</a>";
else
// No, there is no next page so don't print a link
$BrowseLinks .= "\n\t\t&gt;&gt;";
// e) If there's a page range above the one currently shown,
// create a "[xx-xx]" link (linking directly to the next range of pages):
if ($pageOffset < ($lastPage - $maxPageNo))
{
$nextRangeFirstPage = ($pageOffset + $maxPageNo + 1); // calculate the first page of the next page range
$nextRangeLastPage = ($nextRangeFirstPage + $maxPageNo - 1); // calculate the last page of the next page range
if ($nextRangeLastPage > $lastPage)
$nextRangeLastPage = $lastPage; // adjust if this is the last range of pages and if it doesn't go up to the max allowed no of pages
$BrowseLinks .= "\n\t\t<a href=\"" . $baseURL . $href
. "?sqlQuery=" . rawurlencode($query)
. "&amp;submit=" . $displayType
. "&amp;citeStyle=" . rawurlencode($citeStyle)
. "&amp;citeOrder=" . $citeOrder
. "&amp;orderBy=" . rawurlencode($orderBy)
. "&amp;headerMsg=" . rawurlencode($headerMsg)
. "&amp;showQuery=" . $showQuery
. "&amp;showLinks=" . $showLinks
. "&amp;formType=" . $formType
. "&amp;showRows=" . $showRows
. "&amp;rowOffset=" . (($pageOffset + $maxPageNo) * $showRows)
. "&amp;client=" . rawurlencode($client)
. "&amp;viewType=" . $viewType
. "\" title=\"" . $loc["LinkTitle_DisplayResultsPage"] . " " . $nextRangeFirstPage . " " . $loc["LinkTitle_DisplayLinksToResultsPages"] . " " . $nextRangeFirstPage . "&#8211;" . $nextRangeLastPage . "\"> [" . $nextRangeFirstPage . "&#8211;" . $nextRangeLastPage . "]</a>";
}
if (preg_match("/^Mobile$/i", $viewType))
$BrowseLinks .= "\n\t</div>";
else
$BrowseLinks .= "\n\t</td>";
if (preg_match("/^Mobile$/i", $viewType))
$BrowseLinks .= "\n\t<div class=\"viewnav\">";
else
$BrowseLinks .= "\n\t<td class=\"viewnav\" align=\"right\" valign=\"bottom\" width=\"225\">";
// Add view links:
$viewLinksArray = array();
if (($href == "search.php") AND !preg_match("/^Browse$/i", $displayType) AND !preg_match("/^Mobile$/i", $viewType))
{
if (isset($_SESSION['user_permissions']) AND preg_match("/allow_list_view/", $_SESSION['user_permissions']))
{
if (preg_match("/^(Cite|Display)$/i", $displayType)) // display a link to List view:
{
// Replace current SELECT clause with one that's appropriate for List view:
if (isset($_SESSION['lastListViewQuery']))
$listViewSelectClause = "SELECT " . extractSELECTclause($_SESSION['lastListViewQuery']); // get SELECT clause from any previous List view query
else
$listViewSelectClause = buildSELECTclause("List", $showLinks, "", false, false); // produce the default columnar output style
$listViewQuery = newSELECTclause($listViewSelectClause, $query); // replace SELECT clause in current query and URL encode query
// f) create a 'List View' link that will show the currently displayed result set in List view:
$viewLinksArray[] = "<div class=\"leftview\"><a href=\"" . $baseURL . $href
. "?sqlQuery=" . $listViewQuery
. "&amp;submit=List"
. "&amp;citeStyle=" . rawurlencode($citeStyle)
. "&amp;citeOrder=" . $citeOrder
. "&amp;orderBy=" . rawurlencode($orderBy)
. "&amp;headerMsg=" . rawurlencode($headerMsg)
. "&amp;showQuery=" . $showQuery
. "&amp;showLinks=" . $showLinks
. "&amp;formType=" . $formType
. "&amp;showRows=" . $showRows
. "&amp;rowOffset=" . $rowOffset
. "&amp;client=" . rawurlencode($client)
. "&amp;viewType=" . $viewType
. "\"" . addAccessKey("attribute", "list") . " title=\"" . $loc["LinkTitle_DisplayListView"] . addAccessKey("title", "list") . "\">" . $loc["ListView"] . "</a></div>";
}
else
$viewLinksArray[] = "<div class=\"activeview\"><div class=\"leftview\">" . $loc["ListView"] . "</div></div>";
}
if (isset($_SESSION['user_permissions']) AND preg_match("/allow_cite/", $_SESSION['user_permissions']))
{
if (!preg_match("/^Cite$/i", $displayType)) // display a link to Citation view:
{
// Replace current SELECT clause with one that's appropriate for Citation view:
$citeViewSelectClause = buildSELECTclause("Cite", $showLinks, "", false, false); // select all fields required to build proper record citations
$citeViewQuery = newSELECTclause($citeViewSelectClause, $query); // replace SELECT clause in current query and URL encode query
// g) create a 'Citations' link that will show the currently displayed result set in Citation view:
$viewLinksArray[] = "<div class=\"middleview\"><a href=\"" . $baseURL . $href
. "?sqlQuery=" . $citeViewQuery
. "&amp;submit=Cite"
. "&amp;citeStyle=" . rawurlencode($citeStyle)
. "&amp;citeOrder=" . $citeOrder
. "&amp;orderBy=" . rawurlencode($orderBy)
. "&amp;headerMsg=" . rawurlencode($headerMsg)
. "&amp;showQuery=" . $showQuery
. "&amp;showLinks=" . $showLinks
. "&amp;formType=" . $formType
. "&amp;showRows=" . $showRows
. "&amp;rowOffset=" . $rowOffset
. "&amp;client=" . rawurlencode($client)
. "&amp;viewType=" . $viewType
. "\"" . addAccessKey("attribute", "cite") . " title=\"" . $loc["LinkTitle_DisplayCiteView"] . addAccessKey("title", "cite") . "\">" . $loc["Citations"] . "</a></div>";
}
else
$viewLinksArray[] = "<div class=\"activeview\"><div class=\"middleview\">" . $loc["Citations"] . "</div></div>";
}
if (isset($_SESSION['user_permissions']) AND preg_match("/allow_details_view/", $_SESSION['user_permissions']))
{
if (!preg_match("/^Display$/i", $displayType)) // display a link to Details view:
{
// Replace current SELECT clause with one that's appropriate for Details view:
if (isset($_SESSION['lastDetailsViewQuery']))
$detailsViewSelectClause = "SELECT " . extractSELECTclause($_SESSION['lastDetailsViewQuery']); // get SELECT clause from previous Details view query (if any)
else
$detailsViewSelectClause = buildSELECTclause("Display", $showLinks, "", false, false); // select all fields required to display record details
$detailsViewQuery = newSELECTclause($detailsViewSelectClause, $query); // replace SELECT clause in current query and URL encode query
// h) create a 'Details' link that will show the currently displayed result set in Details view:
$viewLinksArray[] = "<div class=\"rightview\"><a href=\"" . $baseURL . $href
. "?sqlQuery=" . $detailsViewQuery
. "&amp;submit=Display"
. "&amp;citeStyle=" . rawurlencode($citeStyle)
. "&amp;citeOrder=" . $citeOrder
. "&amp;orderBy=" . rawurlencode($orderBy)
. "&amp;headerMsg=" . rawurlencode($headerMsg)
. "&amp;showQuery=" . $showQuery
. "&amp;showLinks=" . $showLinks
. "&amp;formType=" . $formType
. "&amp;showRows=" . $showRows
. "&amp;rowOffset=" . $rowOffset
. "&amp;client=" . rawurlencode($client)
. "&amp;viewType=" . $viewType
. "\"" . addAccessKey("attribute", "details") . " title=\"" . $loc["LinkTitle_DisplayDetailsView"] . addAccessKey("title", "details") . "\">" . $loc["Details"] . "</a></div>";
}
else
$viewLinksArray[] = "<div class=\"activeview\"><div class=\"rightview\">" . $loc["Details"] . "</div></div>";
}
if (count($viewLinksArray) > 1)
$BrowseLinks .= "\n\t\t<div class=\"resultviews\">"
. "\n\t\t\t" . implode("\n\t\t\t&nbsp;|&nbsp;", $viewLinksArray)
. "\n\t\t</div>";
}
// Note: we omit 'Web/Print View' links for include mechanisms!
if (!preg_match("/^inc/i", $client))
{
$BrowseLinks .= "\n\t\t";
if (count($viewLinksArray) > 1)
$BrowseLinks .= "&nbsp;&nbsp;&nbsp;";
if (preg_match("/^(Print|Mobile)$/i", $viewType))
{
// i) create a 'Web View' link that will show the currently displayed result set in web view:
$BrowseLinks .= "<a class=\"toggleprint\" href=\"" . $baseURL . $href
. "?sqlQuery=" . rawurlencode($query)
. "&amp;submit=" . $displayType
. "&amp;citeStyle=" . rawurlencode($citeStyle)
. "&amp;citeOrder=" . $citeOrder
. "&amp;orderBy=" . rawurlencode($orderBy)
. "&amp;headerMsg=" . rawurlencode($headerMsg)
. "&amp;showQuery=" . $showQuery
. "&amp;showLinks=1"
. "&amp;formType=" . $formType
. "&amp;showRows=" . $showRows
. "&amp;rowOffset=" . $rowOffset
. "&amp;viewType=Web"
. "\"" . addAccessKey("attribute", "print") . "><img src=\"" . $baseURL . "img/web.gif\" alt=\"web\" title=\"" . $loc["LinkTitle_DisplayWebView"] . addAccessKey("title", "print") . "\" width=\"16\" height=\"16\" hspace=\"0\" border=\"0\"></a>";
}
else
{
if (isset($_SESSION['user_permissions']) AND preg_match("/allow_print_view/", $_SESSION['user_permissions'])) // if the 'user_permissions' session variable contains 'allow_print_view'...
// j) create a 'Print View' link that will show the currently displayed result set in print view:
$BrowseLinks .= "<a class=\"toggleprint\" href=\"" . $baseURL . $href
. "?sqlQuery=" . rawurlencode($query)
. "&amp;submit=" . $displayType
. "&amp;citeStyle=" . rawurlencode($citeStyle)
. "&amp;citeOrder=" . $citeOrder
. "&amp;orderBy=" . rawurlencode($orderBy)
. "&amp;headerMsg=" . rawurlencode($headerMsg)
. "&amp;showQuery=" . $showQuery
. "&amp;showLinks=0"
. "&amp;formType=" . $formType
. "&amp;showRows=" . $showRows
. "&amp;rowOffset=" . $rowOffset
. "&amp;viewType=Print"
. "\"" . addAccessKey("attribute", "print") . "><img src=\"" . $baseURL . "img/print.gif\" alt=\"print\" title=\"" . $loc["LinkTitle_DisplayPrintView"] . addAccessKey("title", "print") . "\" width=\"17\" height=\"18\" hspace=\"0\" border=\"0\"></a>";
}
}
if (preg_match("/^Mobile$/i", $viewType))
{
$BrowseLinks .= "\n\t</div>"
. "\n</div>";
}
else
{
$BrowseLinks .= "\n\t</td>"
. "\n</tr>"
. "\n</table>";
}
return $BrowseLinks;
}
// --------------------------------------------------------------------
// BUILD QUICK SEARCH ELEMENTS
// (i.e., generate the "Quick Search" form)
function buildQuickSearchElements($query, $queryURL, $showQuery, $showLinks, $showRows, $citeStyle, $citeOrder, $displayType)
{
global $tableRefs; // defined in 'db.inc.php'
global $loc; // '$loc' is made globally available in 'core.php'
global $client;
if (!preg_match("/^SELECT/i", $queryURL) OR !preg_match("/%20FROM%20" . $tableRefs . "%20/i", $queryURL)) // only include SELECT queries that query table 'refs'
$queryURL = ""; // this excludes e.g. queries that query table 'users'
$encodedCiteStyle = rawurlencode($citeStyle);
$encodedClient = rawurlencode($client);
$accessKeyAttribute = addAccessKey("attribute", "qck_search");
$accessKeyTitle = addAccessKey("title", "qck_search");
// extract the first field from the 'WHERE' clause:
if (preg_match("/ WHERE [ ()]*(\w+)/i", $query))
$firstField = preg_replace("/.+ WHERE [ ()]*(\w+).*/i", "\\1", $query);
else
$firstField = "";
// build HTML elements that allow for search suggestions for text entered by the user:
if (isset($_SESSION['userAutoCompletions']) AND ($_SESSION['userAutoCompletions'] == "yes"))
$suggestElements = buildSuggestElements("quickSearchName", "quickSearchSuggestions", "quickSearchSuggestProgress", "id-quickSearchSelector-", "\t\t\t\t\t\t");
else
$suggestElements = "";
// add the "Quick Search" form:
$quickSearchForm = <<<EOF
<form action="search.php" method="GET" name="quickSearch">
<fieldset>
<legend>$loc[QuickSearch]:</legend>
<input type="hidden" name="formType" value="quickSearch">
<input type="hidden" name="originalDisplayType" value="$displayType">
<input type="hidden" name="sqlQuery" value="$queryURL">
<input type="hidden" name="showQuery" value="$showQuery">
<input type="hidden" name="showLinks" value="$showLinks">
<input type="hidden" name="showRows" value="$showRows">
<input type="hidden" name="client" value="$encodedClient">
<input type="hidden" name="citeStyle" value="$encodedCiteStyle">
<input type="hidden" name="citeOrder" value="$citeOrder">
<div id="queryField">
<label for="quickSearchSelector">$loc[Field]:</label>
<select id="quickSearchSelector" name="quickSearchSelector" title="$loc[DescriptionSelectFieldQuickSearchForm]">
EOF;
// build correct option tags:
$userMainFieldsArray = preg_split("/ *, */", $_SESSION['userMainFields']); // get the list of "main fields" preferred by the current user
$dropDownFieldNameArray = array("main_fields" => $loc["DropDownFieldName_MainFields"]);
foreach($userMainFieldsArray as $userMainField)
{
// generate the variable name of the correct '$loc' locale for this field:
$dropDownFieldNameLocale = preg_replace_callback("/_(\w)/",
function($matches){
foreach($matches as $match){
return ucfirst($match);
}
},
$userMainField);
$dropDownFieldNameLocale = "DropDownFieldName_" . ucfirst($dropDownFieldNameLocale);
// add this field's name and localized string to the array of fields that will be included in the "Quick Search" drop-down menu:
$dropDownFieldNameArray[$userMainField] = $loc[$dropDownFieldNameLocale];
}
$optionTags = buildSelectMenuOptions($dropDownFieldNameArray, "//", "\t\t\t\t\t\t\t", true);
if (!empty($firstField) AND in_array($firstField, $userMainFieldsArray)) // if the first field from the 'WHERE' clause is one of the main fields
$quickSearchForm .= preg_replace("/<option([^>]* value=\"$firstField\")/", "<option\\1 selected", $optionTags); // we select that field by adding the 'selected' parameter to the appropriate <option> tag
else
$quickSearchForm .= preg_replace("/<option([^>]*)>" . $loc["DropDownFieldName_MainFields"] . "/", "<option\\1 selected>" . $loc["DropDownFieldName_MainFields"], $optionTags); // select the 'main fields' menu entry ...
$quickSearchForm .= <<<EOF
</select>
<label for="quickSearchName">$loc[contains]:</label>
<input type="text" id="quickSearchName" name="quickSearchName" size="11"$accessKeyAttribute title="$loc[DescriptionEnterSearchString]$accessKeyTitle">$suggestElements
</div>
<div id="querySubmit">
<input type="submit" value="$loc[ButtonTitle_Search]" title="$loc[DescriptionSearchDB]">
</div>
</fieldset>
</form>
EOF;
return $quickSearchForm;
}
// --------------------------------------------------------------------
// BUILD REFINE SEARCH ELEMENTS
// (i.e., provide options to refine the search results)
function buildRefineSearchElements($href, $queryURL, $showQuery, $showLinks, $showRows, $citeStyle, $citeOrder, $dropDownFieldsArray, $dropDownFieldSelected, $displayType)
{
global $loc; // '$loc' is made globally available in 'core.php'
global $client;
$encodedCiteStyle = rawurlencode($citeStyle);
$encodedClient = rawurlencode($client);
$accessKeyAttribute = addAccessKey("attribute", "refine");
$accessKeyTitle = addAccessKey("title", "refine");
// build HTML elements that allow for search suggestions for text entered by the user:
if (($href == "search.php") AND (isset($_SESSION['userAutoCompletions']) AND ($_SESSION['userAutoCompletions'] == "yes")))
$suggestElements = buildSuggestElements("refineSearchName", "refineSearchSuggestions", "refineSearchSuggestProgress", "id-refineSearchSelector-", "\t\t\t\t\t");
else
$suggestElements = "";
$refineSearchForm = <<<EOF
<form action="$href" method="GET" name="refineSearch">
<fieldset>
<legend>$loc[SearchWithinResults]:</legend>
<input type="hidden" name="formType" value="refineSearch">
<input type="hidden" name="submit" value="$loc[ButtonTitle_Search]">
<input type="hidden" name="originalDisplayType" value="$displayType">
<input type="hidden" name="sqlQuery" value="$queryURL">
<input type="hidden" name="showQuery" value="$showQuery">
<input type="hidden" name="showLinks" value="$showLinks">
<input type="hidden" name="showRows" value="$showRows">
<input type="hidden" name="client" value="$encodedClient">
<input type="hidden" name="citeStyle" value="$encodedCiteStyle">
<input type="hidden" name="citeOrder" value="$citeOrder">
<div id="refineField">
<label for="refineSearchSelector">$loc[Field]:</label>
<select id="refineSearchSelector" name="refineSearchSelector" title="$loc[DescriptionSelectFieldRefineResultsForm]">
EOF;
// build correct option tags from the column items provided:
$optionTags = buildSelectMenuOptions($dropDownFieldsArray, "//", "\t\t\t\t\t\t", true);
$optionTags = preg_replace("/<option([^>]* value=\"$dropDownFieldSelected\")/", "<option\\1 selected", $optionTags); // add 'selected' attribute
$refineSearchForm .= $optionTags;
$refineSearchForm .= <<<EOF
</select>
<label for="refineSearchName">$loc[contains]:</label>
<input type="text" id="refineSearchName" name="refineSearchName" size="11"$accessKeyAttribute title="$loc[DescriptionEnterSearchString]$accessKeyTitle">$suggestElements
</div>
<div id="refineOpt">
<input type="checkbox" id="refineSearchExclude" name="refineSearchExclude" value="1" title="$loc[DescriptionExcludeResultsCheckboxRefineResultsForm]">
<label for="refineSearchExclude">$loc[ExcludeMatches]</label>
</div>
<div id="refineSubmit">
<input type="submit" name="submit" value="$loc[ButtonTitle_Search]" title="$loc[DescriptionSearchButtonRefineResultsForm]">
</div>
</fieldset>
</form>
EOF;
return $refineSearchForm;
}
// --------------------------------------------------------------------
// BUILD USER GROUP FORM ELEMENTS
// (i.e., provide options to show the user's personal reference groups -OR- the admin's user groups)
// Note: this function serves two purposes (which must not be confused!):
// - if "$href = search.php", it will modify the values of the 'user_groups' field of the 'user_data' table (where a user can assign one or more groups to particular *references*)
// - if "$href = users.php", this function will modify the values of the 'user_groups' field of the 'users' table (where the admin can assign one or more groups to particular *users*)
function buildGroupSearchElements($href, $queryURL, $query, $showQuery, $showLinks, $showRows, $citeStyle, $citeOrder, $displayType)
{
global $loc; // '$loc' is made globally available in 'core.php'
global $client;
if (preg_match("/.+user_groups RLIKE \"[()|^.;* ]+[^;]+?[()|$.;* ]+\"/i", $query)) // if the query does contain a 'WHERE' clause that searches for a particular user group
// TODO: improve the legibility & robustness of the below regex pattern (yes, it's ugly)
$currentGroup = preg_replace("/.+user_groups RLIKE \"(?:\[:(?:space|punct):\]|[()|^.;* \]\[])+([^;]+?(?:\[\^\[:space:\]\[:punct:\]\]\*)?)(?:\[:(?:space|punct):\]|[()|$.;* \]\[])+\".*/i", "\\1", $query); // extract the particular group name
else
$currentGroup = "";
// show the 'Show My Groups' form:
// - if the admin is logged in and calls 'users.php' (since only the admin will be allowed to call 'users.php', checking '$href' is sufficient here) -OR-
// - if a user is logged in AND the 'user_permissions' session variable contains 'allow_user_groups'
if (($href == "users.php") OR (isset($_SESSION['loginEmail']) AND (isset($_SESSION['user_permissions']) AND preg_match("/allow_user_groups/", $_SESSION['user_permissions']))))
{
if (($href == "search.php" AND isset($_SESSION['userGroups'])) OR ($href == "users.php" AND isset($_SESSION['adminUserGroups']))) // if the appropriate session variable is set
{
$groupSearchDisabled = "";
$groupSearchSelectorTitle = $loc["DescriptionSelectFieldGroupsForm"];
$groupSearchButtonTitle = $loc["DescriptionShowButtonGroupsForm"];
}
else
{
$groupSearchDisabled = " disabled"; // disable the 'Show My Groups' form if the session variable holding the user's groups isnt't available
$groupSearchSelectorTitle = "(" . $loc["DescriptionSelectFieldGroupsFormDisabled"] . ")";
$groupSearchButtonTitle = "(" . $loc["DescriptionShowButtonGroupsFormDisabled"] . ")";
}
// adjust the form & dropdown labels according to the calling script (which is either 'search.php' or 'users.php')
if ($href == "search.php")
{
$formLegend = $loc["ShowMyGroup"] . ":";
$dropdownLabel = $loc["My"] . ":";
}
elseif ($href == "users.php")
{
$formLegend = $loc["ShowUserGroup"] . ":";
$dropdownLabel = $loc["Users"] . ":";
}
else // currently, '$href' will be either 'search.php' or 'users.php', but anyhow
{
$formLegend = $loc["ShowGroup"] . ":";
$dropdownLabel = "";
}
$encodedCiteStyle = rawurlencode($citeStyle);
$encodedClient = rawurlencode($client);
$groupSearchForm = <<<EOF
<form action="$href" method="GET" name="groupSearch">
<fieldset>
<legend>$formLegend</legend>
<input type="hidden" name="formType" value="groupSearch">
<input type="hidden" name="originalDisplayType" value="$displayType">
<input type="hidden" name="sqlQuery" value="$queryURL">
<input type="hidden" name="showQuery" value="$showQuery">
<input type="hidden" name="showLinks" value="$showLinks">
<input type="hidden" name="showRows" value="$showRows">
<input type="hidden" name="client" value="$encodedClient">
<input type="hidden" name="citeStyle" value="$encodedCiteStyle">
<input type="hidden" name="citeOrder" value="$citeOrder">
<div id="groupSelect">
<label for="groupSearchSelector">$dropdownLabel</label>
<select id="groupSearchSelector" name="groupSearchSelector" title="$groupSearchSelectorTitle"$groupSearchDisabled>
EOF;
if (($href == "search.php" AND isset($_SESSION['userGroups'])) OR ($href == "users.php" AND isset($_SESSION['adminUserGroups']))) // if the appropriate session variable is set
{
// build properly formatted <option> tag elements from the items listed in the appropriate session variable:
if ($href == "search.php")
$optionTags = buildSelectMenuOptions($_SESSION['userGroups'], "/ *; */", "\t\t\t\t\t\t", false);
elseif ($href == "users.php")
$optionTags = buildSelectMenuOptions($_SESSION['adminUserGroups'], "/ *; */", "\t\t\t\t\t\t", false);
if (!empty($currentGroup)) // if the current SQL query contains a 'WHERE' clause that searches for a particular user group
$optionTags = preg_replace("/<option>(?=$currentGroup<\\/option>)/i", "<option selected>", $optionTags); // we select that group by adding the 'selected' parameter to the appropriate <option> tag
$groupSearchForm .= $optionTags;
}
else
$groupSearchForm .= "<option>($loc[NoGroupsAvl])</option>";
$groupSearchForm .= <<<EOF
</select>
</div>
<div id="groupSubmit">
<input type="submit" value="$loc[ButtonTitle_Show]" title="$groupSearchButtonTitle"$groupSearchDisabled>
</div>
</fieldset>
</form>
EOF;
}
else
$groupSearchForm = "";
return $groupSearchForm;
}
// --------------------------------------------------------------------
// BUILD DISPLAY OPTIONS FORM ELEMENTS
// (i.e., provide options to show/hide columns or change the number of records displayed per page)
function buildDisplayOptionsElements($href, $queryURL, $showQuery, $showLinks, $rowOffset, $showRows, $citeStyle, $citeOrder, $dropDownFieldsArray, $dropDownFieldSelected, $fieldsToDisplay, $displayType, $headerMsg)
{
global $loc; // '$loc' is made globally available in 'core.php'
global $client;
if ($displayType == "Browse")
{
$submitValue = $loc["ButtonTitle_Browse"];
$submitTitle = $loc["DescriptionShowButtonDisplayOptionsFormBrowseView"];
$selectorDivID = "optShowHideField";
$selectorID = "displayOptionsSelector";
$selectorLabel = $loc["Field"] . ":";
$selectorTitle = $loc["DescriptionSelectFieldDisplayOptionsFormBrowseView"];
$showRowsLabel = $loc["ShowRecordsPerPage_SuffixBrowseView"];
$showRowsTitle = $loc["DescriptionShowRecordsPerPageBrowseView"];
}
elseif ($displayType == "Cite")
{
$submitValue = $loc["ButtonTitle_Show"];
$submitTitle = $loc["DescriptionShowButtonDisplayOptionsFormCiteView"];
$selectorDivID = "optCiteStyle";
$selectorID = "citeStyle";
$selectorLabel = $loc["Style"] . ":";
$selectorTitle = $loc["DescriptionSelectStyleDisplayOptionsFormCiteView"];
$showRowsLabel = $loc["ShowRecordsPerPage_SuffixCiteView"];
$showRowsTitle = $loc["DescriptionShowRecordsPerPage"];
}
elseif ($displayType == "Display")
{
$submitValue = $loc["ButtonTitle_Show"];
$submitTitle = $loc["DescriptionShowButtonDisplayOptionsFormDetailsView"];
$selectorDivID = "optShowHideField";
$selectorID = "displayOptionsSelector";
$selectorLabel = $loc["Field"] . ":";
$selectorTitle = $loc["DescriptionSelectFieldDisplayOptionsFormDetailsView"];
$showRowsLabel = $loc["ShowRecordsPerPage_Suffix"];
$showRowsTitle = $loc["DescriptionShowRecordsPerPage"];
}
else
{
$submitValue = $loc["ButtonTitle_Show"];
$submitTitle = $loc["DescriptionShowButtonDisplayOptionsForm"];
$selectorDivID = "optShowHideField";
$selectorID = "displayOptionsSelector";
$selectorLabel = $loc["Field"] . ":";
$selectorTitle = $loc["DescriptionSelectFieldDisplayOptionsForm"];
$showRowsLabel = $loc["ShowRecordsPerPage_Suffix"];
$showRowsTitle = $loc["DescriptionShowRecordsPerPage"];
}
if (($displayType != "Cite") AND ($fieldsToDisplay < 2))
{
$hideButtonDisabled = " disabled"; // disable the 'Hide' button if there's currently only one field being displayed (except the links column)
$hideButtonTitle = "(" . $loc["DescriptionHideButtonDisplayOptionsFormOnlyOneField"] . ")";
}
else
{
$hideButtonDisabled = "";
if ($displayType == "Display")
$hideButtonTitle = $loc["DescriptionHideButtonDisplayOptionsFormDetailsView"];
else
$hideButtonTitle = $loc["DescriptionHideButtonDisplayOptionsForm"];
}
if (($displayType == "Cite") AND (!isset($_SESSION['user_styles'])))
$citeStyleDisabled = " disabled"; // for Citation view, disable the style popup if the session variable holding the user's styles isn't available
else
$citeStyleDisabled = "";
$encodedCiteStyle = rawurlencode($citeStyle);
$encodedClient = rawurlencode($client);
$encodedHeaderMsg = rawurlencode($headerMsg);
$accessKeyAttribute = addAccessKey("attribute", "max_rows");
$accessKeyTitle = addAccessKey("title", "max_rows");
// NOTE: we embed the current value of '$rowOffset' as hidden tag within the 'displayOptions' form. By this, the current row offset can be re-applied after the user pressed the 'Show'/'Hide' button within the 'displayOptions' form.
// To avoid that browse links don't behave as expected, the actual value of '$rowOffset' will be adjusted in function 'seekInMySQLResultsToOffset()' to an exact multiple of '$showRows'!
$displayOptionsForm = <<<EOF
<form action="$href" method="GET" name="displayOptions">
<fieldset>
<legend>$loc[DisplayOptions]:</legend>
<input type="hidden" name="formType" value="displayOptions">
<input type="hidden" name="submit" value="$submitValue">
<input type="hidden" name="originalDisplayType" value="$displayType">
<input type="hidden" name="sqlQuery" value="$queryURL">
<input type="hidden" name="showQuery" value="$showQuery">
<input type="hidden" name="showLinks" value="$showLinks">
<input type="hidden" name="rowOffset" value="$rowOffset">
<input type="hidden" name="showRows" value="$showRows">
<input type="hidden" name="client" value="$encodedClient">
<input type="hidden" name="citeStyle" value="$encodedCiteStyle">
<input type="hidden" name="citeOrder" value="$citeOrder">
<input type="hidden" name="headerMsg" value="$encodedHeaderMsg">
<div id="optMain">
<div id="$selectorDivID">
<label for="$selectorID">$selectorLabel</label>
<select id="$selectorID" name="$selectorID" title="$selectorTitle"$citeStyleDisabled>
EOF;
// build correct option tags from the column items provided:
$optionTags = buildSelectMenuOptions($dropDownFieldsArray, "//", "\t\t\t\t\t\t\t", true);
$optionTags = preg_replace("/<option([^>]* value=\"$dropDownFieldSelected\")/", "<option\\1 selected", $optionTags); // add 'selected' attribute
$displayOptionsForm .= $optionTags;
$displayOptionsForm .= <<<EOF
</select>
</div>
EOF;
$displayOptionsForm .= <<<EOF
<div id="optSubmit">
<input type="submit" name="submit" value="$submitValue" title="$submitTitle">
EOF;
if (!preg_match("/^(Browse|Cite)$/i", $displayType))
$displayOptionsForm .= "\n\t\t\t\t\t\t<input type=\"submit\" name=\"submit\" value=\"" . $loc["ButtonTitle_Hide"] . "\" title=\"$hideButtonTitle\"$hideButtonDisabled>";
$displayOptionsForm .= <<<EOF
</div>
</div>
<div id="optOther">
EOF;
if ($displayType == "Cite")
{
$displayOptionsForm .= <<<EOF
<div id="optCiteOrder">
<label for="citeOrder">$loc[SortBy]:</label>
<select id="citeOrder" name="citeOrder" title="$loc[DescriptionSelectOrderDisplayOptionsFormCiteView]">
EOF;
// build correct option tags for the "Sort by" ('citeOrder') dropdown menu (and select the currently chosen option):
$citeOrderItemsArray = array("author" => $loc["DropDownFieldName_Author"],
"year" => $loc["DropDownFieldName_Year"],
"type" => $loc["DropDownFieldName_Type"],
"type-year" => $loc["DropDownFieldName_TypeYear"],
"creation-date" => $loc["DropDownFieldName_CreationDate"]);
$citeOrderOptionTags = buildSelectMenuOptions($citeOrderItemsArray, "//", "\t\t\t\t\t\t\t", true);
if (isset($citeOrderItemsArray[$citeOrder]))
$citeOrderOptionTags = preg_replace("/<option([^>]*)>(" . $citeOrderItemsArray[$citeOrder] . ")<\\/option>/", "<option\\1 selected>\\2</option>", $citeOrderOptionTags); // add 'selected' attribute to the currently chosen 'citeOrder' option
else // add & select a "(custom order)" option (which indicates that the current sort order matches none of the above 'citeOrder' options):
$citeOrderOptionTags = "\n\t\t\t\t\t\t\t<option value=\"\" selected>(" . $loc["DropDownFieldName_Custom"] . ")</option>" . $citeOrderOptionTags;
$displayOptionsForm .= $citeOrderOptionTags;
$displayOptionsForm .= <<<EOF
</select>
</div>
EOF;
}
$displayOptionsForm .= <<<EOF
<div id="optRecsPerPage">
<input type="text" id="showRows" name="showRows" value="$showRows" size="4"$accessKeyAttribute title="$showRowsTitle$accessKeyTitle">
<label for="showRows">$showRowsLabel</label>
</div>
</div>
</fieldset>
</form>
EOF;
return $displayOptionsForm;
}
// --------------------------------------------------------------------
// BUILD SUGGEST ELEMENTS
// (i.e., provide HTML elements that will generate auto-completions or search suggestions for text entered by the user in text entry fields)
// requires the Prototype & script.aculo.us JavaScript frameworks: <http://www.prototypejs.org/> and <http://script.aculo.us/>
// more info about 'Ajax.Autocompleter': <http://github.com/madrobby/scriptaculous/wikis/ajax-autocompleter>
//
// NOTE: I don't know how to pass custom values (such as the CQL index) to the callback function. Therefore, I'm using a dirty hack here where I add
// '$CQLIndex' (i.e. an "id-" prefix plus the ID of the HTML form element that contains the selected field) at the beginning of the query parameter
// ('paramName'). This ID is required by the callback function to fetch the name of the currently selected refbase field.
function buildSuggestElements($searchFieldID, $searchSuggestionsID, $suggestProgressID, $CQLIndex, $prefix = "\t\t", $tokens = "''", $frequency = 0.8, $minChars = 2, $callBack = "addCQLIndex", $suggestURL = "opensearch.php", $paramName = "query", $parameters = "operation=suggest&recordSchema=html")
{
global $contentTypeCharset; // defined in 'ini.inc.php'
$suggestElements = <<<EOF
$prefix<span id="$suggestProgressID" class="suggestProgress" style="display:none;">...</span>
$prefix<div id="$searchSuggestionsID" class="searchSuggestions" style="display:none;"></div>
$prefix<script language="JavaScript" type="text/javascript" charset="$contentTypeCharset">
$prefix// <![CDATA[
$prefix new Ajax.Autocompleter('$searchFieldID','$searchSuggestionsID','$suggestURL',{tokens:$tokens,frequency:$frequency,minChars:$minChars,indicator:'$suggestProgressID',paramName:'$CQLIndex$paramName',parameters:'$parameters',callback:$callBack});
$prefix// ]]>
$prefix</script>
EOF;
return $suggestElements;
}
// --------------------------------------------------------------------
// Build the database query from user input provided by the "Search within Results" or "Display Options" forms
// above the query results list (which, in turn, was returned by 'search.php' or 'users.php', respectively):
// TODO: - build the complete SQL query using functions 'buildFROMclause()' and 'buildORDERclause()'
function extractFormElementsRefineDisplay($queryTable, $displayType, $originalDisplayType, $query, $showLinks, $citeOrder, $userID)
{
global $tableRefs, $tableUserData, $tableUsers; // defined in 'db.inc.php'
global $loc; // '$loc' is made globally available in 'core.php'
$encodedDisplayType = encodeHTML($displayType); // note that we need to HTML encode '$displayType' for comparison with the HTML encoded locales
// extract form variables:
if ($encodedDisplayType == $loc["ButtonTitle_Search"]) // the user clicked the 'Search' button of the "Search within Results" form
{
$fieldSelector = $_REQUEST['refineSearchSelector']; // extract field name chosen by the user
$refineSearchName = $_REQUEST['refineSearchName']; // extract search text entered by the user
if (isset($_REQUEST['refineSearchExclude'])) // extract user option whether matched records should be included or excluded
$refineSearchActionCheckbox = $_REQUEST['refineSearchExclude']; // the user marked the checkbox next to "Exclude matches"
else
$refineSearchActionCheckbox = "0"; // the user did NOT mark the checkbox next to "Exclude matches"
}
elseif (preg_match("/^(" . $loc["ButtonTitle_Show"] . "|" . $loc["ButtonTitle_Hide"] . "|" . $loc["ButtonTitle_Browse"] . ")$/", $encodedDisplayType)) // the user clicked either the 'Browse' or 'Show'/'Hide' buttons of the "Display Options" form
// (hitting <enter> within the 'ShowRows' text entry field of the "Display Options" form will act as if the user clicked the 'Browse'/'Show' button)
{
if (isset($_REQUEST['displayOptionsSelector']))
$fieldSelector = $_REQUEST['displayOptionsSelector']; // extract field name chosen by the user
else
$fieldSelector = "";
}
else
$fieldSelector = ""; // this avoids 'Undefined variable...' messages when a user has changed the language setting on the options page, and then reloads an existing page (whose URL still has a 'submit' value in the previously used language)
// extract the fields of the SELECT clause from the current SQL query:
$previousSelectClause = extractSELECTclause($query);
// ensure to add any required fields to the SELECT clause:
if ($queryTable == $tableRefs) // 'search.php':
$addRequiredFields = true;
elseif ($queryTable == $tableUsers) // 'users.php':
$addRequiredFields = false; // we'll add any required fields to the 'users.php' SELECT clause below
// TODO: this wouldn't be necessary if function 'buildSELECTclause()' would handle the requirements of 'users.php'
$additionalFields = "";
if ($encodedDisplayType == $loc["ButtonTitle_Search"])
{
// rebuild the current SELECT clause:
$newSelectClause = buildSELECTclause($originalDisplayType, $showLinks, $additionalFields, false, $addRequiredFields, $previousSelectClause);
// replace current SELECT clause:
$query = newSELECTclause($newSelectClause, $query, false);
if ($refineSearchName != "") // if the user typed a search string into the text entry field...
{
// Depending on the chosen output action, construct an appropriate SQL query:
if ($refineSearchActionCheckbox == "0") // if the user did NOT mark the checkbox next to "Exclude matches"
{
// for the fields 'marked=no', 'copy=false' and 'selected=no', force NULL values to be matched:
if (($fieldSelector == "marked" AND $refineSearchName == "no") OR ($fieldSelector == "copy" AND $refineSearchName == "false") OR ($fieldSelector == "selected" AND $refineSearchName == "no"))
$query = preg_replace("/ WHERE /i", " WHERE ($fieldSelector RLIKE " . quote_smart($refineSearchName) . " OR $fieldSelector IS NULL) AND ", $query); // ...add search field name & value to the SQL query
else // add default 'WHERE' clause:
$query = preg_replace("/ WHERE /i", " WHERE $fieldSelector RLIKE " . quote_smart($refineSearchName) . " AND ", $query); // ...add search field name & value to the SQL query
}
else // $refineSearchActionCheckbox == "1" // if the user marked the checkbox next to "Exclude matches"
{
$query = preg_replace("/ WHERE /i", " WHERE ($fieldSelector NOT RLIKE " . quote_smart($refineSearchName) . " OR $fieldSelector IS NULL) AND ", $query); // ...add search field name & value to the SQL query
}
$query = preg_replace('/ AND serial RLIKE "\.\+"/i', '', $query); // remove any 'AND serial RLIKE ".+"' which isn't required anymore
}
// else, if the user did NOT type a search string into the text entry field, we simply keep the old WHERE clause...
}
elseif (preg_match("/^(" . $loc["ButtonTitle_Show"] . "|" . $loc["ButtonTitle_Hide"] . ")$/", $encodedDisplayType)) // the user clicked the 'Show'/'Hide' buttons of the "Display Options" form
{
if (preg_match("/^Cite$/i", $originalDisplayType)) // in case of Citation view, we regenerate the SELECT clause from scratch:
{
// generate a SELECT clause that's appropriate for Citation view (or Details view):
$newSelectClause = buildSELECTclause($originalDisplayType, $showLinks, $additionalFields, false, $addRequiredFields);
// rebuild the current ORDER clause:
if (preg_match("/^(author|year|type|type-year|creation-date)$/i", $citeOrder))
{
if ($citeOrder == "year") // sort records first by year (descending):
$newORDER = "ORDER BY year DESC, first_author, author_count, author, title";
elseif ($citeOrder == "type") // sort records first by record type and thesis type (descending):
$newORDER = "ORDER BY type DESC, thesis DESC, first_author, author_count, author, year, title";
elseif ($citeOrder == "type-year") // sort records first by record type and thesis type (descending), then by year (descending):
$newORDER = "ORDER BY type DESC, thesis DESC, year DESC, first_author, author_count, author, title";
elseif ($citeOrder == "creation-date") // sort records such that newly added/edited records get listed top of the list:
$newORDER = "ORDER BY created_date DESC, created_time DESC, modified_date DESC, modified_time DESC, serial DESC";
elseif ($citeOrder == "author") // supply the default ORDER BY pattern (which is suitable for citation in a journal etc.):
$newORDER = "ORDER BY first_author, author_count, author, year, title";
// replace current ORDER clause:
$query = newORDERclause($newORDER, $query, false);
}
// else if any other or no '$citeOrder' parameter is specified, we keep the current ORDER BY clause
// NOTE: this behaviour is different from functions 'extractFormElementsQueryResults()' and 'extractFormElementsExtract()'
// where we always use 'ORDER BY first_author, author_count, author, year, title' as default ORDER BY clause
// (to ensure correct sorting for output to bibliographic reference lists)
}
elseif (preg_match("/^Display$/i", $originalDisplayType)) // Details view
{
// NOTE: the below code for displaying & hiding of fields in Details view must be adopted if either layout or field names are changed!
$fieldsList = "";
if ($fieldSelector == "all fields")
{
// generate a SELECT clause that shows all fields in Details view:
$newSelectClause = buildSELECTclause($originalDisplayType, $showLinks, $additionalFields, true, $addRequiredFields);
}
else // add (or remove) the chosen fields from the SELECT clause:
{
if ($encodedDisplayType == $loc["ButtonTitle_Show"]) // if the user clicked the 'Show' button, add the chosen fields to the SELECT clause:
{
$matchField = "pages";
if ($fieldSelector == "keywords, abstract")
{
$fieldsList = ", keywords, abstract";
}
elseif ($fieldSelector == "additional fields")
{
$fieldsList = ", address, corporate_author, thesis, publisher, place, editor, language, summary_language, orig_title, series_editor, series_title, abbrev_series_title, series_volume, series_issue, edition, issn, isbn, medium, area, expedition, conference, notes, approved";
if (isset($_SESSION['loginEmail']))
$fieldsList .= ", location"; // we only add the 'location' field if the user is logged in
if (preg_match("/\babstract\b/i", $previousSelectClause))
$matchField = "abstract";
}
elseif ($fieldSelector == "my fields")
{
$fieldsList = ", marked, copy, selected, user_keys, user_notes, user_file, user_groups, cite_key";
if (preg_match("/\bserial\b/i", $previousSelectClause))
$matchField = "serial";
elseif (preg_match("/\babstract\b/i", $previousSelectClause))
$matchField = "abstract";
}
if ((!empty($fieldsList)) AND (!preg_match("/\b" . $fieldsList . "\b/i", $previousSelectClause))) // if none of the chosen fields are currently displayed...
$previousSelectClause = preg_replace("/(?<=\b" . $matchField . "\b)/i", $fieldsList, $previousSelectClause); // ...add the chosen fields to the current SELECT clause:
}
if ($encodedDisplayType == $loc["ButtonTitle_Hide"]) // if the user clicked the 'Hide' button, remove the chosen fields from the SELECT clause:
{
if ($fieldSelector == "keywords, abstract")
$fieldsList = "\b(keywords|abstract)\b";
elseif ($fieldSelector == "additional fields")
$fieldsList = "\b(corporate_author|thesis|address|publisher|place|editor|language|summary_language|orig_title|series_editor|series_title|abbrev_series_title|series_volume|series_issue|edition|issn|isbn|medium|area|expedition|conference|notes|approved|location)\b";
elseif ($fieldSelector == "my fields")
$fieldsList = "\b(marked|copy|selected|user_keys|user_notes|user_file|user_groups|cite_key)\b";
if ((!empty($fieldsList)) AND (preg_match("/\b" . $fieldsList . "\b/i", $previousSelectClause))) // ...and any of the chosen fields *are* currently displayed...
{
// ...remove the chosen fields from the fields given in the current SELECT clause:
$previousSelectClause = preg_replace("/ *, *" . $fieldsList . " */i", "", $previousSelectClause); // all columns except the first
$previousSelectClause = preg_replace("/ *" . $fieldsList . " *, */i", "", $previousSelectClause); // all columns except the last
}
}
// rebuild the current SELECT clause, but include (or exclude) the chosen fields:
$newSelectClause = buildSELECTclause($originalDisplayType, $showLinks, $additionalFields, false, $addRequiredFields, $previousSelectClause);
}
}
else // otherwise, i.e. for List view, add (or remove) the chosen field from the SELECT clause:
{
if ($encodedDisplayType == $loc["ButtonTitle_Show"]) // if the user clicked the 'Show' button...
{
if (!preg_match("/\b" . $fieldSelector . "\b/i", $previousSelectClause)) // ...and the chosen field is *not* already displayed...
$additionalFields = $fieldSelector; // ...add the chosen field to the current SELECT clause
}
elseif ($encodedDisplayType == $loc["ButtonTitle_Hide"]) // if the user clicked the 'Hide' button...
{
if (preg_match("/\b" . $fieldSelector . "\b/i", $previousSelectClause)) // ...and the chosen field *is* currently displayed...
{
// ...remove the chosen field from the fields given in the current SELECT clause:
$previousSelectClause = preg_replace("/ *, *\b" . $fieldSelector . "\b */i", "", $previousSelectClause); // all columns except the first
$previousSelectClause = preg_replace("/ *\b" . $fieldSelector . "\b *, */i", "", $previousSelectClause); // all columns except the last
}
}
// rebuild the current SELECT clause, but include (or exclude) the chosen field:
$newSelectClause = buildSELECTclause("", $showLinks, $additionalFields, false, $addRequiredFields, $previousSelectClause);
}
// replace current SELECT clause:
$query = newSELECTclause($newSelectClause, $query, false);
}
// TODO: don't manipulate the SQL query in '$query' directly, but instead use functions 'extractSELECTclause()' and 'buildSELECTclause()' (similar as above)
elseif ($encodedDisplayType == $loc["ButtonTitle_Browse"]) // if the user clicked the 'Browse' button within the "Display Options" form...
{
$previousField = preg_replace("/^SELECT (\w+).+/i", "\\1", $query); // extract the field that was previously used in Browse view
if (!preg_match("/^" . $fieldSelector . "$/i", $previousField)) // if the user did choose another field in Browse view...
{
// ...modify the SQL query to show a summary for the new field that was chosen by the user:
// (NOTE: these replace patterns aren't 100% safe and may fail if the user has modified the query using 'sql_search.php'!)
$query = preg_replace("/^SELECT $previousField/i", "SELECT $fieldSelector", $query); // use the field that was chosen by the user for Browse view
$query = preg_replace("/GROUP BY $previousField/i", "GROUP BY $fieldSelector", $query); // group data by the field that was chosen by the user
$query = preg_replace("/ORDER BY( records( DESC)?,)? $previousField/i", "ORDER BY\\1 $fieldSelector", $query); // order data by the field that was chosen by the user
}
}
// re-establish the original display type:
// (resetting '$displayType' to its original value is required for Browse view; for List view, it does also correct incorrect
// display types such as 'Search' or 'Show'/'Hide' which stem from the submit buttons in the forms of the results header)
$displayType = $originalDisplayType;
// the following changes to the SQL query are performed for both forms ("Search within Results" and "Display Options"):
if ($queryTable == $tableRefs) // 'search.php':
{
// if the chosen field is one of the user-specific fields from table 'user_data': 'marked', 'copy', 'selected', 'user_keys', 'user_notes', 'user_file', 'user_groups', 'cite_key' or 'related'
if (preg_match("/^(marked|copy|selected|user_keys|user_notes|user_file|user_groups|cite_key|related|my fields)$/i", $fieldSelector)) // 'my fields' is used in Details view as an alias for all user-specific fields
if (!preg_match("/LEFT JOIN $tableUserData/i", $query)) // ...and if the 'LEFT JOIN...' statement isn't already part of the 'FROM' clause...
$query = preg_replace("/ FROM $tableRefs/i", " FROM $tableRefs LEFT JOIN $tableUserData ON serial = record_id AND user_id = $userID", $query); // ...add the 'LEFT JOIN...' part to the 'FROM' clause
}
elseif ($queryTable == $tableUsers) // 'users.php':
{
// TODO: this wouldn't be necessary if function 'buildSELECTclause()' would handle the requirements of 'users.php' (see also above)
$query = preg_replace("/ FROM $tableUsers/i", ", user_id FROM $tableUsers", $query); // add 'user_id' column (although it won't be visible the 'user_id' column gets included in every search query)
// (which is required in order to obtain unique checkbox names as well as for use in the 'getUserID()' function)
}
return array($query, $displayType);
}
// --------------------------------------------------------------------
// SPLIT AND MERGE AGAIN
// This function takes a string and splits it on '$splitDelim' into an array,
// then re-joins the pieces inserting '$joinDelim' as separator).
// Note: The split pattern must be specified as perl-style regular expression
// (including the leading & trailing slashes) and may include mode
// modifiers (such as '/.../i' to perform a case insensitive match)
function splitAndMerge($splitDelim, $joinDelim, $sourceString)
{
// split the string on the specified delimiter (which is interpreted as perl-style regular expression!):
$piecesArray = preg_split($splitDelim, $sourceString);
// re-join the array with the specified separator:
$newString = implode($joinDelim, $piecesArray);
return $newString;
}
// --------------------------------------------------------------------
// EXTRACT PARTS FROM STRING
// This function takes a '$sourceString', splits it on '$splitDelim' and returns
// x parts from the beginning (if x > 0) or the end (if x < 0) of the string
// (x must be given in '$returnParts'); parts will be returned as a merged string
// using '$joinDelim' as delimiter.
// Note: The split pattern must be specified as perl-style regular expression
// (including the leading & trailing slashes) and may include mode
// modifiers (such as '/.../i' to perform a case insensitive match)
function extractPartsFromString($sourceString, $splitDelim, $joinDelim, $returnParts)
{
// split the string on the specified delimiter (which is interpreted as perl-style regular expression!):
$piecesArray = preg_split($splitDelim, $sourceString);
if ($returnParts > 0)
$spliceFromElementNo = 0; // splice from beginning of array
else // ($returnParts < 0)
{
$spliceFromElementNo = $returnParts; // splice from end of array
$returnParts = abs($returnParts);
}
// extracts parts from array:
$extractedPiecesArray = array_splice($piecesArray, $spliceFromElementNo, $returnParts); // 'array_splice()' returns array with extracted elements
// re-join the array with the specified separator:
$newString = implode($joinDelim, $extractedPiecesArray);
return $newString;
}
// --------------------------------------------------------------------
// RE-ARRANGE AUTHOR FIELD CONTENTS
// (this function separates contents of the author field into their functional parts, i.e.:
// {
// {author_name}, {author_initial(s)}
// }
// {
// {author_name}, {author_initial(s)}
// }
// {
// ...
// }
// then, these functional pieces will be joined again according to the separators specified)
// Note: this function assumes that:
// - within one author object, there's only *one* delimiter separating author name & initials!
// Note: The split pattern must be specified as perl-style regular expression
// (including the leading & trailing slashes) and may include mode
// modifiers (such as '/.../i' to perform a case insensitive match)
function reArrangeAuthorContents($authorContents, $familyNameFirst, $oldBetweenAuthorsDelim, $newBetweenAuthorsDelimStandard, $newBetweenAuthorsDelimLastAuthor, $oldAuthorsInitialsDelim, $newAuthorsInitialsDelimFirstAuthor, $newAuthorsInitialsDelimStandard, $betweenInitialsDelim, $initialsBeforeAuthorFirstAuthor, $initialsBeforeAuthorStandard, $shortenGivenNames, $numberOfAuthorsTriggeringEtAl, $includeNumberOfAuthors, $customStringAfterFirstAuthors, $encodeHTML)
{
global $alnum, $alpha, $cntrl, $dash, $digit, $graph, $lower, $print, $punct, $space, $upper, $word, $patternModifiers; // defined in 'transtab_unicode_charset.inc.php' and 'transtab_latin1_charset.inc.php'
$authorsArray = preg_split($oldBetweenAuthorsDelim, $authorContents); // get a list of all authors for this record
$authorCount = count($authorsArray); // check how many authors we have to deal with
$newAuthorContents = ""; // this variable will hold the final author string
$includeStringAfterFirstAuthor = false;
if (empty($numberOfAuthorsTriggeringEtAl))
$numberOfAuthorsTriggeringEtAl = $authorCount;
if (empty($includeNumberOfAuthors))
$includeNumberOfAuthors = $authorCount;
for ($i=0; $i < $authorCount; $i++)
{
$singleAuthorArray = preg_split($oldAuthorsInitialsDelim, $authorsArray[$i]); // for each author, extract author name & initials to separate list items
if (!$familyNameFirst) // if the family name comes *after* the given name (or initials) in the source string, put array elements in reverse order:
$singleAuthorArray = array_reverse($singleAuthorArray); // (Note: this only works, if the array has only *two* elements, i.e., one containing the author's name and one holding the initials!)
if (isset($singleAuthorArray[1]))
{
if ($shortenGivenNames) // if we're supposed to abbreviate given names
{
// within initials, reduce all full first names (-> defined by a starting uppercase character, followed by one ore more lowercase characters)
// to initials, i.e., only retain their first character
$singleAuthorArray[1] = preg_replace("/([$upper])[$lower]+/$patternModifiers", "\\1", $singleAuthorArray[1]);
}
// within initials, remove any dots:
$singleAuthorArray[1] = preg_replace("/([$upper])\.+/$patternModifiers", "\\1", $singleAuthorArray[1]);
// within initials, remove any spaces *between* initials:
$singleAuthorArray[1] = preg_replace("/(?<=[-$upper]) +(?=[-$upper])/$patternModifiers", "", $singleAuthorArray[1]);
// within initials, add a space after a hyphen, but only if ...
if (preg_match("/ $/", $betweenInitialsDelim)) // ... the delimiter that separates initials ends with a space
$singleAuthorArray[1] = preg_replace("/-(?=[$upper])/$patternModifiers", "- ", $singleAuthorArray[1]);
// then, separate initials with the specified delimiter:
$singleAuthorArray[1] = preg_replace("/([$upper])(?=[^$lower]+|$)/$patternModifiers", "\\1$betweenInitialsDelim", $singleAuthorArray[1]);
}
if ((($i == 0) AND $initialsBeforeAuthorFirstAuthor) OR (($i > 0) AND $initialsBeforeAuthorStandard)) // put array elements in reverse order:
$singleAuthorArray = array_reverse($singleAuthorArray); // (Note: this only works, if the array has only *two* elements, i.e., one containing the author's name and one holding the initials!)
// re-join author name & initials, using the specified delimiter, and copy the string to the end of an array:
if ($i == 0) // -> first author
$singleAuthorString = implode($newAuthorsInitialsDelimFirstAuthor, $singleAuthorArray);
else // $i > 0 // -> all authors except the first one
$singleAuthorString = implode($newAuthorsInitialsDelimStandard, $singleAuthorArray);
// append this author to the final author string:
if (($i == 0) OR ($i + 1) < $authorCount) // -> first author, or (for multiple authors) all authors except the last one
{
if ($i == 0) // -> first author
$newAuthorContents .= $singleAuthorString;
else // -> for multiple authors, all authors except the first or the last one
$newAuthorContents .= $newBetweenAuthorsDelimStandard . $singleAuthorString;
// we'll append the string in '$customStringAfterFirstAuthors' to the number of authors given in '$includeNumberOfAuthors' if the total number of authors is greater than the number given in '$numberOfAuthorsTriggeringEtAl':
if ((($i + 1) == $includeNumberOfAuthors) AND ($authorCount > $numberOfAuthorsTriggeringEtAl))
{
if (preg_match("/__NUMBER_OF_AUTHORS__/", $customStringAfterFirstAuthors))
$customStringAfterFirstAuthors = preg_replace("/__NUMBER_OF_AUTHORS__/", ($authorCount - $includeNumberOfAuthors), $customStringAfterFirstAuthors); // resolve placeholder
$includeStringAfterFirstAuthor = true;
break;
}
}
elseif (($authorCount > 1) AND (($i + 1) == $authorCount)) // -> last author (if multiple authors)
{
$newAuthorContents .= $newBetweenAuthorsDelimLastAuthor . $singleAuthorString;
}
}
// do some final clean up:
if ($encodeHTML)
$newAuthorContents = encodeHTML($newAuthorContents); // HTML encode higher ASCII characters within the newly arranged author contents
if ($includeStringAfterFirstAuthor)
$newAuthorContents .= $customStringAfterFirstAuthors; // the custom string won't get HTML encoded so that it's possible to include HTML tags (such as '<i>') within the string
$newAuthorContents = preg_replace("/ +/", " ", $newAuthorContents); // remove double spaces (which occur e.g., when both, $betweenInitialsDelim & $newAuthorsInitialsDelim..., end with a space)
$newAuthorContents = preg_replace("/ +([,.;:?!()]|$)/", "\\1", $newAuthorContents); // remove excess spaces before [,.;:?!()] and from the end of the author string
return $newAuthorContents;
}
// --------------------------------------------------------------------
// EXTRACT AUTHOR'S LAST NAME
// This function takes the contents of the author field and will extract the
// last name of a particular author (specified by position) (e.g., setting
// '$authorPosition' to "1" will return the 1st author's last name).
// Note: this function assumes that:
// 1. within one author object, there's only *one* delimiter separating
// author name & initials!
// 2. author objects are stored in the db as
// "<author_name><author_initials_delimiter><author_initials>", i.e.,
// initials follow *after* the author's name!
// Required Parameters:
// 1. pattern describing delimiter that separates different authors
// 2. pattern describing delimiter that separates author name & initials
// (within one author)
// 3. position of the author whose last name shall be extracted (e.g.,
// "1" will return the 1st author's last name)
// 4. contents of the author field
// Note: The split patterns must be specified as perl-style regular expressions
// (including the leading & trailing slashes) and may include mode
// modifiers (such as '/.../i' to perform a case insensitive match)
function extractAuthorsLastName($oldBetweenAuthorsDelim, $oldAuthorsInitialsDelim, $authorPosition, $authorContents)
{
$authorsArray = preg_split($oldBetweenAuthorsDelim, $authorContents); // get a list of all authors for this record
$authorPosition = ($authorPosition-1); // php array elements start with "0", so we decrease the authors position by 1
$singleAuthor = $authorsArray[$authorPosition]; // for the author in question, extract the full author name (last name & initials)
$singleAuthorArray = preg_split($oldAuthorsInitialsDelim, $singleAuthor); // then, extract author name & initials to separate list items
$singleAuthorsLastName = $singleAuthorArray[0]; // extract this author's last name into a new variable
return $singleAuthorsLastName;
}
// --------------------------------------------------------------------
// EXTRACT AUTHOR'S GIVEN NAME
// This function takes the contents of the author field and will extract the
// initials/given name of a particular author (specified by position) (e.g.,
// setting '$authorPosition' to "1" will return the 1st author's initials/given
// name).
// Required Parameters:
// 1. pattern describing delimiter that separates different authors
// 2. pattern describing delimiter that separates author name &
// initials/given name (within one author)
// 3. position of the author whose last name shall be extracted
// (e.g., "1" will return the 1st author's initials/given name)
// 4. contents of the author field
// Note: The split patterns must be specified as perl-style regular expressions
// (including the leading & trailing slashes) and may include mode
// modifiers (such as '/.../i' to perform a case insensitive match)
function extractAuthorsGivenName($oldBetweenAuthorsDelim, $oldAuthorsInitialsDelim, $authorPosition, $authorContents)
{
$authorsArray = preg_split($oldBetweenAuthorsDelim, $authorContents); // get a list of all authors for this record
$authorPosition = ($authorPosition-1); // php array elements start with "0", so we decrease the authors position by 1
$singleAuthor = $authorsArray[$authorPosition]; // for the author in question, extract the full author name (last name & initials/given name)
$singleAuthorArray = preg_split($oldAuthorsInitialsDelim, $singleAuthor); // then, extract author name & initials/given name to separate list items
if (!empty($singleAuthorArray[1]))
$singleAuthorsGivenName = $singleAuthorArray[1]; // extract this author's initials/given name into a new variable
else
$singleAuthorsGivenName = '';
return $singleAuthorsGivenName;
}
// --------------------------------------------------------------------
// PARSE PLACEHOLDER STRING
// this function will parse a given placeholder string into its indiviual placeholders and replaces
// them with content from the given record
function parsePlaceholderString($formVars, $placeholderString, $fallbackPlaceholderString)
{
global $alnum, $alpha, $cntrl, $dash, $digit, $graph, $lower, $print, $punct, $space, $upper, $word, $patternModifiers; // defined in 'transtab_unicode_charset.inc.php' and 'transtab_latin1_charset.inc.php'
if (empty($placeholderString))
$placeholderString = $fallbackPlaceholderString; // if, for some odd reason, an empty placeholder string was given, we'll use the placeholder(s) given in '$fallbackPlaceholderString'
$placeholderPartsArray = preg_split("/[<>]/", $placeholderString); // split placeholder string into its individual components
$convertedPlaceholderArray = array(); // initialize array variable which will hold the transformed placeholder parts
foreach($placeholderPartsArray as $placeholderPart)
{
if (!empty($placeholderPart))
{
if (preg_match("/:\w+/", $placeholderPart)) // if the part contains a colon (":") followed by one or more word characters the part is assumed to be a placeholder (this will e.g. exclude "http://" strings)
{
// extract any custom options given within a placeholder:
if (preg_match("/:\w+\[[^][]+\]:/i", $placeholderPart)) // the placeholder contains custom options
$options = preg_replace("/.*:\w+(\[[^][]+\]):.*/i", "\\1", $placeholderPart);
else
$options = "";
// extract any prefix given within a placeholder:
if (preg_match("/^[^:]+:\w+(\[[^][]*\])?:/i", $placeholderPart)) // the placeholder contains a prefix
$prefix = preg_replace("/^([^:]+):\w+(\[[^][]*\])?:.*/i", "\\1", $placeholderPart);
else
$prefix = "";
// extract any suffix given within a placeholder:
if (preg_match("/:\w+(\[[^][]*\])?:[^:]+$/i", $placeholderPart)) // the placeholder contains a suffix
$suffix = preg_replace("/.*:\w+(?:\[[^][]*\])?:([^:]+)$/i", "\\1", $placeholderPart);
else
$suffix = "";
// call dedicated functions for the different placeholders (if required):
// '<:serial:>' placeholder:
if (preg_match("/:serial:/i", $placeholderPart))
$convertedPlaceholderArray[] = $prefix . $formVars['serialNo'] . $suffix;
// '<:firstAuthor:>' placeholder:
elseif (preg_match("/:firstAuthor:/i", $placeholderPart))
{
if (!empty($formVars['authorName'])) // if the 'author' field isn't empty
{
$firstAuthor = $prefix;
// Call the 'extractAuthorsLastName()' function to extract the last name of a particular author (specified by position):
// (see function header for description of required parameters)
$firstAuthor .= extractAuthorsLastName("/ *; */",
"/ *, */",
1,
$formVars['authorName']);
$firstAuthor .= $suffix;
$convertedPlaceholderArray[] = $firstAuthor;
}
}
// '<:secondAuthor:>' placeholder:
elseif (preg_match("/:secondAuthor:/i", $placeholderPart))
{
if (!empty($formVars['authorName']) AND preg_match("/;/", $formVars['authorName'])) // if the 'author' field does contain at least one ';' => at least two authors
{
$secondAuthor = $prefix;
// Call the 'extractAuthorsLastName()' function to extract the last name of a particular author (specified by position):
// (see function header for description of required parameters)
$secondAuthor .= extractAuthorsLastName("/ *; */",
"/ *, */",
2,
$formVars['authorName']);
$secondAuthor .= $suffix;
$convertedPlaceholderArray[] = $secondAuthor;
}
}
// '<:authors:>' placeholder:
elseif (preg_match("/:authors(\[[^][]*\])?:/i", $placeholderPart))
{
if (!empty($formVars['authorName'])) // if the 'author' field isn't empty
{
$authors = $prefix;
$authors .= extractDetailsFromAuthors($formVars['authorName'], $options);
$authors .= $suffix;
$convertedPlaceholderArray[] = $authors;
}
}
// '<:title:>' placeholder:
elseif (preg_match("/:title(\[[^][]*\])?:/i", $placeholderPart))
{
if (!empty($formVars['titleName'])) // if the 'title' field isn't empty
{
$title = $prefix;
$title .= extractDetailsFromField("title", $formVars['titleName'], "/[^-$word]+/$patternModifiers", $options);
$title .= $suffix;
$convertedPlaceholderArray[] = $title;
}
}
// '<:year:>' placeholder:
elseif (preg_match("/:year(\[[^][]*\])?:/i", $placeholderPart))
{
if (!empty($formVars['yearNo']) AND preg_match("/\d+/i", $formVars['yearNo'])) // if the 'year' field contains a number
{
$year = $prefix;
$year .= extractDetailsFromYear($formVars['yearNo'], $options);
$year .= $suffix;
$convertedPlaceholderArray[] = $year;
}
}
// '<:publication:>' placeholder:
elseif (preg_match("/:publication(\[[^][]*\])?:/i", $placeholderPart))
{
if (!empty($formVars['publicationName'])) // if the 'publication' field isn't empty
{
$publication = $prefix;
$publication .= extractDetailsFromField("publication", $formVars['publicationName'], "/[^-$word]+/$patternModifiers", $options);
$publication .= $suffix;
$convertedPlaceholderArray[] = $publication;
}
}
// '<:abbrevJournal:>' placeholder:
elseif (preg_match("/:abbrevJournal(\[[^][]*\])?:/i", $placeholderPart))
{
if (!empty($formVars['abbrevJournalName'])) // if the 'abbrev_journal' field isn't empty
{
$abbrevJournal = $prefix;
$abbrevJournal .= extractDetailsFromField("abbrev_journal", $formVars['abbrevJournalName'], "/[^-$word]+/$patternModifiers", $options);
$abbrevJournal .= $suffix;
$convertedPlaceholderArray[] = $abbrevJournal;
}
}
// '<:volume:>' placeholder:
elseif (preg_match("/:volume:/i", $placeholderPart))
{
if (!empty($formVars['volumeNo'])) // if the 'volume' field isn't empty
$convertedPlaceholderArray[] = $prefix . $formVars['volumeNo'] . $suffix;
}
// '<:issue:>' placeholder:
elseif (preg_match("/:issue:/i", $placeholderPart))
{
if (!empty($formVars['issueNo'])) // if the 'issue' field isn't empty
$convertedPlaceholderArray[] = $prefix . $formVars['issueNo'] . $suffix;
}
// '<:pages:>' placeholder:
elseif (preg_match("/:pages:/i", $placeholderPart))
{
if (!empty($formVars['pagesNo'])) // if the 'pages' field isn't empty
$convertedPlaceholderArray[] = $prefix . $formVars['pagesNo'] . $suffix;
}
// '<:startPage:>' placeholder:
elseif (preg_match("/:startPage:/i", $placeholderPart))
{
if (!empty($formVars['pagesNo']) AND preg_match("/\d+/i", $formVars['pagesNo'])) // if the 'pages' field contains a number
{
$startPage = $prefix;
$startPage .= preg_replace("/^\D*?(\w*\d+\w*).*/i", "\\1", $formVars['pagesNo']); // extract starting page
$startPage .= $suffix;
$convertedPlaceholderArray[] = $startPage;
}
}
// '<:endPage:>' placeholder:
elseif (preg_match("/:endPage:/i", $placeholderPart))
{
if (!empty($formVars['pagesNo']) AND preg_match("/\d+/i", $formVars['pagesNo'])) // if the 'pages' field contains a number
{
$pages = preg_replace("/^\D*?(\w*\d+\w*)( *[$dash]+ *\w*\d+\w*)?.*/i$patternModifiers", "\\1\\2", $formVars['pagesNo']); // extract page range (if there's any), otherwise just the first number
$endPage = $prefix;
$endPage .= extractDetailsFromField("pages", $pages, "/\D+/", "[-1]"); // we'll use this function instead of just grabbing a matched regex pattern since it'll also work when just a number but no range is given (e.g. when startPage = endPage)
$endPage .= $suffix;
$convertedPlaceholderArray[] = $endPage;
}
}
// '<:keywords:>' placeholder:
elseif (preg_match("/:keywords(\[[^][]*\])?:/i", $placeholderPart))
{
if (!empty($formVars['keywordsName'])) // if the 'keywords' field isn't empty
{
$keywords = $prefix;
$keywords .= extractDetailsFromField("keywords", $formVars['keywordsName'], "/ *[;,] */", $options);
$keywords .= $suffix;
$convertedPlaceholderArray[] = $keywords;
}
}
// '<:issn:>' placeholder:
elseif (preg_match("/:issn:/i", $placeholderPart))
{
if (!empty($formVars['issnName']))
$convertedPlaceholderArray[] = $prefix . $formVars['issnName'] . $suffix;
}
// '<:isbn:>' placeholder:
elseif (preg_match("/:isbn:/i", $placeholderPart))
{
if (!empty($formVars['isbnName']))
$convertedPlaceholderArray[] = $prefix . $formVars['isbnName'] . $suffix;
}
// '<:issn_isbn:>' placeholder:
elseif (preg_match("/:issn_isbn:/i", $placeholderPart))
{
if (!empty($formVars['issnName'])) // if both, an ISSN and ISBN number are present, the ISSN number will be preferred
$convertedPlaceholderArray[] = $prefix . $formVars['issnName'] . $suffix;
elseif (!empty($formVars['isbnName']))
$convertedPlaceholderArray[] = $prefix . $formVars['isbnName'] . $suffix;
}
// '<:area:>' placeholder:
elseif (preg_match("/:area(\[[^][]*\])?:/i", $placeholderPart))
{
if (!empty($formVars['areaName'])) // if the 'area' field isn't empty
{
$area = $prefix;
$area .= extractDetailsFromField("area", $formVars['areaName'], "/ *[;,] */", $options);
$area .= $suffix;
$convertedPlaceholderArray[] = $area;
}
}
// '<:notes:>' placeholder:
elseif (preg_match("/:notes(\[[^][]*\])?:/i", $placeholderPart))
{
if (!empty($formVars['notesName'])) // if the 'notes' field isn't empty
{
$notes = $prefix;
$notes .= extractDetailsFromField("notes", $formVars['notesName'], "/[^-$word]+/$patternModifiers", $options);
$notes .= $suffix;
$convertedPlaceholderArray[] = $notes;
}
}
// '<:userKeys:>' placeholder:
elseif (preg_match("/:userKeys(\[[^][]*\])?:/i", $placeholderPart))
{
if (!empty($formVars['userKeysName'])) // if the 'user_keys' field isn't empty
{
$userKeys = $prefix;
$userKeys .= extractDetailsFromField("user_keys", $formVars['userKeysName'], "/ *[;,] */", $options);
$userKeys .= $suffix;
$convertedPlaceholderArray[] = $userKeys;
}
}
// '<:citeKey:>' placeholder:
elseif (preg_match("/:citeKey:/i", $placeholderPart))
{
if (!empty($formVars['citeKeyName'])) // if the 'cite_key' field isn't empty
$convertedPlaceholderArray[] = $prefix . $formVars['citeKeyName'] . $suffix;
}
// '<:doi:>' placeholder:
elseif (preg_match("/:doi:/i", $placeholderPart))
{
if (!empty($formVars['doiName'])) // if the 'doi' field isn't empty
$convertedPlaceholderArray[] = $prefix . $formVars['doiName'] . $suffix;
}
// '<:recordIdentifier:>' placeholder:
elseif (preg_match("/:recordIdentifier:/i", $placeholderPart))
{
if (!empty($formVars['citeKeyName'])) // if the 'cite_key' field isn't empty
$convertedPlaceholderArray[] = $prefix . $formVars['citeKeyName'] . $suffix; // if available, we prefer the user-specific cite key as unique record identifier
else
$convertedPlaceholderArray[] = $prefix . $formVars['serialNo'] . $suffix; // otherwise we'll use the record's serial number
}
// '<:randomNumber:>' placeholder:
elseif (preg_match("/:randomNumber(\[[^][]*\])?:/i", $placeholderPart))
{
$randomString = $prefix;
$randomString .= generateRandomNumber($options);
$randomString .= $suffix;
$convertedPlaceholderArray[] = $randomString;
}
// else: un-recognized placeholders will be ignored
}
else // the part is assumed to be a literal string
{
$convertedPlaceholderArray[] = $placeholderPart; // add part as is to array of transformed placeholder parts
}
}
}
$convertedPlaceholderString = implode("", $convertedPlaceholderArray); // merge transformed placeholder parts
return $convertedPlaceholderString;
}
// --------------------------------------------------------------------
// EXTRACT AUTHOR NAMES AND GENERATE IDENTIFIER STRING
// this function takes the contents of the author field and generates an author identifier string (see comments below for
// some examples) which is used by the file name (and cite key) auto-generation feature when replacing the <:authors:> placeholder
function extractDetailsFromAuthors($authorString, $options)
{
global $extractDetailsAuthorsDefault; // defined in 'ini.inc.php'
$returnRawAuthorString = false;
if (empty($options)) // if '$options' is empty
$options = $extractDetailsAuthorsDefault; // load the default options
if (preg_match("/^\[-?\d*\|[^][|]*\|[^][|]*\]$/i", $options)) // if the '$options' variable contains a recognized syntax (minimum spec must be: "[||]", i.e., second and third option delimiters are not optional but must be specified!)
{
// extract the individual options:
if (preg_match("/^\[-?\d+\|/i", $options)) // if the first option contains a number
{
$useMaxNoOfAuthors = preg_replace("/\[(-?\d+)\|[^][|]*\|[^][|]*\]/i", "\\1", $options); // regex note: to include a literal closing bracket (']') in a negated character class ('[^...]') it must be positioned right after the caret character ('^') such as in: '[^]...]'
if ($useMaxNoOfAuthors == 0) // the special number '0' indicates that all authors shall be retrieved
$useMaxNoOfAuthors = 250; // by assigning a very high number to '$useMaxNoOfAuthors' we should be pretty safe to catch all authors from the author field (extremely high values may choke the regular expression engine, though)
elseif ($useMaxNoOfAuthors < 0) // negative numbers have currently no meaning and will be treated as if the corresponding positive number was given
$useMaxNoOfAuthors = abs($useMaxNoOfAuthors);
}
elseif (preg_match("/^\[\|/i", $options)) // if the first option was left empty we assume that the raw author string shall be returned without any modification
$returnRawAuthorString = true;
$authorConnectorString = preg_replace("/\[-?\d*\|([^][|]*)\|[^][|]*\]/i", "\\1", $options);
$etalIdentifierString = preg_replace("/\[-?\d*\|[^][|]*\|([^][|]*)\]/i", "\\1", $options);
}
else // use yet another fallback if the given options contain a buggy syntax
{
$useMaxNoOfAuthors = 2;
$authorConnectorString = "+";
$etalIdentifierString = "_etal";
}
if ($returnRawAuthorString)
$authorDetails = $authorString; // return the raw author string without any modification
else
{
$authorDetails = ""; // initialize variable which will hold the author identifier string
// Add first author (plus additional authors if available up to the number of authors specified in '$useMaxNoOfAuthors');
// but if more authors are present as in '$useMaxNoOfAuthors', add the contents of '$etalIdentifierString' after the first(!) author ignoring all other authors.
// Example with '$extractDetailsAuthorsDefault = "[2|+|_etal]"':
// $authorString = "Steffens, M" -> $authorDetails = "Steffens"
// $authorString = "Steffens, M; Thomas, D" -> $authorDetails = "Steffens+Thomas"
// $authorString = "Steffens, M; Thomas, D; Dieckmann, GS" -> $authorDetails = "Steffens_etal"
// Example with '$extractDetailsAuthorsDefault = "[1|+|++]"':
// $authorString = "Steffens, M" -> $authorDetails = "Steffens"
// $authorString = "Steffens, M; Thomas, D" -> $authorDetails = "Steffens++"
// $authorString = "Steffens, M; Thomas, D; Dieckmann, GS" -> $authorDetails = "Steffens++"
for ($i=1; $i <= ($useMaxNoOfAuthors + 1); $i++)
{
if (preg_match("/^[^;]+(;[^;]+){" . ($i - 1) . "}/", $authorString)) // if the 'author' field does contain (at least) as many authors as specified in '$i'
{
if ($i>1)
{
if (preg_match("/^[^;]+(;[^;]+){" . $useMaxNoOfAuthors . "}/", $authorString)) // if the 'author' field does contain more authors as specified in '$useMaxNoOfAuthors'
{
$authorDetails .= $etalIdentifierString;
break;
}
else
$authorDetails .= $authorConnectorString;
}
// Call the 'extractAuthorsLastName()' function to extract the last name of a particular author (specified by position):
// (see function header for description of required parameters)
$authorDetails .= extractAuthorsLastName("/ *; */",
"/ *, */",
$i,
$authorString);
}
else
break;
}
}
return $authorDetails;
}
// --------------------------------------------------------------------
// EXTRACT YEAR
// this function takes the contents of the year field and returns the year in two-digit
// or four-digit format (depending on the given '$option' which must be either "[2]" or "[4]")
function extractDetailsFromYear($yearString, $options)
{
global $extractDetailsYearDefault; // defined in 'ini.inc.php'
if (empty($options)) // if '$options' is empty
$options = $extractDetailsYearDefault; // load the default option
if (preg_match("/^\[[24]\]$/i", $options)) // if the '$options' variable contains a recognized syntax
$yearDigitFormat = preg_replace("/^\[([24])\]$/i", "\\1", $options); // extract the individual option
else // use yet another fallback if the given option contains a buggy syntax
$yearDigitFormat = 4;
if (preg_match("/^\D*\d{4}/i", $yearString))
{
if ($yearDigitFormat == 2)
$yearDetails = preg_replace("/^\D*\d{2}(\d{2}).*/i", "\\1", $yearString);
else
$yearDetails = preg_replace("/^\D*(\d{4}).*/i", "\\1", $yearString);
}
else
$yearDetails = $yearString; // fallback
return $yearDetails;
}
// --------------------------------------------------------------------
// EXTRACT DETAILS FROM FIELD
// this function takes the contents of the title/publication/abbrev_journal/pages/keywords/area/notes/user_keys field
// and returns x words/items from the beginning (or end) of the string (depending on the given '$option' which must be
// of the form "[x]" where x is a number indicating how many words/items shall be returned; positive number: return x
// words/items from beginning of string, negative number: return x words/items from end of string)
function extractDetailsFromField($fieldName, $sourceString, $splitDelim, $options)
{
global $extractDetailsTitleDefault; // these variables are defined in 'ini.inc.php'
global $extractDetailsPublicationDefault;
global $extractDetailsAbbrevJournalDefault;
global $extractDetailsKeywordsDefault;
global $extractDetailsAreaDefault;
global $extractDetailsNotesDefault;
global $extractDetailsUserKeysDefault;
$returnRawSourceString = false;
if (empty($options)) // if '$options' is empty load the default option
{
if ($fieldName == "title")
$options = $extractDetailsTitleDefault;
elseif ($fieldName == "publication")
$options = $extractDetailsPublicationDefault;
elseif ($fieldName == "abbrev_journal")
$options = $extractDetailsAbbrevJournalDefault;
elseif ($fieldName == "pages")
$options = "[1]";
elseif ($fieldName == "keywords")
$options = $extractDetailsKeywordsDefault;
elseif ($fieldName == "area")
$options = $extractDetailsAreaDefault;
elseif ($fieldName == "notes")
$options = $extractDetailsNotesDefault;
elseif ($fieldName == "user_keys")
$options = $extractDetailsUserKeysDefault;
}
if (preg_match("/^\[(-?\d+(\|[^][|]*)?|-?\d*\|[^][|]*)\]$/i", $options)) // if the '$options' variable contains a recognized syntax
{
// extract the individual options:
if (preg_match("/^\[-?\d+/i", $options)) // if the first option contains a number
{
$extractNumberOfWords = preg_replace("/^\[(-?\d+)(\|[^][|]*)?\]$/i", "\\1", $options);
if ($extractNumberOfWords == 0) // the special number '0' indicates that all field items shall be retrieved
$extractNumberOfWords = 999; // by assigning a very high number to '$extractNumberOfWords' we should be pretty safe to catch all words/items from the given field (extremely high values may choke the regular expression engine, though)
}
elseif (preg_match("/^\[\|/i", $options)) // if the first option was left empty we assume that the raw source string shall be returned without any modification
$returnRawSourceString = true;
if (preg_match("/^\[-?\d*\|[^][|]+\]$/i", $options)) // if the second option contains some content
$joinDelim = preg_replace("/^\[-?\d*\|([^][|]+)\]$/i", "\\1", $options);
else
$joinDelim = "";
}
else // use yet another fallback if the given option contains a buggy syntax
{
$extractNumberOfWords = 1;
$joinDelim = "";
}
if (!($returnRawSourceString) AND preg_match($splitDelim, $sourceString))
$sourceStringDetails = extractPartsFromString($sourceString, $splitDelim, $joinDelim, $extractNumberOfWords);
else
$sourceStringDetails = $sourceString; // fallback
return $sourceStringDetails;
}
// --------------------------------------------------------------------
// GENERATE RANDOM NUMBER
// this function generates a random number taken from the range which is defined in '$options' (format: "[min|max]", e.g. "[0|9999]"),
// if '$options' is empty the maximum possible range will be used
function generateRandomNumber($options)
{
global $extractDetailsRandomNumberDefault; // defined in 'ini.inc.php'
if (empty($options)) // if '$options' is empty
$options = $extractDetailsRandomNumberDefault; // load the default options
if (preg_match("/^\[\d+\|\d+\]$/i", $options)) // if the '$options' variable contains a recognized syntax
{
// extract the individual options:
$minRandomNumber = preg_replace("/\[(\d+)\|.+/i", "\\1", $options); // extract first option which defines the minimum random number
$maxRandomNumber = preg_replace("/\[\d+\|(\d+)\]/i", "\\1", $options); // extract second option which defines the maximum random number
// generate random number:
$randomNumber = mt_rand($minRandomNumber, $maxRandomNumber);
}
else // no (or unrecognized) options
{
// generate random number:
$randomNumber = mt_rand(); // if called without the optional min, max arguments 'mt_rand()' returns a pseudo-random value between 0 and RAND_MAX
}
return $randomNumber;
}
// --------------------------------------------------------------------
// GET UPLOAD INFO
// Given the name of a file upload field, return a four (or five) element associative
// array containing information about the file. The element names are:
// name - original name of file on client
// type - MIME type of file (e.g.: 'image/gif')
// tmp_name - name of temporary file on server
// error - holds an error number >0 if something went wrong, otherwise 0
// (the 'error' element was added with PHP 4.2.0. Error code explanation: <http://www.php.net/file-upload.errors>)
// size - size of file in bytes
// depending what happend on upload, they will contain the following values (PHP 4.1 and above):
// no file upload upload exceeds 'upload_max_filesize' successful upload
// -------------- ------------------------------------ -----------------
// name "" [name] [name]
// type "" "" [type]
// tmp_name "" OR "none" "" [tmp_name]
// error 4 1 0
// size 0 0 [size]
// The function prefers the $_FILES array if it is available, falling back
// to $HTTP_POST_FILES and $HTTP_POST_VARS as necessary.
function getUploadInfo($name)
{
global $HTTP_POST_FILES, $HTTP_POST_VARS;
$uploadFileInfo = array(); // initialize array variable
// Look for information in PHP 4.1 $_FILES array first.
// Note: The entry in $_FILES might be present even if no file was uploaded (see above).
// Check the 'tmp_name' and/or the 'error' member to make sure there is a file.
if (isset($_FILES))
if (isset($_FILES[$name]))
$uploadFileInfo = ($_FILES[$name]);
// Look for information in PHP 4 $HTTP_POST_FILES array next.
// (Again, check the 'tmp_name' and/or the 'error' member to make sure there is a file.)
elseif (isset($HTTP_POST_FILES))
if (isset($HTTP_POST_FILES[$name]))
$uploadFileInfo = ($HTTP_POST_FILES[$name]);
// Look for PHP 3 style upload variables.
// Check the _name member, because $HTTP_POST_VARS[$name] might not
// actually be a file field.
elseif (isset($HTTP_POST_VARS[$name])
&& isset($HTTP_POST_VARS[$name . "_name"]))
{
// Map PHP 3 elements to PHP 4-style element names
$uploadFileInfo["name"] = $HTTP_POST_VARS[$name . "_name"];
$uploadFileInfo["tmp_name"] = $HTTP_POST_VARS[$name];
$uploadFileInfo["size"] = $HTTP_POST_VARS[$name . "_size"];
$uploadFileInfo["type"] = $HTTP_POST_VARS[$name . "_type"];
}
if (isset($uploadFileInfo["tmp_name"]) && ($uploadFileInfo["tmp_name"] == "none")) // on some systems (PHP versions) the 'tmp_name' element might contain 'none' if there was no file being uploaded
$uploadFileInfo["tmp_name"] = ""; // in order to standardize array output we replace 'none' with an empty string
return $uploadFileInfo;
}
// --------------------------------------------------------------------
// BUILD RELATED RECORDS LINK
// (this function generates a proper SQL query string from the contents of the user-specific 'related' field (table 'user_data') and returns a HTML link;
// clicking this link will show all records that match the serials or partial queries that were specified within the 'related' field)
function buildRelatedRecordsLink($relatedFieldString, $userID)
{
global $tableRefs, $tableUserData; // defined in 'db.inc.php'
// initialize some arrays:
$serialsArray = array(); // we'll use this array to hold all record serial numbers that we encounter
$queriesArray = array(); // this array will hold all sub-queries that were extracted from the 'related' field
// split the source string on any semi-colon ";" (optionally surrounded by whitespace) which works as our main delimiter:
$relatedFieldArray = preg_split("/ *; */", $relatedFieldString);
foreach ($relatedFieldArray as $relatedFieldArrayElement)
{
$relatedFieldArrayElement = trim($relatedFieldArrayElement); // remove any preceding or trailing whitespace
if (!empty($relatedFieldArrayElement))
{
if (is_numeric($relatedFieldArrayElement)) // if the current array element is a number, we assume its a serial number
$serialsArray[] = $relatedFieldArrayElement; // append the current array element to the end of the serials array
else
{
// replace any colon ":" (optionally surrounded by whitespace) with " RLIKE " and enclose the search value with quotes:
// (as an example, 'author:steffens, m' will be transformed to 'author RLIKE "steffens, m"')
if (preg_match("/:/",$relatedFieldArrayElement))
$relatedFieldArrayElement = preg_replace("/ *: *(.+)/"," RLIKE \"\\1\"",$relatedFieldArrayElement);
// else we assume '$relatedFieldArrayElement' to contain a valid 'WHERE' clause!
$queriesArray[] = $relatedFieldArrayElement; // append the current array element to the end of the queries array
}
}
}
if (!empty($serialsArray)) // if the 'related' field did contain any record serials
{
$serialsString = implode("|", $serialsArray);
$serialsString = "serial RLIKE " . quote_smart("^(" . $serialsString . ")$");
$queriesArray[] = $serialsString; // append the serial query to the end of the queries array
}
// re-join the queries array with an "OR" separator:
$queriesString = implode(" OR ", $queriesArray);
// build the full SQL query:
// TODO: build the complete SQL query using functions 'buildFROMclause()' and 'buildORDERclause()'
$relatedQuery = buildSELECTclause("", "", "", false, false);
// if any of the user-specific fields are present in the contents of the 'related' field, we'll add the 'LEFT JOIN...' part to the 'FROM' clause:
if (preg_match("/marked|copy|selected|user_keys|user_notes|user_file|user_groups|cite_key|related/",$queriesString))
$relatedQuery .= " FROM $tableRefs LEFT JOIN $tableUserData ON serial = record_id AND user_id = $userID";
else // we skip the 'LEFT JOIN...' part of the 'FROM' clause:
$relatedQuery .= " FROM $tableRefs";
$relatedQuery .= " WHERE " . $queriesString . " ORDER BY author, year DESC, publication"; // add 'WHERE' & 'ORDER BY' clause
// build the correct query URL:
$relatedRecordsLink = "search.php?sqlQuery=" . rawurlencode($relatedQuery) . "&amp;formType=sqlSearch&amp;showLinks=1"; // we skip unnecessary parameters ('search.php' will use it's default values for them)
return $relatedRecordsLink;
}
// --------------------------------------------------------------------
// LINKIFY FIELD ITEMS
// (this function generates 'show.php' HTML links for the items of the given field)
// NOTE: HTML links are only generated for fields that are supported by 'show.php', and
// only for those where it makes sense (e.g. the 'abstract' field doesn't get linked).
// The list of fields can be adopted in variable '$linkedFields' in 'ini.inc.php'.
function linkifyFieldItems($field, $fieldData, $userID = "", $localSearchReplaceActionsArray = array(), $encodingExceptionsArray = array(), $itemDelim = "/\s*[;]+\s*/", $joinDelim = "; ", $showQuery = "", $showLinks = "", $showRows = "", $citeStyle = "", $citeOrder = "", $wrapResults = "", $displayType = "", $viewType = "")
{
global $databaseBaseURL; // these variables are defined in 'ini.inc.php'
global $linkedFields;
global $loc; // '$loc' is made globally available in 'core.php'
global $client;
// Fields that may contain multiple items (delimited by '$itemDelim'):
static $multiItemFields = array("author", "keywords", "area", "notes", "location", "contribution_id",
"user_keys", "user_notes", "user_groups");
// Fields that require the 'userID' parameter:
// (note that, currently, 'show.php' only supports the fields 'marked',
// 'selected', 'user_keys', 'user_notes', 'user_groups' and 'cite_key')
static $userSpecificFields = array("marked", "copy", "selected", "user_keys", "user_notes",
"user_file", "user_groups", "cite_key");
// Display results in Details view when querying for one of the given fields:
// (see note below)
static $showFieldsInDetailsView = array("title", "serial", "cite_key");
// Check whether we're dealing with an unsupported field or if data are missing:
if (!in_array($field, $linkedFields) OR empty($fieldData) OR (in_array($field, $userSpecificFields) AND empty($userID)))
{
// Contents of unsupported fields won't get hotlinked but we'll have to HTML
// encode special chars and apply any field-specific search & replace actions:
if (!empty($fieldData))
return encodeField($field, $fieldData, $localSearchReplaceActionsArray, $encodingExceptionsArray);
else
return $fieldData;
}
else // supported field
{
$queryParametersArray = array();
// Add the 'userID' parameter for user-specific fields:
if (in_array($field, $userSpecificFields) AND !empty($userID))
$queryParametersArray["userID"] = $userID;
// Add query parameters if they're different from the defaults:
if (!empty($client))
$queryParametersArray["client"] = $client;
// NOTE: refbase usually tries to keep the user's current view. However, in
// case of the search links, I think it usually makes sense to present
// results in one of the two list-style views (i.e. List view or
// Citation view) -- and not in Details view, for example. Thus, we
// exclude Details view for regular fields. However, as an exception,
// fields listed in '$showFieldsInDetailsView' will be always displayed
// in Details view. This is done since the user most likely expects to
// see record details when he clicks on a record identifier (such as the
// 'serial' or 'cite_key' field) or the record's title.
if (in_array($field, $showFieldsInDetailsView))
$queryParametersArray["submit"] = "Display";
elseif (!empty($displayType) AND ($displayType != $_SESSION['userDefaultView']) AND (preg_match("/^(List|Cite)$/i", $displayType)))
$queryParametersArray["submit"] = $displayType;
if (!empty($viewType) AND ($viewType != "Web"))
$queryParametersArray["viewType"] = $viewType;
if ($showQuery == "1")
$queryParametersArray["showQuery"] = $showQuery;
if ($showLinks == "0")
$queryParametersArray["showLinks"] = $showLinks;
if ($wrapResults == "0")
$queryParametersArray["wrapResults"] = $wrapResults;
// We use absolute links for CLI clients, for include mechanisms, or when
// returning only a partial document structure:
if (preg_match("/^(cli|inc)/i", $client) OR ($wrapResults == "0"))
$baseURL = $databaseBaseURL;
else
$baseURL = "";
// Map MySQL field names to localized column names:
$fieldNamesArray = mapFieldNames();
$linkifiedFieldItems = array();
// Check whether we need to split field values for this field:
if (in_array($field, $multiItemFields))
$itemArray = preg_split($itemDelim, $fieldData);
else
$itemArray = array($fieldData);
// Linkify each field item:
foreach ($itemArray as $item)
{
$queryParametersArrayTemp = $queryParametersArray;
// Escape any meta characters:
$escapedItem = preg_quote($item, "");
// Adopt 'show.php' parameter name if it doesn't equal the field name:
if ($field == "serial")
$queryParametersArrayTemp["record"] = $escapedItem;
elseif ($field == "marked")
$queryParametersArrayTemp["ismarked"] = $escapedItem;
else
$queryParametersArrayTemp[$field] = $escapedItem;
// Generate item link:
$linkifiedFieldItems[] = "<a href=\"" . $baseURL . generateURL("show.php", "html", $queryParametersArrayTemp, true, $showRows, 0, $citeStyle, $citeOrder) . "\""
. " title=\"" . $loc["LinkTitle_SearchFieldItem_Prefix"] . $fieldNamesArray[$field] . $loc["LinkTitle_SearchFieldItem_Suffix"] . encodeHTML($item) . "\">"
. encodeField($field, $item, $localSearchReplaceActionsArray, $encodingExceptionsArray) // HTML encode special chars AND apply any field-specific search & replace actions
. "</a>";
}
return "<span class=\"itemlinks\">" . implode($joinDelim, $linkifiedFieldItems) . "</span>";
}
}
// --------------------------------------------------------------------
// MODIFY USER GROUPS
// add (remove) selected records to (from) the specified user group
// Note: this function serves two purposes (which must not be confused!):
// - if "$queryTable = user_data", it will modify the values of the 'user_groups' field of the 'user_data' table (where a user can assign one or more groups to particular *references*)
// - if "$queryTable = users", this function will modify the values of the 'user_groups' field of the 'users' table (where the admin can assign one or more groups to particular *users*)
function modifyUserGroups($queryTable, $displayType, $recordSerialsArray, $userID, $userGroup)
{
global $tableUserData, $tableUsers; // defined in 'db.inc.php'
connectToMySQLDatabase();
$userGroupQuoted = preg_quote($userGroup, "/"); // escape meta characters (including '/' that is used as delimiter for the PCRE match & replace functions below and which gets passed as second argument)
if ($queryTable == $tableUserData) // for the current user, get all entries within the 'user_data' table that refer to the selected records (listed in '$recordSerialsArray'):
$query = "SELECT record_id, user_groups FROM $tableUserData WHERE record_id RLIKE " . quote_smart("^(" . implode("|", $recordSerialsArray) . ")$") . " AND user_id = " . quote_smart($userID);
elseif ($queryTable == $tableUsers) // for the admin, get all entries within the 'users' table that refer to the selected records (listed in '$recordSerialsArray'):
$query = "SELECT user_id as record_id, user_groups FROM $tableUsers WHERE user_id RLIKE " . quote_smart("^(" . implode("|", $recordSerialsArray) . ")$");
// (note that by using 'user_id as record_id' we can use the term 'record_id' as identifier of the primary key for both tables)
$result = queryMySQLDatabase($query); // RUN the query on the database through the connection
$foundSerialsArray = array(); // initialize array variable (which will hold the serial numbers of all found records)
$rowsFound = @ mysqli_num_rows($result);
if ($rowsFound > 0) // If there were rows found ...
{
while ($row = @ mysqli_fetch_array($result)) // for all rows found
{
$recordID = $row["record_id"]; // get the serial number of the current record
$foundSerialsArray[] = $recordID; // add this record's serial to the array of found serial numbers
$recordUserGroups = $row["user_groups"]; // extract the user groups that the current record belongs to
// ADD the specified user group to the 'user_groups' field:
if ($displayType == "Add" AND !preg_match("/(^|.*;) *$userGroupQuoted *(;.*|$)/", $recordUserGroups)) // if the specified group isn't listed already within the 'user_groups' field:
{
if (empty($recordUserGroups)) // and if the 'user_groups' field is completely empty
$recordUserGroups = $userGroup; // add the specified user group to the 'user_groups' field
else // if the 'user_groups' field does already contain some user content:
$recordUserGroups .= "; " . $userGroup; // append the specified user group to the 'user_groups' field
}
// REMOVE the specified user group from the 'user_groups' field:
elseif ($displayType == "Remove") // remove the specified group from the 'user_groups' field:
{
$recordUserGroups = preg_replace("/^ *$userGroupQuoted *(?=;|$)/", "", $recordUserGroups); // the specified group is listed at the very beginning of the 'user_groups' field
$recordUserGroups = preg_replace("/ *; *$userGroupQuoted *(?=;|$)/", "", $recordUserGroups); // the specified group occurs after some other group name within the 'user_groups' field
$recordUserGroups = preg_replace("/^ *; */i", "", $recordUserGroups); // remove any remaining group delimiters at the beginning of the 'user_groups' field
}
if ($queryTable == $tableUserData) // for the current record & user ID, update the matching entry within the 'user_data' table:
$queryUserData = "UPDATE $tableUserData SET user_groups = " . quote_smart($recordUserGroups) . " WHERE record_id = " . quote_smart($recordID) . " AND user_id = " . quote_smart($userID);
elseif ($queryTable == $tableUsers) // for the current user ID, update the matching entry within the 'users' table:
$queryUserData = "UPDATE $tableUsers SET user_groups = " . quote_smart($recordUserGroups) . " WHERE user_id = " . quote_smart($recordID);
$resultUserData = queryMySQLDatabase($queryUserData); // RUN the query on the database through the connection
}
}
if (($queryTable == $tableUserData) AND ($displayType == "Add"))
{
// for all selected records that have no entries in the 'user_data' table (for this user), we'll need to add a new entry containing the specified group:
$leftoverSerialsArray = array_diff($recordSerialsArray, $foundSerialsArray); // get all unique array elements of '$recordSerialsArray' which are not in '$foundSerialsArray'
foreach ($leftoverSerialsArray as $leftoverRecordID) // for each record that we haven't processed yet (since it doesn't have an entry in the 'user_data' table for this user)
{
if ($leftoverRecordID > 0) // function 'extractFormElementsQueryResults()' in 'search.php' assigns '$recordSerialsArray[]="0"' if '$recordSerialsArray' is empty
{
$foundSerialsArray[] = $leftoverRecordID; // add this record's serial to the array of found serial numbers
// for the current record & user ID, add a new entry (containing the specified group) to the 'user_data' table:
$queryUserData = "INSERT INTO $tableUserData SET "
. "user_groups = " . quote_smart($userGroup) . ", "
. "record_id = " . quote_smart($leftoverRecordID) . ", "
. "user_id = " . quote_smart($userID) . ", "
. "data_id = NULL"; // inserting 'NULL' into an auto_increment PRIMARY KEY attribute allocates the next available key value
$resultUserData = queryMySQLDatabase($queryUserData); // RUN the query on the database through the connection
}
}
}
// TODO!
// save an informative message:
// if (count($foundSerialsArray) == "1")
// $recordHeader = $loc["record"]; // use singular form if only one record was updated
// else
// $recordHeader = $loc["records"]; // use plural form if multiple records were updated
// $HeaderString = returnMsg("The groups of " . . " records were updated successfully!", "", "", "HeaderString");
getUserGroups($queryTable, $userID); // update the appropriate session variable
}
// --------------------------------------------------------------------
// Get all user groups specified by the current user (or admin)
// and (if some groups were found) save them as semicolon-delimited string to a session variable:
// Note: this function serves two purposes (which must not be confused!):
// - if "$queryTable = user_data", it will fetch unique values from the 'user_groups' field of the 'user_data' table (where a user can assign one or more groups to particular *references*)
// - if "$queryTable = users", this function will fetch unique values from the 'user_groups' field of the 'users' table (where the admin can assign one or more groups to particular *users*)
function getUserGroups($queryTable, $userID)
{
global $tableUserData, $tableUsers; // defined in 'db.inc.php'
connectToMySQLDatabase();
// CONSTRUCT SQL QUERY:
// Note: 'user_groups RLIKE ".+"' will cause the database to only return user data entries where the 'user_groups' field
// is neither NULL (=> 'user_groups IS NOT NULL') nor the empty string (=> 'user_groups NOT RLIKE "^$"')
if ($queryTable == $tableUserData)
// Find all unique 'user_groups' entries in the 'user_data' table belonging to the current user:
$query = "SELECT DISTINCT user_groups FROM $tableUserData WHERE user_id = " . quote_smart($userID) . " AND user_groups RLIKE \".+\"";
elseif ($queryTable == $tableUsers)
// Find all unique 'user_groups' entries in the 'users' table:
$query = "SELECT DISTINCT user_groups FROM $tableUsers WHERE user_groups RLIKE \".+\"";
$result = queryMySQLDatabase($query); // RUN the query on the database through the connection
$userGroupsArray = array(); // initialize array variable
$rowsFound = @ mysqli_num_rows($result);
if ($rowsFound > 0) // If there were rows found ...
{
while ($row = @ mysqli_fetch_array($result)) // for all rows found
{
// remove any meaningless delimiter(s) from the beginning or end of a field string:
$rowUserGroupsString = trimTextPattern($row["user_groups"], "( *; *)+", true, true);
// split the contents of the 'user_groups' field on the specified delimiter (which is interpreted as perl-style regular expression!):
$rowUserGroupsArray = preg_split("/ *; */", $rowUserGroupsString);
$userGroupsArray = array_merge($userGroupsArray, $rowUserGroupsArray); // append this row's group names to the array of found user groups
}
// remove duplicate group names from array:
$userGroupsArray = array_unique($userGroupsArray);
// sort in ascending order:
sort($userGroupsArray);
// join array of unique user groups with '; ' as separator:
$userGroupsString = implode('; ', $userGroupsArray);
// Write the resulting string of user groups into a session variable:
if ($queryTable == $tableUserData)
saveSessionVariable("userGroups", $userGroupsString);
elseif ($queryTable == $tableUsers)
saveSessionVariable("adminUserGroups", $userGroupsString);
}
else // no user groups found
{ // delete any session variable (which is now outdated):
if ($queryTable == $tableUserData)
deleteSessionVariable("userGroups");
elseif ($queryTable == $tableUsers)
deleteSessionVariable("adminUserGroups");
}
}
// --------------------------------------------------------------------
// Get all user queries specified by the current user
// and (if some queries were found) save them as semicolon-delimited string to the session variable 'userQueries':
function getUserQueries($userID)
{
global $tableQueries; // defined in 'db.inc.php'
connectToMySQLDatabase();
// CONSTRUCT SQL QUERY:
// Find all unique query entries in the 'queries' table belonging to the current user:
// (query names should be unique anyhow, so the DISTINCT parameter wouldn't be really necessary)
$query = "SELECT DISTINCT(query_name),last_execution FROM $tableQueries WHERE user_id = " . quote_smart($userID) . " ORDER BY last_execution DESC";
// Note: we sort (in descending order) by the 'last_execution' field to get the last used query entries first;
// by that, the last used query will be always at the top of the popup menu within the 'Recall My Query' form
$result = queryMySQLDatabase($query); // RUN the query on the database through the connection
$userQueriesArray = array(); // initialize array variable
$rowsFound = @ mysqli_num_rows($result);
if ($rowsFound > 0) // If there were rows found ...
{
while ($row = @ mysqli_fetch_array($result)) // for all rows found
$userQueriesArray[] = $row["query_name"]; // append this row's query name to the array of found user queries
// join array of unique user queries with '; ' as separator:
$userQueriesString = implode('; ', $userQueriesArray);
// Write the resulting string of user queries into a session variable:
saveSessionVariable("userQueries", $userQueriesString);
}
else // no user queries found
deleteSessionVariable("userQueries"); // delete any 'userQueries' session variable (which is now outdated)
}
// --------------------------------------------------------------------
// Get all cite keys specified by the current user:
function getUserCiteKeys($userID)
{
global $tableRefs, $tableUserData; // defined in 'db.inc.php'
connectToMySQLDatabase();
// CONSTRUCT SQL QUERY:
// Find all cite keys in table 'user_data' belonging to the current user:
// (note that the SQL query is formulated such that only those records from table 'user_data' are returned
// which have a matching entry in table 'refs'; i.e. stray items from table 'user_data' are omitted)
$query = "SELECT cite_key FROM $tableRefs LEFT JOIN $tableUserData ON serial = record_id AND user_id = " . quote_smart($userID) . " WHERE cite_key RLIKE \".+\" ORDER BY cite_key";
$result = queryMySQLDatabase($query); // RUN the query on the database through the connection
$userCiteKeysArray = array(); // initialize array variable
$rowsFound = @ mysqli_num_rows($result);
if ($rowsFound > 0) // If there were rows found ...
{
while ($row = @ mysqli_fetch_array($result)) // for all rows found
{
// If this row's cite key already exists in the global array of found cite keys ('$citeKeysArray'),
// we'll uniquify it, otherwise we'll take it as is
$citeKey = ensureUniqueCiteKey($row["cite_key"]);
// We also append the original cite key to '$userCiteKeysArray' which holds all uniquified cite keys
// as array keys and the corresponding original cite key names (including duplicate items!) as array
// values
$userCiteKeysArray[$citeKey] = $row["cite_key"];
}
}
return $userCiteKeysArray;
}
// --------------------------------------------------------------------
// This function checks if the given cite key already exists in the global array of found cite keys ('$citeKeysArray').
// If the given cite key already exists, an incrementing number will be added to uniquify it (the number will be increased
// until the cite key is truly unique); after ensuring that a given cite key is unique, it's added to '$citeKeysArray' and
// returned:
// Note: the global '$citeKeysArray' does NOT contain all cite keys defined in the entire refbase database; instead it holds:
// - on import: for records that just have been imported, the list of cite keys for all imported records (generated
// according to the current user's prefs) -PLUS- the list of all of the user's existing cite keys
// - on export: for records that just have been exported, the list of cite keys (generated according to the current
// user's prefs) for all exported records
function ensureUniqueCiteKey($citeKey)
{
global $citeKeysArray; // '$citeKeysArray' is made globally available from within this function
if (!isset($citeKeysArray))
$citeKeysArray = array(); // initialize array variable
if (isset($citeKeysArray[$citeKey])) // if this cite key already exists
{
if (preg_match("/(?<=_)\d+$/", $citeKey)) // if this cite key already contains a suffix such as "_2" we assume it to be the old number of occurrence
$citeKey = preg_replace("/(?<=_)(\d+)$/e", "'\\1' + 1", $citeKey); // increment the old number of occurrence (that already exists in this cite key) by 1
else
$citeKey = $citeKey . "_2"; // append a number of occurrence to this cite key
$citeKey = ensureUniqueCiteKey($citeKey); // recurse, to check again whether the generated cite key already exists
}
else
{
$citeKeysArray[$citeKey] = $citeKey; // append the cite key to the array of known cite keys
}
return $citeKey;
}
// --------------------------------------------------------------------
// Get all available formats/styles/types:
function getAvailableFormatsStylesTypes($dataType, $formatType) // '$dataType' must be one of the following: 'format', 'style', 'type'; '$formatType' must be either '', 'export', 'import' or 'cite'
{
global $tableDepends, $tableFormats, $tableStyles, $tableTypes; // defined in 'db.inc.php'
connectToMySQLDatabase();
// CONSTRUCT SQL QUERY:
if ($dataType == "format")
$query = "SELECT format_name, format_id FROM $tableFormats LEFT JOIN $tableDepends ON $tableFormats.depends_id = $tableDepends.depends_id WHERE format_type = " . quote_smart($formatType) . " AND format_enabled = 'true' AND depends_enabled = 'true' ORDER BY order_by, format_name";
elseif ($dataType == "style")
$query = "SELECT style_name, style_id FROM $tableStyles LEFT JOIN $tableDepends ON $tableStyles.depends_id = $tableDepends.depends_id WHERE style_enabled = 'true' AND depends_enabled = 'true' ORDER BY order_by, style_name";
elseif ($dataType == "type")
$query = "SELECT type_name, type_id FROM $tableTypes WHERE type_enabled = 'true' ORDER BY order_by, type_name";
$result = queryMySQLDatabase($query); // RUN the query on the database through the connection
$availableFormatsStylesTypesArray = array(); // initialize array variable
$rowsFound = @ mysqli_num_rows($result);
if ($rowsFound > 0) // If there were rows found ...
while ($row = @ mysqli_fetch_array($result)) // for all rows found
$availableFormatsStylesTypesArray[$row[$dataType . "_id"]] = $row[$dataType . "_name"]; // append this row's format/style/type name to the array of found user formats/styles/types
return $availableFormatsStylesTypesArray;
}
// --------------------------------------------------------------------
// Get all formats/styles/types that are available and were enabled by the admin for the current user:
function getEnabledUserFormatsStylesTypes($userID, $dataType, $formatType, $returnIDsAsValues) // '$dataType' must be one of the following: 'format', 'style', 'type'; '$formatType' must be either '', 'export', 'import' or 'cite'
{
global $tableDepends, $tableFormats, $tableStyles, $tableTypes, $tableUserFormats, $tableUserStyles, $tableUserTypes; // defined in 'db.inc.php'
connectToMySQLDatabase();
// CONSTRUCT SQL QUERY:
if ($dataType == "format")
$query = "SELECT $tableFormats.format_name, $tableFormats.format_id FROM $tableFormats LEFT JOIN $tableUserFormats on $tableFormats.format_id = $tableUserFormats.format_id LEFT JOIN $tableDepends ON $tableFormats.depends_id = $tableDepends.depends_id WHERE format_type = " . quote_smart($formatType) . " AND format_enabled = 'true' AND depends_enabled = 'true' AND user_id = " . quote_smart($userID) . " ORDER BY $tableFormats.order_by, $tableFormats.format_name";
elseif ($dataType == "style")
$query = "SELECT $tableStyles.style_name, $tableStyles.style_id FROM $tableStyles LEFT JOIN $tableUserStyles on $tableStyles.style_id = $tableUserStyles.style_id LEFT JOIN $tableDepends ON $tableStyles.depends_id = $tableDepends.depends_id WHERE style_enabled = 'true' AND depends_enabled = 'true' AND user_id = " . quote_smart($userID) . " ORDER BY $tableStyles.order_by, $tableStyles.style_name";
elseif ($dataType == "type")
$query = "SELECT $tableTypes.type_name, $tableTypes.type_id FROM $tableTypes LEFT JOIN $tableUserTypes USING (type_id) WHERE type_enabled = 'true' AND user_id = " . quote_smart($userID) . " ORDER BY $tableTypes.order_by, $tableTypes.type_name";
$result = queryMySQLDatabase($query); // RUN the query on the database through the connection
$enabledFormatsStylesTypesArray = array(); // initialize array variable
$rowsFound = @ mysqli_num_rows($result);
if ($rowsFound > 0) // If there were rows found ...
while ($row = @ mysqli_fetch_array($result)) // for all rows found
{
if ($returnIDsAsValues) // return format/style/type IDs as element values:
$enabledFormatsStylesTypesArray[] = $row[$dataType . "_id"]; // append this row's format/style/type ID to the array of found user formats/styles/types
else // return format/style/type names as element values and use the corresponding IDs as element keys:
$enabledFormatsStylesTypesArray[$row[$dataType . "_id"]] = $row[$dataType . "_name"]; // append this row's format/style/type name to the array of found user formats/styles/types
}
return $enabledFormatsStylesTypesArray;
}
// --------------------------------------------------------------------
// Get all user formats/styles/types that are available and enabled for the current user (by admins choice) AND which this user has chosen to be visible:
// and (if some formats/styles/types were found) save them each as semicolon-delimited string to the session variables 'user_export_formats', 'user_cite_formats', 'user_styles' or 'user_types', respectively:
function getVisibleUserFormatsStylesTypes($userID, $dataType, $formatType) // '$dataType' must be one of the following: 'format', 'style', 'type'; '$formatType' must be either '', 'export', 'import' or 'cite'
{
global $loginEmail;
global $adminLoginEmail; // ('$adminLoginEmail' is specified in 'ini.inc.php')
global $tableDepends, $tableFormats, $tableStyles, $tableTypes, $tableUserFormats, $tableUserStyles, $tableUserTypes; // defined in 'db.inc.php'
connectToMySQLDatabase();
// CONSTRUCT SQL QUERY:
if ($dataType == "format")
{
// Find all enabled+visible formats in table 'user_formats' belonging to the current user:
// Note: following conditions must be matched to have a format "enabled+visible" for a particular user:
// - 'formats' table: the 'format_enabled' field must contain 'true' for the given format
// (the 'formats' table gives the admin control over which formats are available to the database users)
// - 'depends' table: the 'depends_enabled' field must contain 'true' for the 'depends_id' that matches the 'depends_id' of the given format in table 'formats'
// (the 'depends' table specifies whether there are any external tools required for a particular format and if these tools are available)
// - 'user_formats' table: there must be an entry for the given user where the 'format_id' matches the 'format_id' of the given format in table 'formats' -AND-
// the 'show_format' field must contain 'true' for the 'format_id' that matches the 'format_id' of the given format in table 'formats'
// (the 'user_formats' table specifies all of the available formats for a particular user that have been selected by this user to be included in the format popups)
$query = "SELECT format_name FROM $tableFormats LEFT JOIN $tableUserFormats on $tableFormats.format_id = $tableUserFormats.format_id LEFT JOIN $tableDepends ON $tableFormats.depends_id = $tableDepends.depends_id WHERE format_type = " . quote_smart($formatType) . " AND format_enabled = 'true' AND depends_enabled = 'true' AND user_id = " . quote_smart($userID) . " AND show_format = 'true' ORDER BY $tableFormats.order_by, $tableFormats.format_name";
}
elseif ($dataType == "style")
{
// Find all enabled+visible styles in table 'user_styles' belonging to the current user:
// (same conditions apply as for formats)
$query = "SELECT style_name FROM $tableStyles LEFT JOIN $tableUserStyles on $tableStyles.style_id = $tableUserStyles.style_id LEFT JOIN $tableDepends ON $tableStyles.depends_id = $tableDepends.depends_id WHERE style_enabled = 'true' AND depends_enabled = 'true' AND user_id = " . quote_smart($userID) . " AND show_style = 'true' ORDER BY $tableStyles.order_by, $tableStyles.style_name";
}
elseif ($dataType == "type")
{
// Find all enabled+visible types in table 'user_types' belonging to the current user:
// (opposed to formats & styles, we're not checking for any dependencies here)
$query = "SELECT type_name FROM $tableTypes LEFT JOIN $tableUserTypes USING (type_id) WHERE user_id = " . quote_smart($userID) . " AND show_type = 'true' ORDER BY $tableTypes.order_by, $tableTypes.type_name";
}
$result = queryMySQLDatabase($query); // RUN the query on the database through the connection
$userFormatsStylesTypesArray = array(); // initialize array variable
// generate the name of the session variable:
if (!empty($formatType))
$sessionVariableName = "user_" . $formatType . "_" . $dataType . "s"; // yields 'user_export_formats' or 'user_cite_formats'
else
$sessionVariableName = "user_" . $dataType . "s"; // yields 'user_styles' or 'user_types'
$rowsFound = @ mysqli_num_rows($result);
if ($rowsFound > 0) // If there were rows found ...
{
while ($row = @ mysqli_fetch_array($result)) // for all rows found
$userFormatsStylesTypesArray[] = $row[$dataType . "_name"]; // append this row's format/style/type name to the array of found user formats/styles/types
// we'll only update the appropriate session variable if either a normal user is logged in -OR- the admin is logged in and views his own user options page
if (($loginEmail != $adminLoginEmail) OR (($loginEmail == $adminLoginEmail) && ($userID == getUserID($loginEmail))))
{
// join array of unique user formats/styles/types with '; ' as separator:
$userFormatsStylesTypesString = implode('; ', $userFormatsStylesTypesArray);
// Write the resulting string of user formats/styles/types into a session variable:
saveSessionVariable($sessionVariableName, $userFormatsStylesTypesString);
}
}
else // no user formats/styles/types found
// we'll only delete the appropriate session variable if either a normal user is logged in -OR- the admin is logged in and views his own user options page
if (($loginEmail != $adminLoginEmail) OR (($loginEmail == $adminLoginEmail) && ($userID == getUserID($loginEmail))))
deleteSessionVariable($sessionVariableName); // delete any 'user_export_formats'/'user_cite_formats'/'user_styles'/'user_types' session variable (which is now outdated)
return $userFormatsStylesTypesArray;
}
// --------------------------------------------------------------------
// Get all formats/styles/types that are available (or enabled for the current user) and return them as properly formatted <option> tag elements.
// Note that this function will return two pretty different things, depending on who's logged in:
// - if the admin is logged in, it will return all *available* formats/styles/types as <option> tags
// (with those items being selected which were _enabled_ by the admin for the current user)
// - if a normal user is logged in, this function will return all formats/styles/types as <option> tags which were *enabled* by the admin for the current user
// (with those items being selected which were chosen to be _visible_ by the current user)
function returnFormatsStylesTypesAsOptionTags($userID, $dataType, $formatType) // '$dataType' must be one of the following: 'format', 'style', 'type'; '$formatType' must be either '', 'export', 'import' or 'cite'
{
global $loginEmail;
global $adminLoginEmail; // ('$adminLoginEmail' is specified in 'ini.inc.php')
if ($loginEmail == $adminLoginEmail) // if the admin is logged in
$availableFormatsStylesTypesArray = getAvailableFormatsStylesTypes($dataType, $formatType); // get all available formats/styles/types
$enabledFormatsStylesTypesArray = getEnabledUserFormatsStylesTypes($userID, $dataType, $formatType, false); // get all formats/styles/types that were enabled by the admin for the current user
if ($loginEmail == $adminLoginEmail) // if the admin is logged in
{
$optionTags = buildSelectMenuOptions($availableFormatsStylesTypesArray, "/ *; */", "\t\t\t", true); // build properly formatted <option> tag elements from the items listed in '$availableFormatsStylesTypesArray'
$selectedFormatsStylesTypesArray = $enabledFormatsStylesTypesArray; // get all formats/styles/types that were enabled by the admin for the current user
}
else // if ($loginEmail != $adminLoginEmail) // if a normal user is logged in
{
$optionTags = buildSelectMenuOptions($enabledFormatsStylesTypesArray, "/ *; */", "\t\t\t", true); // build properly formatted <option> tag elements from the items listed in '$enabledFormatsStylesTypesArray'
$selectedFormatsStylesTypesArray = getVisibleUserFormatsStylesTypes($userID, $dataType, $formatType); // get all formats/styles/types that were chosen to be visible for the current user
}
foreach($selectedFormatsStylesTypesArray as $itemKey => $itemValue) // escape possible meta characters within names of formats/styles/types that shall be selected (otherwise the grep pattern below would fail)
$selectedFormatsStylesTypesArray[$itemKey] = preg_quote($itemValue);
$selectedFormatsStylesTypes = implode("|", $selectedFormatsStylesTypesArray); // merge array of formats/styles/types that shall be selected
$optionTags = preg_replace("/<option([^>]*)>($selectedFormatsStylesTypes)<\\/option>/", "<option\\1 selected>\\2</option>", $optionTags); // select all formats/styles/types that are listed within '$selectedFormatsStylesTypesArray'
return $optionTags;
}
// --------------------------------------------------------------------
// Fetch the name of the citation style file that's associated with the style given in '$citeStyle'
// Note: Refbase identifies popup items by their name (and not by ID numbers) which means that the style names within the 'styles' table must be unique!
// That said, this function assumes unique style names, i.e., there's no error checking for duplicates!
function getStyleFile($citeStyle)
{
global $tableStyles; // defined in 'db.inc.php'
connectToMySQLDatabase();
// CONSTRUCT SQL QUERY:
// get the 'style_spec' for the record entry in table 'styles' whose 'style_name' matches that in '$citeStyle':
$query = "SELECT style_spec FROM $tableStyles WHERE style_name = " . quote_smart($citeStyle);
$result = queryMySQLDatabase($query); // RUN the query on the database through the connection
$row = mysqli_fetch_array($result);
return($row["style_spec"]);
}
// --------------------------------------------------------------------
// Fetch the path/name of the format file that's associated with the format given in '$formatName'
function getFormatFile($formatName, $formatType) // '$formatType' must be either 'export', 'import' or 'cite'
{
global $tableFormats; // defined in 'db.inc.php'
connectToMySQLDatabase();
// CONSTRUCT SQL QUERY:
// get the 'format_spec' for the record entry in table 'formats' whose 'format_name' matches that in '$formatName':
$query = "SELECT format_spec FROM $tableFormats WHERE format_name = " . quote_smart($formatName) . " AND format_type = " . quote_smart($formatType);
$result = queryMySQLDatabase($query); // RUN the query on the database through the connection
$row = mysqli_fetch_array($result);
return($row["format_spec"]);
}
// --------------------------------------------------------------------
// Fetch the path of the external utility that's required for a particular import/export format
function getExternalUtilityPath($externalUtilityName)
{
global $tableDepends; // defined in 'db.inc.php'
connectToMySQLDatabase();
// CONSTRUCT SQL QUERY:
// get the path for the record entry in table 'depends' whose field 'depends_external' matches that in '$externalUtilityName':
$query = "SELECT depends_path FROM $tableDepends WHERE depends_external = " . quote_smart($externalUtilityName);
$result = queryMySQLDatabase($query); // RUN the query on the database through the connection
$row = mysqli_fetch_array($result);
return($row["depends_path"]);
}
// --------------------------------------------------------------------
// Get the user (or group) permissions for the current user
// and (optionally) save all allowed user actions as semicolon-delimited string to the session variable 'user_permissions':
function getPermissions($user_OR_groupID, $permissionType, $savePermissionsToSessionVariable) // '$permissionType' must be either 'user' or 'group'; '$savePermissionsToSessionVariable' must be either 'true' or 'false'
{
global $tableUserPermissions; // defined in 'db.inc.php'
// NOTE: the group permissions feature (table 'group_permissions') has not been implemented yet, i.e., currently, only '$permissionType=user' is recognized!
// global $tableGroupPermissions;
// if ($permissionType == "group")
// $tablePermissions = $tableGroupPermissions;
// else
$tablePermissions = $tableUserPermissions;
connectToMySQLDatabase();
// CONSTRUCT SQL QUERY:
// Fetch all permission settings from the 'user_permissions' (or 'group_permissions') table for the current user:
$query = "SELECT allow_add, allow_edit, allow_delete, allow_download, allow_upload, allow_list_view, allow_details_view, allow_print_view, allow_browse_view, allow_sql_search, allow_user_groups, allow_user_queries, allow_rss_feeds, allow_import, allow_export, allow_cite, allow_batch_import, allow_batch_export, allow_modify_options FROM " . $tablePermissions . " WHERE " . $permissionType . "_id = " . quote_smart($user_OR_groupID);
$result = queryMySQLDatabase($query); // RUN the query on the database through the connection
if (mysqli_num_rows($result) == 1) // interpret query result: Do we have exactly one row?
{
$userPermissionsArray = array(); // initialize array variables
$userPermissionsFieldNameArray = array();
$row = mysqli_fetch_array($result); // fetch the one row into the array '$row'
$fieldsFound = mysqli_num_fields($result); // count the number of fields
for ($i=0; $i<$fieldsFound; $i++)
{
// Fetch the current attribute name:
$fieldName = getMySQLFieldInfo($result, $i, "name");
$userPermissionsArray[$fieldName] = $row[$i]; // ... append this field's permission value using the field's permission name as key
if ($row[$i] == "yes") // if the current permission is set to 'yes'...
$userPermissionsFieldNameArray[] = $fieldName; // ... append this field's permission name (as value) to the array of allowed user actions
}
// join array of allowed user actions with '; ' as separator:
$allowedUserActionsString = implode('; ', $userPermissionsFieldNameArray);
if ($savePermissionsToSessionVariable)
// Write the resulting string of allowed user actions into a session variable:
saveSessionVariable("user_permissions", $allowedUserActionsString);
return $userPermissionsArray;
}
else
{
if ($savePermissionsToSessionVariable)
// since no (or more than one) user/group was found with the given ID, we fall back to the default permissions which apply when no user is logged in, i.e.,
// we assume 'user_id' or 'group_id' is zero! (the 'start_session()' function will take care of setting up permissions when no user is logged in)
deleteSessionVariable("user_permissions"); // therefore, we delete any existing 'user_permissions' session variable (which is now outdated)
return array();
}
}
// --------------------------------------------------------------------
// Returns language information:
// if empty($userID): get all languages that were setup and enabled by the admin
// if !empty($userID): get the preferred language for the user with the specified userID
function getLanguages($userID)
{
global $tableLanguages, $tableUsers; // defined in 'db.inc.php'
connectToMySQLDatabase();
// CONSTRUCT SQL QUERY:
if (empty($userID))
// Find all unique language entries in the 'languages' table that are enabled:
// (language names should be unique anyhow, so the DISTINCT parameter wouldn't be really necessary)
$query = "SELECT DISTINCT language_name FROM $tableLanguages WHERE language_enabled = 'true' ORDER BY $tableLanguages.language_name";
else
// Get the preferred language for the user with the user ID given in '$userID':
$query = "SELECT language AS language_name FROM $tableUsers WHERE user_id = " . quote_smart($userID);
$result = queryMySQLDatabase($query); // RUN the query on the database through the connection
$languagesArray = array(); // initialize array variable
$rowsFound = @ mysqli_num_rows($result);
if ($rowsFound > 0) // If there were rows found ...
{
while ($row = @ mysqli_fetch_array($result)) // for all rows found
$languagesArray[] = $row["language_name"]; // append this row's language name to the array of found languages
}
return $languagesArray;
}
// --------------------------------------------------------------------
// Return the current user's preferred interface language:
function getUserLanguage()
{
global $loginUserID; // saved as session variable on login
global $defaultLanguage; // defined in 'ini.inc.php'
if (isset($_SESSION['loginEmail'])) // if a user is logged in
{
// get the preferred language for the current user:
$userLanguagesArray = getLanguages($loginUserID);
$userLanguage = $userLanguagesArray[0];
}
else // NO user logged in
$userLanguage = $defaultLanguage; // use the default language
return $userLanguage;
}
// --------------------------------------------------------------------
// Get all user options for the current user:
function getUserOptions($userID)
{
global $tableUserOptions; // defined in 'db.inc.php'
connectToMySQLDatabase();
if (empty($userID))
$userID = 0;
// CONSTRUCT SQL QUERY:
// Fetch all options from table 'user_options' for the user with the user ID given in '$userID':
$query = "SELECT * FROM $tableUserOptions WHERE user_id = " . quote_smart($userID);
$result = queryMySQLDatabase($query); // RUN the query on the database through the connection
$userOptionsArray = array(); // initialize array variable
$rowsFound = @ mysqli_num_rows($result);
if ($rowsFound == 1) // Interpret query result: Do we have exactly one row?
$userOptionsArray = @ mysqli_fetch_array($result); // fetch the one row into the array '$userOptionsArray'
return $userOptionsArray;
}
// --------------------------------------------------------------------
// Get the list of "main fields" preferred by the current user:
// and save the list of fields as comma-delimited string to the session variable 'userMainFields'
// TODO: Make *one* generic function that can replace functions 'getMainFields()',
// 'getDefaultNumberOfRecords()' and 'getPrefAutoCompletions()'
function getMainFields($userID)
{
global $loginEmail;
global $adminLoginEmail; // these variables are defined in 'ini.inc.php'
global $defaultMainFields;
$userOptionsArray = array(); // initialize array variable
// Get all user options for the current user:
// note that if the user isn't logged in (userID=0), the list of "main fields" is taken from variable
// '$defaultMainFields' in 'ini.inc.php' and not from option 'main_fields' in table 'user_options
if ($userID != 0)
$userOptionsArray = getUserOptions($userID);
// Extract the list of "main fields":
if (!empty($userOptionsArray) AND !empty($userOptionsArray['main_fields']))
$userMainFieldsString = $userOptionsArray['main_fields']; // honour the logged in user's preferred list of "main fields" (if not empty or NULL)
else
$userMainFieldsString = $defaultMainFields; // by default, we take the list of "main fields" from the global variable '$defaultMainFields'
// We'll only update the appropriate session variable if either a normal user is logged in -OR- the admin is logged in and views his own user options page
if (($loginEmail != $adminLoginEmail) OR (($loginEmail == $adminLoginEmail) && ($userID == getUserID($loginEmail))))
// Write the list of fields into a session variable:
saveSessionVariable("userMainFields", $userMainFieldsString);
$userMainFieldsArray = preg_split("/ *, */", $userMainFieldsString); // split the string of fields into its individual fields
return $userMainFieldsArray;
}
// --------------------------------------------------------------------
// Returns the current date (e.g. '2003-12-31'), time (e.g. '23:59:49') and user name & email address (e.g. 'Matthias Steffens (refbase@extracts.de)'):
// this information is used when adding/updating/deleting records in the database
function getCurrentDateTimeUser()
{
global $loginEmail;
global $loginFirstName;
global $loginLastName;
$currentDate = date('Y-m-d'); // get the current date in a format recognized by MySQL (which is 'YYYY-MM-DD', e.g. '2003-12-31')
$currentTime = date('H:i:s'); // get the current time in a format recognized by MySQL (which is 'HH:MM:SS', e.g. '23:59:49')
$currentUser = $loginFirstName . " " . $loginLastName . " (" . $loginEmail . ")"; // we use session variables to construct the user name, e.g. 'Matthias Steffens (refbase@extracts.de)'
return array($currentDate, $currentTime, $currentUser);
}
// --------------------------------------------------------------------
// Build a correct call number prefix for the currently logged-in user (e.g. 'IPÖ @ msteffens'):
function getCallNumberPrefix()
{
global $loginEmail;
global $abbrevInstitution;
// we use session variables to construct a correct call number prefix:
$loginEmailArray = preg_split("/@/", $loginEmail); // split the login email address at '@'
$loginEmailUserName = $loginEmailArray[0]; // extract the user name (which is the first element of the array '$loginEmailArray')
$callNumberPrefix = $abbrevInstitution . " @ " . $loginEmailUserName;
return $callNumberPrefix;
}
// --------------------------------------------------------------------
// Get the default view for the current user:
function getDefaultView($userID)
{
global $defaultView; // defined in 'ini.inc.php'
$userOptionsArray = array(); // initialize array variables
$viewsArray = array("List" => "allow_list_view",
"Cite" => "allow_cite",
"Display" => "allow_details_view",
"Browse" => "allow_browse_view");
$userDefaultView = $defaultView; // by default, we take the default view from the global variable '$defaultView'
// Note that if the user isn't logged in (userID=0), the default view is taken from variable '$defaultView'
// in 'ini.inc.php' and is not overridden by any of the '*_view' permissions ('allow_list_view', 'allow_details_view',
// 'allow_browse_view', 'allow_cite') in table 'user_permissions'
// Adopt the user's default view if he/she is NOT allowed to use the global default (given in '$defaultView'):
if (isset($viewsArray[$defaultView]) AND isset($_SESSION['user_permissions']) AND !preg_match("/" . $viewsArray[$defaultView] . "/", $_SESSION['user_permissions'])) // if the 'user_permissions' session variable does NOT contain the '*_view' permission that corresponds to '$defaultView'
{
foreach ($viewsArray as $viewType => $viewPermission) // use the next allowed view as default view
{
if (preg_match("/" . $viewPermission . "/", $_SESSION['user_permissions']))
{
$userDefaultView = $viewType;
break;
}
}
}
// Write the name of the default view into a session variable:
saveSessionVariable("userDefaultView", $userDefaultView);
return $userDefaultView;
}
// --------------------------------------------------------------------
// Returns the total number of records in the database:
function getTotalNumberOfRecords()
{
global $tableRefs; // defined in 'db.inc.php'
connectToMySQLDatabase();
// CONSTRUCT SQL QUERY:
$query = "SELECT COUNT(serial) FROM $tableRefs"; // query the total number of records
$result = queryMySQLDatabase($query); // RUN the query on the database through the connection
$row = mysqli_fetch_row($result); // fetch the current row into the array $row (it'll be always *one* row, but anyhow)
$numberOfRecords = $row[0]; // extract the contents of the first (and only) row
return $numberOfRecords;
}
// --------------------------------------------------------------------
// Get the default number of records per page preferred by the current user:
// TODO: Make *one* generic function that can replace functions 'getMainFields()',
// 'getDefaultNumberOfRecords()' and 'getPrefAutoCompletions()'
function getDefaultNumberOfRecords($userID)
{
global $loginEmail;
global $adminLoginEmail; // these variables are defined in 'ini.inc.php'
global $defaultNumberOfRecords;
$userOptionsArray = array(); // initialize array variable
// Get all user options for the current user:
// note that if the user isn't logged in (userID=0), we don't load the default number of records from option
// 'records_per_page' in table 'user_options' (where 'user_id = 0'). Instead, we'll return as many records as
// defined in variable '$defaultNumberOfRecords' in 'ini.inc.php'.
if ($userID != 0)
$userOptionsArray = getUserOptions($userID);
// Extract the number of records that's to be returned by default:
if (!empty($userOptionsArray) AND !empty($userOptionsArray['records_per_page']))
$showRows = $userOptionsArray['records_per_page']; // honour the logged in user's preferred number of records (if not empty or NULL)
else
$showRows = $defaultNumberOfRecords; // by default, we take the number of records from the global variable '$defaultNumberOfRecords'
// We'll only update the appropriate session variable if either a normal user is logged in -OR- the admin is logged in and views his own user options page
if (($loginEmail != $adminLoginEmail) OR (($loginEmail == $adminLoginEmail) && ($userID == getUserID($loginEmail))))
// Write results into a session variable:
saveSessionVariable("userRecordsPerPage", $showRows);
return $showRows;
}
// --------------------------------------------------------------------
// Get the user's preference for displaying auto-completions:
// TODO: Make *one* generic function that can replace functions 'getMainFields()',
// 'getDefaultNumberOfRecords()' and 'getPrefAutoCompletions()'
function getPrefAutoCompletions($userID)
{
global $loginEmail;
global $adminLoginEmail; // these variables are defined in 'ini.inc.php'
global $autoCompleteUserInput;
$userOptionsArray = array(); // initialize array variable
// Get all user options for the current user:
// note that if the user isn't logged in (userID=0), we don't load the pref setting from option
// 'show_auto_completions' in table 'user_options' (where 'user_id = 0'). Instead, we'll take
// the setting from variable '$autoCompleteUserInput' in 'ini.inc.php'.
if ($userID != 0)
$userOptionsArray = getUserOptions($userID);
// Extract the setting which defines whether auto-completions shall be displayed for text entered by the user:
if (!empty($userOptionsArray) AND !empty($userOptionsArray['show_auto_completions']))
$showAutoCompletions = $userOptionsArray['show_auto_completions']; // honour the logged in user's preference for displaying auto-completions (if not empty or NULL)
else
$showAutoCompletions = $autoCompleteUserInput; // by default, we take the pref setting from the global variable '$autoCompleteUserInput'
// We'll only update the appropriate session variable if either a normal user is logged in -OR- the admin is logged in and views his own user options page
if (($loginEmail != $adminLoginEmail) OR (($loginEmail == $adminLoginEmail) && ($userID == getUserID($loginEmail))))
// Write results into a session variable:
saveSessionVariable("userAutoCompletions", $showAutoCompletions);
return $showAutoCompletions;
}
// --------------------------------------------------------------------
// Returns the date/time information (in format 'YYYY-MM-DD hh-mm-ss') when the database was last modified:
function getLastModifiedDateTime()
{
global $tableRefs; // defined in 'db.inc.php'
connectToMySQLDatabase();
// CONSTRUCT SQL QUERY:
$query = "SELECT modified_date, modified_time FROM $tableRefs ORDER BY modified_date DESC, modified_time DESC, created_date DESC, created_time DESC LIMIT 1"; // get date/time info for the record that was added/edited most recently
$result = queryMySQLDatabase($query); // RUN the query on the database through the connection
$row = mysqli_fetch_row($result); // fetch the current row into the array $row (it'll be always *one* row, but anyhow)
$lastModifiedDateTime = $row[0] . " " . $row[1];
return $lastModifiedDateTime;
}
// --------------------------------------------------------------------
// Update the specified user permissions for the selected user(s):
function updateUserPermissions($userIDArray, $userPermissionsArray) // '$userPermissionsArray' must contain one or more key/value elements of the form array('allow_add' => 'yes', 'allow_delete' => 'no') where key is a particular 'allow_*' field name from table 'user_permissions' and value is either 'yes' or 'no'
{
global $tableUserPermissions; // defined in 'db.inc.php'
connectToMySQLDatabase();
$permissionQueryArray = array();
// CONSTRUCT SQL QUERY:
// prepare the 'SET' part of the SQL query string:
foreach($userPermissionsArray as $permissionKey => $permissionValue)
$permissionQueryArray[] = $permissionKey . " = " . quote_smart($permissionValue);
if (!empty($userIDArray) AND !empty($permissionQueryArray))
{
// Update all specified permission settings in the 'user_permissions' table for the selected user(s):
$query = "UPDATE $tableUserPermissions SET " . implode(", ", $permissionQueryArray) . " WHERE user_id RLIKE " . quote_smart("^(" . implode("|", $userIDArray) . ")$");
$result = queryMySQLDatabase($query); // RUN the query on the database through the connection
return true;
}
else
return false;
}
// --------------------------------------------------------------------
// Generate or extract the cite key for the given record:
function generateCiteKey($formVars)
{
global $defaultCiteKeyFormat; // defined in 'ini.inc.php'
global $handleNonASCIICharsInCiteKeysDefault;
global $userOptionsArray; // '$userOptionsArray' is made globally available in file 'import_modify.php' as well as by functions 'generateExport()' and 'generateCitations()' in 'search.php'
// by default, we use any record-specific cite key that was entered manually by the user:
if (isset($formVars['citeKeyName']))
$citeKey = $formVars['citeKeyName'];
else
$citeKey = "";
// check if the user's options for auto-generation of cite keys command us to replace the manually entered cite key:
if (!empty($userOptionsArray))
{
if ($userOptionsArray['export_cite_keys'] == "yes") // if this user wants to include cite keys on import/export
{
if ($userOptionsArray['autogenerate_cite_keys'] == "yes") // if cite keys shall be auto-generated on import/export
{
if (empty($citeKey) OR ($userOptionsArray['prefer_autogenerated_cite_keys'] == "yes")) // if there's no manually entered cite key -OR- if the auto-generated cite key shall overwrite contents from the 'cite_key' field on import/export
{
if ($userOptionsArray['use_custom_cite_key_format'] == "yes") // if the user wants to use a custom cite key format
$citeKeyFormat = $userOptionsArray['cite_key_format'];
else // use the default cite key format that was specified by the admin in 'ini.inc.php'
$citeKeyFormat = $defaultCiteKeyFormat;
// auto-generate a cite key according to the given naming scheme:
$citeKey = parsePlaceholderString($formVars, $citeKeyFormat, "<:authors:><:year:>");
}
}
}
else
$citeKey = ""; // by omitting a cite key Bibutils will take care of generation of cite keys for its export formats (BibTeX, Endnote, RIS)
}
// check how to handle non-ASCII characters:
if (!empty($userOptionsArray) AND !empty($userOptionsArray['nonascii_chars_in_cite_keys'])) // use the user's own setting
$handleNonASCIIChars = $userOptionsArray['nonascii_chars_in_cite_keys'];
else
$handleNonASCIIChars = $handleNonASCIICharsInCiteKeysDefault; // use the default setting that was specified by the admin in 'ini.inc.php'
// in addition to the handling of non-ASCII chars (given in '$handleNonASCIIChars') we'll
// strip additional characters from the generated cite keys: for cite keys, we only allow
// letters, digits, and the following characters: !$&*+-./:;<>?[]^_`|
// see e.g. the discussion of cite keys at: <http://search.cpan.org/~gward/btparse-0.34/doc/bt_language.pod>
if (!empty($citeKey))
$citeKey = handleNonASCIIAndUnwantedCharacters($citeKey, "[:alnum:]" . preg_quote("!$&*+-./:;<>?[]^_`|", "/"), $handleNonASCIIChars);
// ensure that each cite key is unique:
if (!empty($citeKey) AND !empty($userOptionsArray) AND ($userOptionsArray['export_cite_keys'] == "yes") AND ($userOptionsArray['uniquify_duplicate_cite_keys'] == "yes"))
// if the generated cite key already exists in the global array of found cite keys
// ('$citeKeysArray'), we'll uniquify it, otherwise we'll keep it as is:
$citeKey = ensureUniqueCiteKey($citeKey);
return $citeKey;
}
// --------------------------------------------------------------------
// Handle non-ASCII and unwanted characters:
// this function controls the handling of any non-ASCII chars and
// unwanted characters in file/directory names and cite keys
function handleNonASCIIAndUnwantedCharacters($fileDirCitekeyName, $allowedFileDirCitekeyNameCharacters, $handleNonASCIIChars)
{
// we treat non-ASCII characters in file/directory names and cite keys depending on the setting of variable '$handleNonASCIIChars':
if ($handleNonASCIIChars == "strip")
$fileDirCitekeyName = convertToCharacterEncoding("ASCII", "IGNORE", $fileDirCitekeyName); // remove any non-ASCII characters
elseif ($handleNonASCIIChars != "keep")
// i.e., if '$handleNonASCIIChars = "keep"' we don't attempt to strip/transliterate any non-ASCII chars in the generated file/directory name or cite key;
// otherwise if '$handleNonASCIIChars = "transliterate"' (or when '$handleNonASCIIChars' contains an unrecognized/empty string)
// we'll transliterate most of the non-ASCII characters and strip all other non-ASCII chars that can't be converted into ASCII equivalents:
$fileDirCitekeyName = convertToCharacterEncoding("ASCII", "TRANSLIT", $fileDirCitekeyName);
// in addition, we remove all characters from the generated file/directory name or cite key which are not listed in variable '$allowedFileDirCitekeyNameCharacters':
if (!empty($allowedFileDirCitekeyNameCharacters))
$fileDirCitekeyName = preg_replace("/[^" . $allowedFileDirCitekeyNameCharacters . "]+/", "", $fileDirCitekeyName);
return $fileDirCitekeyName;
}
// --------------------------------------------------------------------
// this is a stupid hack that maps the names of the '$row' array keys to those used
// by the '$formVars' array (which is required by function 'generateCiteKey()')
// (eventually, the '$formVars' array should use the MySQL field names as names for its array keys)
function buildFormVarsArray($row)
{
$formVars = array(); // initialize array variable
if(isset($row['author']))
$formVars['authorName'] = $row['author'];
if(isset($row['title']))
$formVars['titleName'] = $row['title'];
if(isset($row['type']))
$formVars['typeName'] = $row['type'];
if(isset($row['year']))
$formVars['yearNo'] = $row['year'];
if(isset($row['publication']))
$formVars['publicationName'] = $row['publication'];
if(isset($row['abbrev_journal']))
$formVars['abbrevJournalName'] = $row['abbrev_journal'];
if(isset($row['volume']))
$formVars['volumeNo'] = $row['volume'];
if(isset($row['issue']))
$formVars['issueNo'] = $row['issue'];
if(isset($row['pages']))
$formVars['pagesNo'] = $row['pages'];
if(isset($row['corporate_author']))
$formVars['corporateAuthorName'] = $row['corporate_author'];
if(isset($row['thesis']))
$formVars['thesisName'] = $row['thesis'];
if(isset($row['address']))
$formVars['addressName'] = $row['address'];
if(isset($row['keywords']))
$formVars['keywordsName'] = $row['keywords'];
if(isset($row['abstract']))
$formVars['abstractName'] = $row['abstract'];
if(isset($row['publisher']))
$formVars['publisherName'] = $row['publisher'];
if(isset($row['place']))
$formVars['placeName'] = $row['place'];
if(isset($row['editor']))
$formVars['editorName'] = $row['editor'];
if(isset($row['language']))
$formVars['languageName'] = $row['language'];
if(isset($row['summary_language']))
$formVars['summaryLanguageName'] = $row['summary_language'];
if(isset($row['orig_title']))
$formVars['origTitleName'] = $row['orig_title'];
if(isset($row['series_editor']))
$formVars['seriesEditorName'] = $row['series_editor'];
if(isset($row['series_title']))
$formVars['seriesTitleName'] = $row['series_title'];
if(isset($row['abbrev_series_title']))
$formVars['abbrevSeriesTitleName'] = $row['abbrev_series_title'];
if(isset($row['series_volume']))
$formVars['seriesVolumeNo'] = $row['series_volume'];
if(isset($row['series_issue']))
$formVars['seriesIssueNo'] = $row['series_issue'];
if(isset($row['edition']))
$formVars['editionNo'] = $row['edition'];
if(isset($row['issn']))
$formVars['issnName'] = $row['issn'];
if(isset($row['isbn']))
$formVars['isbnName'] = $row['isbn'];
if(isset($row['medium']))
$formVars['mediumName'] = $row['medium'];
if(isset($row['area']))
$formVars['areaName'] = $row['area'];
if(isset($row['expedition']))
$formVars['expeditionName'] = $row['expedition'];
if(isset($row['conference']))
$formVars['conferenceName'] = $row['conference'];
if(isset($row['notes']))
$formVars['notesName'] = $row['notes'];
if(isset($row['approved']))
$formVars['approvedRadio'] = $row['approved'];
if(isset($row['location']))
$formVars['locationName'] = $row['location'];
if(isset($row['call_number']))
$formVars['callNumberName'] = $row['call_number'];
if(isset($row['serial']))
$formVars['serialNo'] = $row['serial'];
if(isset($row['online_publication']))
$formVars['onlinePublicationCheckBox'] = $row['online_publication'];
if(isset($row['online_citation']))
$formVars['onlineCitationName'] = $row['online_citation'];
if(isset($row['marked']))
$formVars['markedRadio'] = $row['marked'];
if(isset($row['copy']))
$formVars['copyName'] = $row['copy'];
if(isset($row['selected']))
$formVars['selectedRadio'] = $row['selected'];
if(isset($row['user_keys']))
$formVars['userKeysName'] = $row['user_keys'];
if(isset($row['user_notes']))
$formVars['userNotesName'] = $row['user_notes'];
if(isset($row['user_file']))
$formVars['userFileName'] = $row['user_file'];
if(isset($row['user_groups']))
$formVars['userGroupsName'] = $row['user_groups'];
if(isset($row['cite_key']))
$formVars['citeKeyName'] = $row['cite_key'];
if(isset($row['related']))
$formVars['relatedName'] = $row['related'];
if(isset($row['orig_record']))
$formVars['origRecord'] = $row['orig_record'];
if(isset($row['file']))
$formVars['fileName'] = $row['file'];
if(isset($row['url']))
$formVars['urlName'] = $row['url'];
if(isset($row['doi']))
$formVars['doiName'] = $row['doi'];
return $formVars;
}
// --------------------------------------------------------------------
// Build properly formatted <option> tag elements from items listed within an array or string (and which -- in the case of strings -- are delimited by '$splitDelim').
// The string given in '$prefix' will be used to prefix each of the <option> tags (e.g., use '\t\t' to indent each of the tags by 2 tabs)
function buildSelectMenuOptions($sourceData, $splitDelim, $prefix, $useArrayKeysAsValues)
{
if (is_string($sourceData)) // split the string on the specified delimiter (which is interpreted as regular expression!):
$sourceData = preg_split($splitDelim, $sourceData);
if ($useArrayKeysAsValues)
{
$optionTags = ""; // initialize variable
// copy each item as option tag element to the end of the '$optionTags' variable:
foreach ($sourceData as $itemID => $item)
{
if (!empty($item))
$optionTags .= "\n$prefix<option value=\"$itemID\">$item</option>";
else // empty items will also get an empty value:
$optionTags .= "\n$prefix<option value=\"\"></option>";
}
}
else
$optionTags = "\n$prefix<option>" . implode("</option>\n$prefix<option>", $sourceData) . "</option>";
return $optionTags;
}
// --------------------------------------------------------------------
// Produce a <select> list with unique items from the specified field
// Parameters:
// 1: Database connection
// 2. Table that contains values
// 3. The field name of the table's primary key
// 4. Table name of the user data table
// 5. The field name within the user data table that corresponds to the field in 3.
// 6. The field name of the user ID field within the user data table
// 7. The user ID of the currently logged in user (which must be provided as a session variable)
// 8. Attribute that contains values
// 9. <SELECT> element name
// 10. An additional non-database value (display string)
// 11. String that gets submitted instead of the display string given in 10.
// 12. Optional <OPTION SELECTED>
// 13. Restrict query to field... (keep empty if no restriction wanted)
// 14. ...where field contents are...
// 15. Split field contents into substrings? (yes = true, no = false)
// 16. POSIX-PATTERN to split field contents into substrings (in order to obtain actual values)
// 17. The type of the output format that shall be returned ("ARRAY", "HTML SELECT", "HTML UL" or "JSON")
// 18. The POSIX-PATTERN that matches those substrings from the field's contents that shall be included as search suggestions
// 19. Boolean that specifies whether search suggestions shall be wrapped into an enclosing HTML (or JSON) structure (yes if 'true')
function selectDistinct($connection, // 1.
$refsTableName, // 2.
$refsTablePrimaryKey, // 3.
$userDataTableName, // 4.
$userDataTablePrimaryKey, // 5.
$userDataTableUserID, // 6.
$userDataTableUserIDvalue, // 7.
$columnName, // 8.
$pulldownName, // 9.
$additionalOptionDisplay, // 10.
$additionalOption, // 11.
$defaultValue, // 12.
$RestrictToField, // 13.
$RestrictToFieldContents, // 14.
$SplitValues, // 15.
$SplitPattern, // 16.
$outputFormat = "HTML SELECT", // 17.
$searchSuggestionsPattern = "", // 18.
$wrapSearchSuggestions = true) // 19.
{
$defaultWithinResultSet = FALSE;
// Query to find distinct values of '$columnName' in '$refsTableName':
if (isset($_SESSION['loginEmail'])) // if a user is logged in
{
if ($RestrictToField == "")
$distinctQuery = "SELECT DISTINCT $columnName FROM $refsTableName LEFT JOIN $userDataTableName ON $refsTablePrimaryKey = $userDataTablePrimaryKey AND $userDataTableUserID = $userDataTableUserIDvalue ORDER BY $columnName";
else
$distinctQuery = "SELECT DISTINCT $columnName FROM $refsTableName LEFT JOIN $userDataTableName ON $refsTablePrimaryKey = $userDataTablePrimaryKey AND $userDataTableUserID = $userDataTableUserIDvalue WHERE $RestrictToField RLIKE $RestrictToFieldContents ORDER BY $columnName";
}
else // if NO user is logged in
{
if ($RestrictToField == "")
$distinctQuery = "SELECT DISTINCT $columnName FROM $refsTableName ORDER BY $columnName";
else
$distinctQuery = "SELECT DISTINCT $columnName FROM $refsTableName WHERE $RestrictToField RLIKE $RestrictToFieldContents ORDER BY $columnName";
}
// Run the distinctQuery on the database through the connection:
$resultId = queryMySQLDatabase($distinctQuery);
// Retrieve all distinct values:
$i = 0;
$resultBuffer = array();
while ($row = @ mysqli_fetch_array($resultId))
{
if ($SplitValues) // if desired, split field contents into substrings
{
// split field data on the pattern specified in '$SplitPattern':
$splittedFieldData = preg_split("#" . $SplitPattern . "#", $row[$columnName]);
// ... copy all array elements to end of '$resultBuffer':
foreach($splittedFieldData as $element)
{
$element = trim($element);
// NOTE: in case of OpenSearch search suggestions, we only include those substrings
// that match the regular expression given in '$searchSuggestionsPattern'
if (empty($searchSuggestionsPattern) OR (!empty($searchSuggestionsPattern) AND !empty($element) AND preg_match("/" . $searchSuggestionsPattern . "/i", $element)))
$resultBuffer[$i++] = $element;
}
}
else // copy field data (as is) to end of '$resultBuffer':
{
$element = trim($row[$columnName]);
if (empty($searchSuggestionsPattern) OR (!empty($searchSuggestionsPattern) AND !empty($element)))
$resultBuffer[$i++] = $element;
}
}
if ($SplitValues) // (otherwise, data are already DISTINCT and ORDERed BY!)
{
if (!empty($resultBuffer))
{
// remove duplicate values from array:
$resultBuffer = array_unique($resultBuffer);
// sort in ascending order:
sort($resultBuffer);
}
}
if ($outputFormat == "ARRAY") // return data as a PHP array:
{
return $resultBuffer;
}
else // return data as HTML or JSON:
{
$outputData = "";
if ($outputFormat == "HTML SELECT") // output data in an HTML select widget:
{
// Start the HTML select widget:
if ($wrapSearchSuggestions)
$outputData = "\n\t\t<select name=\"$pulldownName\">";
$optionTags = ""; // initialize variable
// Add any additional option element:
if (!empty($additionalOptionDisplay) AND !empty($additionalOption))
$optionTags .= "\n\t\t\t<option value=\"$additionalOption\">$additionalOptionDisplay</option>";
// Build correct option tags from the provided database values:
$optionTags .= buildSelectMenuOptions($resultBuffer, "//", "\t\t\t", false);
$outputData .= preg_replace("/<option([^>]*)>($defaultValue)<\\/option>/", "<option\\1 selected>\\2</option>", $optionTags); // add 'selected' attribute
if ($wrapSearchSuggestions)
$outputData .= "\n\t\t</select>";
}
elseif (($outputFormat == "HTML UL") AND !empty($resultBuffer)) // output data in an unordered HTML list:
{
$outputData = "<li>" . implode("</li><li>", $resultBuffer) . "</li>";
if ($wrapSearchSuggestions)
$outputData = "<ul>" . $outputData . "</ul>";
}
elseif (($outputFormat == "JSON") AND !empty($resultBuffer)) // output data in JSON format:
{
$outputData = '"' . implode('", "', $resultBuffer) . '"'; // for PHP 5 >= 5.2.0 and UTF-8 data, function 'json_encode()' could be used instead
if ($wrapSearchSuggestions)
$outputData = "[" . $outputData . "]";
}
return $outputData;
}
}
// --------------------------------------------------------------------
// Returns values from the given field & table:
function getFieldContents($tableName, $columnName, $userID = "", $queryWhereClause = "", $orderBy = "", $getDistinctValues = true)
{
global $tableRefs, $tableUserData; // defined in 'db.inc.php'
connectToMySQLDatabase();
if ($getDistinctValues)
$distinct = "DISTINCT ";
else
$distinct = "";
// CONSTRUCT SQL QUERY:
$query = "SELECT " . $distinct . $columnName
. " FROM " . $tableName;
if (($tableName == $tableRefs) AND isset($_SESSION['loginEmail']) AND !empty($userID)) // when querying table 'refs', and if a user is logged in...
$query .= " LEFT JOIN " . $tableUserData . " ON serial = record_id AND user_id = " . quote_smart($userID);
if (!empty($queryWhereClause))
$query .= " WHERE " . $queryWhereClause;
if (!empty($orderBy))
$query .= " ORDER BY " . $orderBy;
$result = queryMySQLDatabase($query); // RUN the query on the database through the connection
$fieldContentsArray = array(); // initialize array variable
$rowsFound = @ mysqli_num_rows($result);
if ($rowsFound > 0) // If there were rows found ...
{
while ($row = @ mysqli_fetch_array($result)) // for all rows found
$fieldContentsArray[] = $row[$columnName]; // append this row's field value to the array of extracted field values
}
return $fieldContentsArray;
}
// --------------------------------------------------------------------
// Remove a text pattern from the beginning and/or end of a string:
// This function is used to remove leading and/or trailing delimiters from a string.
// Notes: - '$removePattern' must be specified as perl-style regular expression!
// - set both variables '$trimLeft' & '$trimRight' to 'true' if you want your text pattern to get removed from BOTH sides of the source string;
// if you only want to trim the LEFT side of your source string: set '$trimLeft = true' & '$trimRight = false';
// if you only want to trim the RIGHT side of your source string: set '$trimLeft = false' & '$trimRight = true';
// Example: if '$removePattern' = ' *; *' and both, '$trimLeft' and '$trimRight', are set to 'true',
// the string '; red; green; yellow; ' would be transformed to 'red; green; yellow'.
function trimTextPattern($sourceString, $removePattern, $trimLeft, $trimRight)
{
if ($trimLeft)
$sourceString = preg_replace("/^" . $removePattern . "/", "", $sourceString); // remove text pattern from beginning of source string
if ($trimRight)
$sourceString = preg_replace("/" . $removePattern . "$/", "", $sourceString); // remove text pattern from end of source string
return $sourceString; // return the trimmed source string
}
// --------------------------------------------------------------------
// Quote variable to make safe (and escape special characters in a string for use in a SQL statement):
function quote_smart($value)
{
// Remove slashes from value if 'magic_quotes_gpc = On':
$value = stripSlashesIfMagicQuotes($value);
// Remove any leading or trailing whitespace:
$value = trim($value);
// Quote & escape special chars if not a number or a numeric string:
if (!is_numeric($value))
{
$value = "\"" . escapeSQL($value) . "\"";
}
// Quote numbers with leading zeros (which would otherwise get stripped):
elseif (preg_match("/^0+\d+$/", $value))
{
$value = "\"" . $value . "\"";
}
return $value;
}
// --------------------------------------------------------------------
// Get the path to the currently executing script, relative to the document root:
function scriptURL()
{
if (isset($_SERVER['SCRIPT_NAME']))
{
$pathToScript = $_SERVER['SCRIPT_NAME'];
}
else
{
$pathToScript = $_SERVER['PHP_SELF'];
// Sanitize PHP_SELF:
if (preg_match('#\.php.+#', $pathToScript))
{
// Remove anything after the PHP file extension:
$pathToScript = preg_replace('#(?<=\.php).+#', '', $pathToScript);
}
}
// NOTE: When a 'show.php' URL is called from within another script via function 'fetchDataFromURL()'
// (as is the case for 'index.php'), '$_SERVER['SCRIPT_NAME']' and '$_SERVER['PHP_SELF']' do seem
// to return double slashes for path separators (e.g. "/refs//search.php"). I don't know why this
// happens. The line below fixes this:
$pathToScript = preg_replace('#//+#', '/', $pathToScript);
return $pathToScript;
}
// --------------------------------------------------------------------
// Verify the given path:
//
// NOTES: - Currently, this function just ensures that the given '$path' (i.e. a
// file directory path or URL) ends with a slash. However, it would be
// nice if this function would also check whether the given path or URL
// exists, and issue a warning if not.
// - '$type' must be either "path" or "URL"
//
// TODO: - Should we attempt to auto-guess the '$type' form the given '$path'?
// - Make sure that the correct path separator gets used on Windows
function checkPath($path, $type = "path", $addTrailingSlash = true)
{
// if (preg_match("/Windows/i", getenv("OS")) // should we query 'HTTP_USER_AGENT' instead?
// $pathSeparator = '\\'; // is this actually necessary? (I vaguely remember that PHP also uses '/' on Windows)
// else
$pathSeparator = '/';
if ($addTrailingSlash)
$path = preg_replace('#(?<!^|/)$#', $pathSeparator, $path);
return $path;
}
// --------------------------------------------------------------------
// Removes slashes from the input string if 'magic_quotes_gpc = On':
function stripSlashesIfMagicQuotes($sourceString)
{
$magicQuotes = ini_get("magic_quotes_gpc"); // check the value of the 'magic_quotes_gpc' directive in 'php.ini'
if ($magicQuotes) // magic_quotes_gpc = On
$sourceString = convertSlashes($sourceString);
return $sourceString;
}
// --------------------------------------------------------------------
// Fix escape sequences within a string (i.e., remove 'unwanted' slashes):
function convertSlashes($sourceString)
{
// $sourceString = stripslashes($sourceString);
// Note that function 'stripslashes()' cannot be used here since it may remove too many slashes!
// As an example, assume a user input in 'show.php' like this:
//
// <my cite_key> ... <is within list> ... Mock++1997Bacteria
//
// 'Mock++1997Bacteria' gets preg_quote()d in 'show.php' to 'Mock\+\+1997Bacteria'. This
// is necessary to escape any potential grep metacharacters inside the user's cite keys.
//
// So, for an input of '^(Mock\+\+1997Bacteria)$', following scenario will occur with 'magic_quotes_gpc = On':
//
// Case 1 ('convertSlashes()' uses 'stripslashes()'):
// 'show.php' -> 'quote_smart()' -> 'stripSlashesIfMagicQuotes()' -> 'convertSlashes()': ^(Mock++1997Bacteria)$ -> this step incorrectly strips the slashes!
// 'show.php' -> 'quote_smart()' -> 'escapeSQL()': ^(Mock++1997Bacteria)$
// 'show.php' -> 'quote_smart()': "^(Mock++1997Bacteria)$"
// 'search.php' receives: \"^(Mock++1997Bacteria)$\"
// 'search.php' -> 'verifySQLQuery()' -> 'stripSlashesIfMagicQuotes()' -> 'convertSlashes()': "^(Mock++1997Bacteria)$"
//
// Case 2 ('convertSlashes()' uses 'str_replace'):
// 'show.php' -> 'quote_smart()' -> 'stripSlashesIfMagicQuotes()' -> 'convertSlashes()': ^(Mock\+\+1997Bacteria)$
// 'show.php' -> 'quote_smart()' -> 'escapeSQL()': ^(Mock\\+\\+1997Bacteria)$
// 'show.php' -> 'quote_smart()': "^(Mock\\+\\+1997Bacteria)$"
// 'search.php' receives: \"^(Mock\\\\+\\\\+1997Bacteria)$\"
// 'search.php' -> 'verifySQLQuery()' -> 'stripSlashesIfMagicQuotes()' -> 'convertSlashes()': "^(Mock\\+\\+1997Bacteria)$"
//
// This means that 'stripslashes()' fails while the code below seems to work:
$sourceString = str_replace('\"', '"', $sourceString); // replace any \" with "
$sourceString = str_replace("\\'", "'", $sourceString); // replace any \' with '
$sourceString = str_replace("\\\\", "\\", $sourceString);
// $sourceString = preg_replace('/(\\\\)+/i', '\\\\', $sourceString); // instead of the previous line, this would kinda work if SQL strings aren't quote_smart()ed
return $sourceString;
}
// --------------------------------------------------------------------
// Perform search & replace actions on the given text input:
// ('$includesSearchPatternDelimiters' must be a boolean value that specifies whether the leading and trailing slashes
// are included within the search pattern ['true'] or not ['false'])
function searchReplaceText($searchReplaceActionsArray, $sourceString, $includesSearchPatternDelimiters)
{
// this allows to use '$loc' within the replacement pattern
// e.g. like this: array("/(.+)/e" => "\$loc['\\1']")
global $loc; // '$loc' is made globally available in 'core.php'
// apply the search & replace actions defined in '$searchReplaceActionsArray' to the text passed in '$sourceString':
foreach ($searchReplaceActionsArray as $searchString => $replaceString)
{
if (!$includesSearchPatternDelimiters)
$searchString = "/" . $searchString . "/"; // add search pattern delimiters
if (preg_match($searchString, $sourceString))
$sourceString = preg_replace($searchString, $replaceString, $sourceString);
}
return $sourceString;
}
// --------------------------------------------------------------------
// Perform case transformations on the given text input:
// ('$transformation' must be either 'lower', 'upper', 'title' or 'heading')
//
// NOTE: For UTF-8, the PHP functions 'strtolower()' and 'strtoupper()' will only work correctly
// if the server has locales installed which support UTF-8! More info is available at:
// <http://www.phpwact.org/php/i18n/charsets>
// <http://www.phpwact.org/php/i18n/utf-8>
//
// TODO: Implement function 'changeCase()' so that it always works for UTF-8
// See e.g. functions 'utf8_strtolower()' and 'utf8_strtoupper()' at
// <http://dev.splitbrain.org/view/darcs/dokuwiki/inc/utf8.php>
function changeCase($transformation, $sourceString)
{
if (preg_match("/lower/i", $transformation)) // change source text to lower case
$sourceString = strtolower($sourceString);
elseif (preg_match("/upper/i", $transformation)) // change source text to upper case
$sourceString = strtoupper($sourceString);
elseif (preg_match("/title/i", $transformation)) // change source text to title case
$sourceString = preg_replace("/\b(\w)(\w+)/e", "strtoupper('\\1').strtolower('\\2')", $sourceString); // the 'e' modifier allows to execute PHP code within the replacement pattern
elseif (preg_match("/heading/i", $transformation)) // change source text to heading case (opposed to 'title', we only touch words with more than 3 chars, and we only change the case of the first letter but not any subsequent ones)
$sourceString = preg_replace("/\b(\w)(\w{3,})/e", "strtoupper('\\1').'\\2'", $sourceString); // the 'e' modifier allows to execute PHP code within the replacement pattern
return $sourceString;
}
// --------------------------------------------------------------------
// Sets the system's locale information:
// On *NIX systems, use "locale -a" on the command line to display all locales
// supported on your system. See <http://www.php.net/setlocale> for more information.
function setSystemLocale($charSet = "", $systemLocales = "NONE")
{
global $contentTypeCharset; // these variables are defined in 'ini.inc.php'
global $convertExportDataToUTF8;
if (empty($charSet))
$charSet = $contentTypeCharset;
if ($systemLocales == "NONE") {
if ($charSet == "UTF-8")
$systemLocales = array('en_US.UTF-8', 'en_GB.UTF-8', 'en_CA.UTF-8', 'en_AU.UTF-8', 'en_NZ.UTF-8', 'de_DE.UTF-8', 'fr_FR.UTF-8', 'es_ES.UTF-8');
else // we assume "ISO-8859-1" by default
$systemLocales = array('en_US.ISO8859-1', 'en_GB.ISO8859-1', 'en_CA.ISO8859-1', 'en_AU.ISO8859-1', 'en_NZ.ISO8859-1', 'de_DE.ISO8859-1', 'fr_FR.ISO8859-1', 'es_ES.ISO8859-1');
}
setlocale(LC_COLLATE, $systemLocales); // set locale for string comparison (including pattern matching)
setlocale(LC_CTYPE, $systemLocales); // set locale for character classification and conversion, for example 'strtoupper()'
// get the current settings without affecting them:
$systemLocaleCollate = setlocale(LC_COLLATE, "0");
$systemLocaleCType = setlocale(LC_CTYPE, "0");
return array($systemLocaleCollate, $systemLocaleCType);
}
// --------------------------------------------------------------------
// Sets the mimetype & character encoding in the header:
function setHeaderContentType($contentType, $contentTypeCharset)
{
header('Content-type: ' . $contentType . '; charset=' . $contentTypeCharset);
}
// --------------------------------------------------------------------
// Set HTTP status response
// From user 'Ciantic' found at <http://www.php.net/header> (24-Dec-2005 03:07)
// This contains all HTTP status responses defined in RFC2616 section 6.1.1
// See <http://www.w3.org/Protocols/rfc2616/rfc2616-sec6.html#sec6.1.1>
function setHTTPStatus($statusCode)
{
// HTTP Protocol defined status codes:
static $http = array(100 => "HTTP/1.1 100 Continue",
101 => "HTTP/1.1 101 Switching Protocols",
200 => "HTTP/1.1 200 OK",
201 => "HTTP/1.1 201 Created",
202 => "HTTP/1.1 202 Accepted",
203 => "HTTP/1.1 203 Non-Authoritative Information",
204 => "HTTP/1.1 204 No Content",
205 => "HTTP/1.1 205 Reset Content",
206 => "HTTP/1.1 206 Partial Content",
300 => "HTTP/1.1 300 Multiple Choices",
301 => "HTTP/1.1 301 Moved Permanently",
302 => "HTTP/1.1 302 Found",
303 => "HTTP/1.1 303 See Other",
304 => "HTTP/1.1 304 Not Modified",
305 => "HTTP/1.1 305 Use Proxy",
307 => "HTTP/1.1 307 Temporary Redirect",
400 => "HTTP/1.1 400 Bad Request",
401 => "HTTP/1.1 401 Unauthorized",
402 => "HTTP/1.1 402 Payment Required",
403 => "HTTP/1.1 403 Forbidden",
404 => "HTTP/1.1 404 Not Found",
405 => "HTTP/1.1 405 Method Not Allowed",
406 => "HTTP/1.1 406 Not Acceptable",
407 => "HTTP/1.1 407 Proxy Authentication Required",
408 => "HTTP/1.1 408 Request Time-out",
409 => "HTTP/1.1 409 Conflict",
410 => "HTTP/1.1 410 Gone",
411 => "HTTP/1.1 411 Length Required",
412 => "HTTP/1.1 412 Precondition Failed",
413 => "HTTP/1.1 413 Request Entity Too Large",
414 => "HTTP/1.1 414 Request-URI Too Large",
415 => "HTTP/1.1 415 Unsupported Media Type",
416 => "HTTP/1.1 416 Requested range not satisfiable",
417 => "HTTP/1.1 417 Expectation Failed",
500 => "HTTP/1.1 500 Internal Server Error",
501 => "HTTP/1.1 501 Not Implemented",
502 => "HTTP/1.1 502 Bad Gateway",
503 => "HTTP/1.1 503 Service Unavailable",
504 => "HTTP/1.1 504 Gateway Time-out");
header($http[$statusCode]);
}
// --------------------------------------------------------------------
// This function takes the URL given in '$sourceURL' and retrieves the returned data:
function fetchDataFromURL($sourceURL)
{
global $errors;
$handle = fopen($sourceURL, "r"); // fetch data from URL in read mode
$sourceData = "";
if ($handle)
{
while (!feof($handle))
{
$sourceData .= fread($handle, 4096); // read data in chunks
}
fclose($handle);
}
else
{
$errorMessage = "Error occurred: Failed to open " . $sourceURL; // network error
if (!isset($errors["sourceText"]))
$errors["sourceText"] = $errorMessage;
else
$errors["sourceText"] = $errors["sourceText"] . "<br>" . $errorMessage;
}
return $sourceData;
}
// --------------------------------------------------------------------
// Send '$dataString' as POST request (using the 'application/x-www-form-urlencoded'
// content type) to the given '$host'/'$path':
function sendPostRequest($host, $path, $referer, $dataString)
{
$port = 80; // server port to be used with the connection
$timeout = 600; // connection time out in seconds
$result = "";
// build header:
$header = "POST " . $path . " HTTP/1.0\r\n" // "HTTP/1.1" would return data with "Transfer-Encoding: chunked"
. "Host: " . $host . "\r\n"
. "Referer: " . $referer . "\r\n"
. "Content-Type: application/x-www-form-urlencoded\r\n"
. "Content-Length: ". strlen($dataString) ."\r\n"
. "\r\n";
// open connection:
// see <http://www.php.net/fsockopen>
$fp = fsockopen($host, $port, $errorNo, $errorMsg, $timeout);
if (!$fp)
{
$result = "Error $errorNo : $errorMsg";
}
else
{
// POST data:
fputs($fp, $header . $dataString);
// read result:
while (!feof($fp))
$result .= fgets($fp, 1024);
// close connection:
fclose($fp);
}
return $result;
}
// --------------------------------------------------------------------
// Detect character encoding:
// NOTE: - Currently, this function only distinguishes between ISO-8859-1 and UTF-8!
function detectCharacterEncoding($sourceString, $detectOrder = "")
{
// Method A:
// Function 'mb_detect_encoding()' requires PHP with multi-byte support (i.e., PHP must
// be compiled with the '--enable-mbstring' configure option).
// (see: <http://php.net/manual/en/function.mb-detect-encoding.php>)
// $charSet = "";
// if (empty($detectOrder))
// Set the default character encoding detection order:
// (see: <http://www.php.net/mb-detect-order>)
// $detectOrder = implode(", ", mb_detect_order()); // on an English system this may be e.g. "ASCII, UTF-8" which wouldn't be useful in our case
// $detectOrder = "UTF-8, ISO-8859-1"; // in case of refbase, we currently hardcode the detection order
// Detect the character encoding of the given '$sourceString' with the given '$detectOrder':
// $charSet = mb_detect_encoding($sourceString . "a", $detectOrder); // an ASCII char is appended to avoid a bug, see comment by <hoermann dot j at gmail dot com> at <http://php.net/manual/en/function.mb-detect-encoding.php>
// Method B:
// Based on function 'detectUTF8()' by user <chris at w3style dot co dot uk>
// at <http://php.net/manual/en/function.mb-detect-encoding.php>
// (see also: <http://w3.org/International/questions/qa-forms-utf-8.html>)
// Check if a string contains UTF-8 characters:
// NOTE: This regex pattern only looks for non-ASCII multibyte sequences in
// the UTF-8 range and stops once it finds at least one multibytes string.
if (preg_match('%(?:
[\xC2-\xDF][\x80-\xBF] # non-overlong 2-byte
| \xE0[\xA0-\xBF][\x80-\xBF] # excluding overlongs
| [\xE1-\xEC\xEE\xEF][\x80-\xBF]{2} # straight 3-byte
| \xED[\x80-\x9F][\x80-\xBF] # excluding surrogates
| \xF0[\x90-\xBF][\x80-\xBF]{2} # planes 1-3
| [\xF1-\xF3][\x80-\xBF]{3} # planes 4-15
| \xF4[\x80-\x8F][\x80-\xBF]{2} # plane 16
)+%xs', $sourceString))
$charSet = "UTF-8"; // found at least one multibyte UTF-8 character
else
$charSet = "ISO-8859-1";
return $charSet;
}
// --------------------------------------------------------------------
// Convert to character encoding:
// This function converts text that's represented in '$sourceCharset' into the character encoding
// given in '$targetCharset'. If '$sourceCharset' isn't given, we default to the refbase database
// encoding (which is indicated in '$contentTypeCharset'). '$transliteration' must be either
// "TRANSLIT" or "IGNORE" causing characters which are unrecognized by the target charset to get
// either transliterated or ignored, respectively.
function convertToCharacterEncoding($targetCharset, $transliteration, $sourceString, $sourceCharset = "")
{
global $contentTypeCharset; // defined in 'ini.inc.php'
global $transtab_latin1_ascii; // defined in 'transtab_latin1_ascii.inc.php'
global $transtab_unicode_ascii; // defined in 'transtab_unicode_ascii.inc.php'
global $transtab_unicode_latin1; // defined in 'transtab_unicode_latin1.inc.php'
global $transtab_unicode_refbase; // defined in 'transtab_unicode_refbase.inc.php'
if (empty($sourceCharset))
$sourceCharset = $contentTypeCharset;
// In case of ISO-8859-1/UTF-8 to ASCII conversion we attempt to transliterate non-ASCII chars,
// comparable to the fallback notations that people use commonly in email and on typewriters to
// represent unavailable characters:
if (($targetCharset == "ASCII") AND ($transliteration == "TRANSLIT"))
{
if ($sourceCharset == "UTF-8")
$convertedString = searchReplaceText($transtab_unicode_ascii, $sourceString, false);
else // we assume "ISO-8859-1" by default
$convertedString = searchReplaceText($transtab_latin1_ascii, $sourceString, false);
// Strip any additional non-ASCII characters which we weren't able to transliterate:
$convertedString = iconv($sourceCharset, "ASCII//IGNORE", $convertedString);
// Notes from <http://www.php.net/manual/en/function.iconv.php> regarding "TRANSLIT" and "IGNORE":
// - If you append the string //TRANSLIT to out_charset transliteration is activated.
// This means that when a character can't be represented in the target charset, it can
// be approximated through one or several similarly looking characters. If you append
// the string //IGNORE, characters that cannot be represented in the target charset
// are silently discarded. Otherwise, str is cut from the first illegal character.
}
// Similar to the ISO-8859-1/UTF-8 to ASCII conversion we attempt to transliterate non-latin1 chars when
// converting from UTF-8 to ISO-8859-1.
// NOTE: we don't use 'iconv("UTF-8", "ISO-8859-1//TRANSLIT", $sourceString)' here, since this seems to
// abort the conversion with an error ("Detected an illegal character in input string") if e.g. a
// greek delta character is encountered.
elseif (($targetCharset == "ISO-8859-1") AND ($transliteration == "TRANSLIT") AND ($sourceCharset == "UTF-8"))
{
// Convert Unicode entities to refbase markup (if possible):
$convertedString = searchReplaceText($transtab_unicode_refbase, $sourceString, true);
// Attempt to transliterate any remaining non-latin1 characters:
$convertedString = searchReplaceText($transtab_unicode_latin1, $convertedString, false);
// Strip any additional non-latin1 characters which we weren't able to transliterate:
$convertedString = iconv($sourceCharset, "ISO-8859-1//IGNORE", $convertedString);
}
else
$convertedString = iconv($sourceCharset, "$targetCharset//$transliteration", $sourceString);
return $convertedString;
}
// --------------------------------------------------------------------
// Encode HTML entities:
// (this custom function is provided so that it'll be easier to change the way how entities are HTML encoded later on)
function encodeHTML($sourceString)
{
global $contentTypeCharset; // defined in 'ini.inc.php'
// Note: Using PHP 5.0.4, I couldn't get 'htmlentities()' to work properly with UTF-8. Apparently, versions before
// PHP 4.3.11 and PHP 5.0.4 had a partially incorrect utf8 to htmlentities mapping (see <http://bugs.php.net/28067>),
// however, PHP 5.0.4 still seems buggy to me. Therefore, in case of UTF-8, we'll use 'mb_convert_encoding()' instead.
// IMPORTANT: this requires multi-byte support enabled on your PHP server!
// (i.e., PHP must be compiled with the '--enable-mbstring' configure option)
if ($contentTypeCharset == "UTF-8")
{
// encode HTML special chars
$sourceString = encodeHTMLspecialchars($sourceString);
// converts from 'UTF-8' to 'HTML-ENTITIES' (see: <http://php.net/manual/en/function.mb-convert-encoding.php>)
$encodedString = mb_convert_encoding($sourceString, 'HTML-ENTITIES', "$contentTypeCharset");
}
else
$encodedString = htmlentities($sourceString, ENT_COMPAT, "$contentTypeCharset");
// Notes from <http://www.php.net/htmlentities>:
//
// - The optional second parameter lets you define what will be done with 'single' and "double" quotes.
// It takes on one of three constants with the default being ENT_COMPAT:
// ENT_COMPAT: Will convert double-quotes and leave single-quotes alone.
// ENT_QUOTES: Will convert both double and single quotes.
// ENT_NOQUOTES: Will leave both double and single quotes unconverted.
//
// - The optional third argument defines the character set used in conversion. Support for this argument
// was added in PHP 4.1.0. Presently, the ISO-8859-1 character set is used as the default.
return $encodedString;
}
// --------------------------------------------------------------------
// Encode HTML special chars:
// As opposed to the 'encodeHTML()' function this function will only convert the characters supported by the
// 'htmlspecialchars()' function:
// - '&' (ampersand) becomes '&amp;'
// - '"' (double quote) becomes '&quot;' when ENT_NOQUOTES is not set
// - ''' (single quote) becomes '&#039;' only when ENT_QUOTES is set
// - '<' (less than) becomes '&lt;'
// - '>' (greater than) becomes '&gt;'
// Note that these (and only these!) entities are also supported by XML (which is why we use this function within the XML
// generating functions 'generateRSS()', 'modsRecord()' & 'atomEntry()' and leave all other higher ASCII chars unencoded)
function encodeHTMLspecialchars($sourceString)
{
global $contentTypeCharset; // defined in 'ini.inc.php'
$encodedString = htmlspecialchars($sourceString, ENT_COMPAT, "$contentTypeCharset");
// Notes from <http://www.php.net/htmlspecialchars>:
//
// - The optional second parameter lets you define what will be done with 'single' and "double" quotes.
// It takes on one of three constants with the default being ENT_COMPAT:
// ENT_COMPAT: Will convert double-quotes and leave single-quotes alone.
// ENT_QUOTES: Will convert both double and single quotes.
// ENT_NOQUOTES: Will leave both double and single quotes unconverted.
//
// - The optional third argument defines the character set used in conversion. Support for this argument
// was added in PHP 4.1.0. Presently, the ISO-8859-1 character set is used as the default.
return $encodedString;
}
// --------------------------------------------------------------------
// Decode HTML entities:
// This function converts HTML entities in '$sourceString' to the character encoding given in '$targetCharset'.
// It is intended to work similar to function 'html_entity_decode()' but should also support conversion of numeric
// entities as well as UTF-8 on PHP 4. In case of refbase, '$targetCharset' should be either "UTF-8" or "ISO-8859-1".
function decodeHTML($targetCharset, $sourceString)
{
global $contentTypeCharset; // defined in 'ini.inc.php'
static $transtab_HTML;
// Method A:
// Function 'html_entity_decode()' is available since PHP 4.3.0, but UTF-8 support was only added with PHP 5?
// (see <http://www.php.net/html-entity-decode>)
// NOTE: This function doesn't convert numeric entities, so, if used, it should be combined with the code block
// underneath "Replace numeric entities" below.
// $convertedString = html_entity_decode($sourceString, ENT_QUOTES, "$targetCharset");
// W.r.t. the second parameter, see notes underneath the call to 'htmlentities()' in function 'encodeHTML()'
// Method B:
// Function 'mb_convert_encoding()' requires PHP with multi-byte support (i.e., PHP must be compiled with the
// '--enable-mbstring' configure option). Converts from 'HTML-ENTITIES' to '$targetCharset'.
// (see: <http://php.net/manual/en/function.mb-convert-encoding.php>)
// NOTE: Compared to methods A + C, this seems to yield different results! ?:-/
// $convertedString = mb_convert_encoding($sourceString, "$targetCharset", 'HTML-ENTITIES');
// Method C:
// Assembled from user contributions at <http://www.php.net/html-entity-decode>
// - Replace numeric entities:
$convertedString = preg_replace('/&#x0*([0-9a-f]+);/ei', "charNumToCharString('$targetCharset', hexdec('\\1'))", $sourceString); // hex notation
$convertedString = preg_replace('/&#0*([0-9]+);/e', "charNumToCharString('$targetCharset', '\\1')", $convertedString); // decimal notation
// - Replace literal entities:
if (!isset($transtab_HTML))
{
// Get the translation table that's used by function 'htmlspecialchars()':
$transtab_HTML = get_html_translation_table(HTML_ENTITIES, ENT_QUOTES);
$transtab_HTML = array_flip($transtab_HTML);
// Change the translation table from latin1 to UTF-8 (if necessary):
if ($targetCharset == "UTF-8")
foreach ($transtab_HTML as $key => $value)
$transtab_HTML[$key] = utf8_encode($value); // encode ISO-8859-1 char as UTF-8
}
$convertedString = strtr($convertedString, $transtab_HTML);
return $convertedString;
}
// --------------------------------------------------------------------
// Decode HTML special chars:
// As opposed to the 'decodeHTML()' function this function will only decode the characters supported by the
// 'htmlspecialchars()' function:
// - '&amp;' (ampersand) becomes '&'
// - '&quot;' (double quote) becomes '"' when ENT_NOQUOTES is not set
// - '&#039;' (single quote) becomes ''' only when ENT_QUOTES is set
// - '&lt;' (less than) becomes '<'
// - '&gt;' (greater than) becomes '>'
function decodeHTMLspecialchars($sourceString)
{
static $transtab_HTMLspecialchars;
// Method A:
// Function 'htmlspecialchars_decode()' seems to be available since PHP 5.1.0.
// (see <http://www.php.net/htmlspecialchars-decode>)
// $decodedString = htmlspecialchars_decode($sourceString, ENT_QUOTES);
// W.r.t. the second parameter, see notes underneath the call to 'htmlspecialchars()' in function 'encodeHTMLspecialchars()'
// Method B:
// Assembled from user contributions at <http://www.php.net/htmlspecialchars-decode>
if (!isset($transtab_HTMLspecialchars))
{
// Get the translation table that's used by function 'htmlspecialchars()':
$transtab_HTMLspecialchars = get_html_translation_table(HTML_SPECIALCHARS, ENT_QUOTES);
$transtab_HTMLspecialchars = array_flip($transtab_HTMLspecialchars);
if (!isset($transtab_HTMLspecialchars['&#039;'])) // we need to add '&#039;' since the above call to 'get_html_translation_table()' returns just '&#39;'
$transtab_HTMLspecialchars['&#039;'] = "'";
}
$decodedString = strtr($sourceString, $transtab_HTMLspecialchars);
return $decodedString;
}
// --------------------------------------------------------------------
// Returns the character string that corresponds to the given character code value:
// (modified after user contributions by <akniep at rayo dot info>, <aurynas dot butkus at gmail dot com>
// and <romans at void dot lv> at <http://www.php.net/html-entity-decode>)
// NOTE: - In case of refbase, '$targetCharset' should be either "UTF-8" or "ISO-8859-1"
// - For a latin1-based database, we'll convert any Unicode-only entities into the
// corresponding refbase markup (if possible), and any remaining UTF-8 characters
// will be converted to their ASCII equivalents.
function charNumToCharString($targetCharset, $num)
{
global $transtab_unicode_ascii; // defined in 'transtab_unicode_ascii.inc.php'
global $transtab_unicode_refbase; // defined in 'transtab_unicode_refbase.inc.php'
// Generates a UTF-8 string that corresponds to the given Unicode value:
if ($num < 0)
$utfChar = '';
elseif ($num < 128)
$utfChar = chr($num);
elseif ($num < 2048)
$utfChar = chr(($num >> 6) + 192) . chr(($num & 63) + 128);
elseif ($num < 65536)
$utfChar = chr(($num >> 12) + 224) . chr((($num >> 6) & 63) + 128) . chr(($num & 63) + 128);
elseif ($num < 2097152)
$utfChar = chr(($num >> 18) + 240) . chr((($num >> 12) & 63) + 128) . chr((($num >> 6) & 63) + 128) . chr(($num & 63) + 128);
if (!empty($utfChar) AND $targetCharset == "ISO-8859-1")
{
// Convert Unicode entities to refbase markup (if possible):
$utfChar = searchReplaceText($transtab_unicode_refbase, $utfChar, true);
// Convert any remaining UTF-8 characters to their ASCII equivalents:
// TODO: Should we use iconv (function 'convertToCharacterEncoding()') instead?
$utfChar = searchReplaceText($transtab_unicode_ascii, $utfChar, false);
}
return $utfChar;
}
// --------------------------------------------------------------------
// Encode special chars, perform charset conversions (if necessary)
// and apply any field-specific search & replace actions:
// ('$targetFormat' must be either "HTML" or "XML")
function encodeField($fieldName, $fieldValue, $localSearchReplaceActionsArray = array(), $encodingExceptionsArray = array(), $encode = true, $targetFormat = "HTML")
{
global $contentTypeCharset; // these variables are defined in 'ini.inc.php'
global $convertExportDataToUTF8;
global $searchReplaceActionsArray;
if (($encode) AND (!in_array($fieldName, $encodingExceptionsArray)))
{
if ($targetFormat == "HTML")
{
// Encode non-ASCII chars as HTML entities:
$fieldValue = encodeHTML($fieldValue);
}
elseif ($targetFormat == "XML")
{
// Only convert those special chars to entities which are supported by XML:
$fieldValue = encodeHTMLspecialchars($fieldValue);
// Convert field data to UTF-8:
if (($convertExportDataToUTF8 == "yes") AND ($contentTypeCharset != "UTF-8"))
$fieldValue = convertToCharacterEncoding("UTF-8", "IGNORE", $fieldValue);
}
}
// Apply *locally* defined search & replace 'actions' to all fields that are listed
// in the 'fields' element of the arrays contained in '$localSearchReplaceActionsArray':
foreach ($localSearchReplaceActionsArray as $fieldActionsArray)
if (in_array($fieldName, $fieldActionsArray['fields']))
$fieldValue = searchReplaceText($fieldActionsArray['actions'], $fieldValue, true);
if ($targetFormat == "HTML")
{
// Apply *globally* defined search & replace 'actions' to all fields that are listed
// in the 'fields' element of the arrays contained in '$searchReplaceActionsArray':
foreach ($searchReplaceActionsArray as $fieldActionsArray)
if (in_array($fieldName, $fieldActionsArray['fields']))
$fieldValue = searchReplaceText($fieldActionsArray['actions'], $fieldValue, true);
}
return $fieldValue;
}
// --------------------------------------------------------------------
// Strip HTML and PHP tags from input string:
// See <http://www.php.net/strip_tags>
function stripTags($sourceString, $allowedTags = "")
{
$cleanedString = strip_tags($sourceString, $allowedTags);
return $cleanedString;
}
// --------------------------------------------------------------------
// Verify the SQL query specified by the user and modify it if security concerns are encountered:
// (this function does add/remove user-specific query code as required and will fix problems with escape sequences within the SQL query)
function verifySQLQuery($sqlQuery, $referer, $displayType, $showLinks)
{
global $loginEmail;
global $loginUserID;
global $fileVisibility; // these variables are specified in 'ini.inc.php'
global $librarySearchPattern;
global $showAdditionalFieldsDetailsViewDefault;
global $showUserSpecificFieldsDetailsViewDefault;
global $tableRefs, $tableUserData; // defined in 'db.inc.php'
global $loc; // '$loc' is made globally available in 'core.php'
// note that, if several errors occur, only the last error message will be displayed
// disallow display/querying of the 'file' field if NONE of the following conditions are met:
// - the variable '$fileVisibility' (defined in 'ini.inc.php') is set to 'everyone'
// - the variable '$fileVisibility' is set to 'login' AND the user is logged in
// - the variable '$fileVisibility' is set to 'user-specific' AND the 'user_permissions' session variable contains 'allow_download'
if (!($fileVisibility == "everyone" OR ($fileVisibility == "login" AND isset($_SESSION['loginEmail'])) OR ($fileVisibility == "user-specific" AND (isset($_SESSION['user_permissions']) AND preg_match("/allow_download/", $_SESSION['user_permissions'])))))
{
// remove 'file' field from SQL query:
$sqlQuery = stripFieldFromSQLQuery($sqlQuery, "file", true);
}
// disallow display/querying of the 'location' field if the user is NOT logged in:
// (this is mostly done to shield user email addresses from exposure to search engines and/or email harvesting robots)
if (!isset($_SESSION['loginEmail']))
{
// remove 'location' field from SQL query:
$sqlQuery = stripFieldFromSQLQuery($sqlQuery, "location", true);
}
// supply generic 'WHERE' clause if it didn't exist in the SELECT query:
if (preg_match("/^SELECT/i", $sqlQuery) AND !preg_match("/ FROM " . $tableRefs . ".* WHERE /i", $sqlQuery))
$sqlQuery = preg_replace("/(?= ORDER BY| LIMIT| GROUP BY| HAVING| PROCEDURE| FOR UPDATE| LOCK IN|$)/i", " WHERE serial RLIKE \".+\"", $sqlQuery, 1);
// supply generic 'ORDER BY' clause if it didn't exist in the SELECT query:
// TODO: - add a suitable 'ORDER BY' clause for Browse view and if '$citeOrder != "author"'
if (preg_match("/^SELECT/i", $sqlQuery) AND !preg_match("/ FROM " . $tableRefs . ".* ORDER BY /i", $sqlQuery) AND ($displayType != "Browse"))
$sqlQuery = preg_replace("/(?= LIMIT| GROUP BY| HAVING| PROCEDURE| FOR UPDATE| LOCK IN|$)/i", " ORDER BY author, year DESC, publication", $sqlQuery, 1);
// handle the display & querying of user-specific fields:
if (!isset($_SESSION['loginEmail'])) // if NO user is logged in...
{
// ... and any user-specific fields are part of the SELECT or ORDER BY statement...
if ((empty($referer) OR preg_match("#.+search\.php#i",$referer)) AND (preg_match("/(SELECT |ORDER BY |, *)(marked|copy|selected|user_keys|user_notes|user_file|user_groups|cite_key|related)/i",$sqlQuery))) // if the calling script ends with 'search.php' (i.e., is NOT 'show.php' or 'sru.php', see note below!) AND any user-specific fields are part of the SELECT or ORDER BY clause
{
// if the 'SELECT' clause contains any user-specific fields:
if (preg_match("/SELECT(.(?!FROM))+?(marked|copy|selected|user_keys|user_notes|user_file|user_groups|cite_key|related)/i",$sqlQuery))
{
// return an appropriate error message:
// note: we don't write out any error message if the user-specific fields do only occur within the 'ORDER' clause (but not within the 'SELECT' clause)
$HeaderString = returnMsg($loc["Warning_DisplayUserSpecificFieldsOmitted"] . "!", "warning", "strong", "HeaderString");
}
$sqlQuery = preg_replace("/(SELECT|ORDER BY) (marked|copy|selected|user_keys|user_notes|user_file|user_groups|cite_key|related)( DESC)?/i", "\\1 ", $sqlQuery); // ...delete any user-specific fields from beginning of 'SELECT' or 'ORDER BY' clause
$sqlQuery = preg_replace("/, *(marked|copy|selected|user_keys|user_notes|user_file|user_groups|cite_key|related)( DESC)?/i", "", $sqlQuery); // ...delete any remaining user-specific fields from 'SELECT' or 'ORDER BY' clause
$sqlQuery = preg_replace("/(SELECT|ORDER BY) *, */i", "\\1 ", $sqlQuery); // ...remove any field delimiters that directly follow the 'SELECT' or 'ORDER BY' terms
$sqlQuery = preg_replace("/SELECT *(?=FROM)/i", buildSELECTclause("", "", "", false, false) . " ", $sqlQuery); // ...supply generic 'SELECT' clause if it did ONLY contain user-specific fields
$sqlQuery = preg_replace("/ORDER BY *(?=LIMIT|GROUP BY|HAVING|PROCEDURE|FOR UPDATE|LOCK IN|$)/i", "ORDER BY author, year DESC, publication", $sqlQuery); // ...supply generic 'ORDER BY' clause if it did ONLY contain user-specific fields
}
// ... and the 'LEFT JOIN...' statement is part of the 'FROM' clause...
if ((preg_match("#.+search\.php#i",$referer)) AND (preg_match("/LEFT JOIN $tableUserData/i",$sqlQuery))) // if the calling script ends with 'search.php' (i.e., is NOT 'show.php' or 'sru.php', see note below!) AND the 'LEFT JOIN...' statement is part of the 'FROM' clause...
$sqlQuery = preg_replace("/FROM $tableRefs LEFT JOIN.+WHERE/i","FROM $tableRefs WHERE",$sqlQuery); // ...delete 'LEFT JOIN...' part from 'FROM' clause
// ... and any user-specific fields are part of the WHERE clause...
if ((preg_match("#.+search\.php#i",$referer) OR preg_match("/^RSS$/i",$displayType)) AND (preg_match("/WHERE.+(marked|copy|selected|user_keys|user_notes|user_file|user_groups|cite_key|related)/i",$sqlQuery))) // if a user who's NOT logged in tries to query user-specific fields (by use of 'sql_search.php')...
// Note that the script 'show.php' may query the user-specific field 'selected' (e.g., by URLs of the form: 'show.php?author=...&userID=...&only=selected')
// but since (in that case) the '$referer' variable is either empty or does not end with 'search.php' this if clause will not apply (which is ok since we want to allow 'show.php' to query the 'selected' field).
// The same applies in the case of 'sru.php' which may query the user-specific field 'cite_key' (e.g., by URLs like: 'sru.php?version=1.1&query=bib.citekey=...&x-info-2-auth1.0-authenticationToken=email=...')
// Note that this also implies that a user who's not logged in might perform a query such as: 'http://localhost/refs/show.php?cite_key=...&userID=...'
{
// Note: in the patterns below we'll attempt to account for parentheses but this won't catch all cases!
$sqlQuery = preg_replace("/WHERE( *\( *?)* *(marked|copy|selected|user_keys|user_notes|user_file|user_groups|cite_key|related).+?(?= (AND|OR)\b| ORDER BY| LIMIT| GROUP BY| HAVING| PROCEDURE| FOR UPDATE| LOCK IN|$)/i","WHERE\\1",$sqlQuery); // ...delete any user-specific fields from 'WHERE' clause
$sqlQuery = preg_replace("/( *\( *?)*( *(AND|OR)\b)? *(marked|copy|selected|user_keys|user_notes|user_file|user_groups|cite_key|related).+?(?=( *\) *?)* +((AND|OR)\b|ORDER BY|LIMIT|GROUP BY|HAVING|PROCEDURE|FOR UPDATE|LOCK IN|$))/i","\\1",$sqlQuery); // ...delete any user-specific fields from 'WHERE' clause
$sqlQuery = preg_replace("/WHERE( *\( *?)* *(AND|OR)\b/i","WHERE\\1",$sqlQuery); // ...delete any superfluous 'AND' or 'OR' that wasn't removed properly by the two regex patterns above
$sqlQuery = preg_replace("/WHERE( *\( *?)*(?= ORDER BY| LIMIT| GROUP BY| HAVING| PROCEDURE| FOR UPDATE| LOCK IN|$)/i","WHERE serial RLIKE \".+\"",$sqlQuery); // ...supply generic 'WHERE' clause if it did ONLY contain user-specific fields
// return an appropriate error message:
$HeaderString = returnMsg($loc["Warning_QueryUserSpecificFieldsOmitted"] . "!", "warning", "strong", "HeaderString");
}
}
else // if a user is logged in...
{
if (preg_match("/LEFT JOIN $tableUserData/i",$sqlQuery)) // if the 'LEFT JOIN...' statement is part of the 'FROM' clause...
{
// ...and any user-specific fields other(!) than the 'selected' or 'cite_key' field are part of the 'SELECT' or 'WHERE' clause...
// Note that we exclude the 'selected' field here (although it is user-specific). By that we allow the 'selected' field to be queried by every user who's logged in.
// This is done to support the 'show.php' script which may query the user-specific field 'selected' (e.g., by URLs of the form: 'show.php?author=...&userID=...&only=selected')
// Similarly, we exclude 'cite_key' here to allow every user to query other user's 'cite_key' fields using 'sru.php' (e.g., by URLs like: 'sru.php?version=1.1&query=bib.citekey=...&x-info-2-auth1.0-authenticationToken=email=...')
if (preg_match("/, (marked|copy|user_keys|user_notes|user_file|user_groups|related)/i",$sqlQuery) OR preg_match("/WHERE.+(marked|copy|user_keys|user_notes|user_file|user_groups|related)/i",$sqlQuery))
{
$sqlQuery = preg_replace("/user_id *= *[0-9]+/i","user_id = $loginUserID",$sqlQuery); // ...replace any other user ID with the ID of the currently logged in user
if (!empty($librarySearchPattern) AND !(preg_match("/^location$/i",$librarySearchPattern[0]) AND preg_match("/location RLIKE " . quote_smart($librarySearchPattern[1]) . "/i",$sqlQuery))) // don't replace the 'location' part of the WHERE clause if it stems from variable '$librarySearchPattern' in 'ini.inc.php' (NOTE: this is quite hacky! :-/)
$sqlQuery = preg_replace("/location RLIKE .+?(?= (AND|OR)\b| ORDER BY| LIMIT| GROUP BY| HAVING| PROCEDURE| FOR UPDATE| LOCK IN|$)/i","location RLIKE " . quote_smart($loginEmail),$sqlQuery); // ...replace any other user email address with the login email address of the currently logged in user
}
}
// if we're going to display record details for a logged in user, we have to ensure the display of the 'location' field as well as the user-specific fields (which may have been deleted from a query due to a previous logout action);
// in 'Display Details' view, the 'call_number' and 'serial' fields are the last generic fields before any user-specific fields:
if (((preg_match("/^Display$/i",$displayType) AND ($showUserSpecificFieldsDetailsViewDefault == "yes")) OR (preg_match("/^Export$/i",$displayType))) AND (preg_match("/, call_number, serial FROM $tableRefs/i",$sqlQuery))) // if the user-specific fields are missing from the SELECT statement...
$sqlQuery = preg_replace("/, call_number, serial FROM $tableRefs/i",", call_number, serial, marked, copy, selected, user_keys, user_notes, user_file, user_groups, cite_key, related FROM $tableRefs",$sqlQuery); // ...add all user-specific fields to the 'SELECT' clause
// in 'Display Details' view, the 'location' field should occur within the SELECT statement before the 'call_number' and 'serial' fields:
// if (((preg_match("/^Display$/i",$displayType) AND ($showAdditionalFieldsDetailsViewDefault == "yes")) OR (preg_match("/^Export$/i",$displayType))) AND (preg_match("/(?<!location,) call_number, serial(?=(, marked, copy, selected, user_keys, user_notes, user_file, user_groups, cite_key, related)? FROM $tableRefs)/i",$sqlQuery))) // if the 'location' field is missing from the SELECT statement...
// $sqlQuery = preg_replace("/(?<!location), call_number, serial(?=(, marked, copy, selected, user_keys, user_notes, user_file, user_groups, cite_key, related)? FROM $tableRefs)/i",", location, call_number, serial",$sqlQuery); // ...add the 'location' field to the 'SELECT' clause
// NOTE: I've commented the above code block for now, since, for '$showAdditionalFieldsDetailsViewDefault=yes' with additional fields being hidden, it causes the 'location' field to appear when clicking any of the sort/browse/view links
// The drawback is that the 'location' field isn't added to the SQL query now when a record in Details view is reloaded after an anonymous user did view the record in Details view and then decided to log in
if ((preg_match("/^(Cite|Display|Export|RSS)$/i",$displayType)) AND (!preg_match("/LEFT JOIN $tableUserData/i",$sqlQuery))) // if the 'LEFT JOIN...' statement isn't already part of the 'FROM' clause...
$sqlQuery = preg_replace("/ FROM $tableRefs/i"," FROM $tableRefs LEFT JOIN $tableUserData ON serial = record_id AND user_id = $loginUserID",$sqlQuery); // ...add the 'LEFT JOIN...' part to the 'FROM' clause
}
// restrict adding of columns to SELECT queries (so that 'DELETE FROM refs ...' statements won't get modified as well);
// we'll also exclude the Browse view since these links aren't needed (and would cause problems) in this view
if (preg_match("/^SELECT/i",$sqlQuery) AND ($displayType != "Browse"))
{
$sqlQuery = preg_replace("/ FROM $tableRefs/i",", orig_record FROM $tableRefs",$sqlQuery); // add 'orig_record' column (which is required in order to present visual feedback on duplicate records)
$sqlQuery = preg_replace("/ FROM $tableRefs/i",", serial FROM $tableRefs",$sqlQuery); // add 'serial' column (which is required in order to obtain unique checkbox names)
if ($showLinks == "1")
$sqlQuery = preg_replace("/ FROM $tableRefs/i",", file, url, doi, isbn, type FROM $tableRefs",$sqlQuery); // add 'file', 'url', 'doi', 'isbn' & 'type' columns
}
// fix escape sequences within the SQL query:
$query = stripSlashesIfMagicQuotes($sqlQuery);
return $query;
}
// --------------------------------------------------------------------
// Removes the field given in '$field' from the SQL query and
// issues a warning if '$issueWarning == true':
// TODO: I18n
function stripFieldFromSQLQuery($sqlQuery, $field, $issueWarning = true)
{
// note that, upon multiple warnings, only the last warning message will be displayed
// if the given '$field' is part of the SELECT or ORDER BY statement...
if (preg_match("/(SELECT |ORDER BY |, *)" . $field . "/i", $sqlQuery))
{
// if the 'SELECT' clause contains '$field':
if ($issueWarning AND (preg_match("/SELECT(.(?!FROM))+?" . $field . "/i", $sqlQuery)))
{
// return an appropriate error message:
// note: we don't write out any error message if the given '$field' does only occur within the 'ORDER' clause (but not within the 'SELECT' clause)
$HeaderString = returnMsg("Display of '" . $field . "' field was omitted!", "warning", "strong", "HeaderString");
}
$sqlQuery = preg_replace("/(SELECT|ORDER BY) " . $field . "( DESC)?/i", "\\1 ", $sqlQuery); // ...delete '$field' from beginning of 'SELECT' or 'ORDER BY' clause
$sqlQuery = preg_replace("/, *" . $field . "( DESC)?/i", "", $sqlQuery); // ...delete any other occurrences of '$field' from 'SELECT' or 'ORDER BY' clause
$sqlQuery = preg_replace("/(SELECT|ORDER BY) *, */i", "\\1 ", $sqlQuery); // ...remove any field delimiters that directly follow the 'SELECT' or 'ORDER BY' terms
$sqlQuery = preg_replace("/SELECT *(?=FROM)/i", buildSELECTclause("", "", "", false, false) . " ", $sqlQuery); // ...supply generic 'SELECT' clause if it did ONLY contain the given '$field'
$sqlQuery = preg_replace("/ORDER BY *(?=LIMIT|GROUP BY|HAVING|PROCEDURE|FOR UPDATE|LOCK IN|$)/i", "ORDER BY author, year DESC, publication", $sqlQuery); // ...supply generic 'ORDER BY' clause if it did ONLY contain the given '$field'
}
// if the given '$field' is part of the WHERE clause...
if (preg_match("/WHERE.+" . $field ."/i", $sqlQuery)) // this simple pattern works since we have already stripped any instance(s) of the given '$field' from the ORDER BY clause
{
// Note: in the patterns below we'll attempt to account for parentheses but this won't catch all cases!
$sqlQuery = preg_replace("/WHERE( *\( *?)* *" . $field . ".+?(?= (AND|OR)\b| ORDER BY| LIMIT| GROUP BY| HAVING| PROCEDURE| FOR UPDATE| LOCK IN|$)/i", "WHERE\\1", $sqlQuery); // ...delete '$field' from 'WHERE' clause
$sqlQuery = preg_replace("/( *\( *?)*( *(AND|OR)\b)? *" . $field . ".+?(?=( *\) *?)* +((AND|OR)\b|ORDER BY|LIMIT|GROUP BY|HAVING|PROCEDURE|FOR UPDATE|LOCK IN|$))/i", "\\1", $sqlQuery); // ...delete '$field' from 'WHERE' clause
$sqlQuery = preg_replace("/WHERE( *\( *?)* *(AND|OR)\b/i","WHERE\\1",$sqlQuery); // ...delete any superfluous 'AND' that wasn't removed properly by the two regex patterns above
$sqlQuery = preg_replace("/WHERE( *\( *?)*(?= ORDER BY| LIMIT| GROUP BY| HAVING| PROCEDURE| FOR UPDATE| LOCK IN|$)/i", "WHERE serial RLIKE \".+\"", $sqlQuery); // ...supply generic 'WHERE' clause if it did ONLY contain the given '$field'
if ($issueWarning)
{
// return an appropriate error message:
$HeaderString = returnMsg("Querying of '" . $field . "' field was omitted!", "warning", "strong", "HeaderString");
}
}
return $sqlQuery;
}
// --------------------------------------------------------------------
// this function uses 'mysqli_real_escape_string()' to:
// - prepend backslashes to \, ', "
// - replace the characters \x00, \n, \r, and \x1a with a MySQL acceptable representation
// for queries (e.g., the newline character is replaced with the litteral string '\n')
function escapeSQL($sourceString)
{
global $connection;
$sourceString = mysqli_real_escape_string($connection, $sourceString);
return $sourceString;
}
// --------------------------------------------------------------------
// generate a UNIX date/time stamp (integer) from a MySQL-formatted date (YYYY-MM-DD)
// and time (HH:MM:SS) (or the current date/time if no specific date/time was given):
function generateUNIXTimeStamp($date = "", $time = "")
{
if (!empty($date))
$dateArray = preg_split("/-/", $date); // split MySQL-formatted date string (e.g. "2004-09-27") into its pieces (year, month, day)
else
$dateArray = array(date('Y'), date('m'), date('d')); // use current year, month & day
if (!empty($time))
$timeArray = preg_split("/:/", $time); // split MySQL-formatted time string (e.g. "23:58:23") into its pieces (hours, minutes, seconds)
else
$timeArray = array(date('H'), date('i'), date('s')); // use current hour, minute & second
// return the Unix timestamp corresponding to the arguments given; the timestamp is a long integer
// containing the number of seconds between the Unix Epoch (January 1 1970 00:00:00 GMT) and the time specified:
$timeStamp = mktime($timeArray[0], $timeArray[1], $timeArray[2], $dateArray[1], $dateArray[2], $dateArray[0]);
return $timeStamp;
}
// --------------------------------------------------------------------
// generate an ISO date/time stamp (string) according to ISO-8601,
// the international standard for date and time representations:
// (ISO-8601 date/time example: "2008-01-11T18:30:21+0100";
// more info: <http://en.wikipedia.org/wiki/ISO_8601>
// <http://www.cl.cam.ac.uk/~mgk25/iso-time.html>)
function generateISO8601TimeStamp($date = "", $time = "")
{
$timeStamp = generateUNIXTimeStamp($date, $time);
$iso8601date = date('Y-m-d\TH:i:s', $timeStamp); // PHP 4+5
// for PHP4 support, we manually insert a colon in the TZ designation:
$timezone = date("O", $timeStamp); // get timezone
$iso8601date .= substr($timezone, 0, -2) . ":" . substr($timezone, -2, 2); // append timezone
// $iso8601date = date('c', $timeStamp); // PHP 5
return $iso8601date;
}
// --------------------------------------------------------------------
// generate a RFC-2822 formatted date/time stamp (string):
// (RFC-2822 date/time example: "Fri, 11 Jan 2008 18:30:21 +0100")
function generateRFC2822TimeStamp($date = "", $time = "")
{
$timeStamp = generateUNIXTimeStamp($date, $time);
$rfc2822date = date('r', $timeStamp);
return $rfc2822date;
}
// --------------------------------------------------------------------
// generate an email address from MySQL 'created_by' fields that conforms
// to the RFC-2822 specifications (<http://www.faqs.org/rfcs/rfc2822.html>):
function generateRFC2822EmailAddress($createdBy)
{
// Note that the following patterns don't attempt to do fancy parsing of email addresses but simply assume the string format
// of the 'created_by' field (table 'refs'). If you change the string format, you must modify these patterns as well!
$authorName = preg_replace("/(.+?)\([^)]+\)/", "\\1", $createdBy);
$authorEmail = preg_replace("/.+?\(([^)]+)\)/", "\\1", $createdBy);
$rfc2822address = encodeHTMLspecialchars($authorName . "<" . $authorEmail . ">");
return $rfc2822address;
}
// --------------------------------------------------------------------
// Takes a SQL query and tries to describe it in natural language:
// (Note that, currently, this function doesn't attempt to cover all kinds of SQL queries [which would be a task by its own!]
// but rather sticks to what is needed in the context of refbase: I.e., right now, only the 'WHERE' clause will be translated)
function explainSQLQuery($sourceSQLQuery)
{
// fix escape sequences within the SQL query:
$translatedSQL = stripSlashesIfMagicQuotes($sourceSQLQuery);
// $translatedSQL = str_replace('\"','"',$sourceSQLQuery); // replace any \" with "
// $translatedSQL = preg_replace('/(\\\\)+/','\\\\',$translatedSQL);
// define an array of search & replace actions:
// (Note that the order of array elements IS important since it defines when a search/replace action gets executed)
$sqlSearchReplacePatterns = array(" != " => " is not equal to ",
" = " => " is equal to ",
" > " => " is greater than ",
" >= " => " is equal to or greater than ",
" < " => " is less than ",
" <= " => " is equal to or less than ",
"NOT RLIKE \"\\^([^\"]+?)\\$\"" => "is not equal to '\\1'",
"NOT RLIKE \"\\^" => "does not start with '",
"NOT RLIKE \"([^\"]+?)\\$\"" => "does not end with '\\1'",
"NOT RLIKE" => "does not contain",
"RLIKE \"\\^([^\"]+?)\\$\"" => "is equal to '\\1'",
"RLIKE \"\\^" => "starts with '",
"RLIKE \"([^\"]+?)\\$\"" => "ends with '\\1'",
"RLIKE" => "contains",
"AND" => "and");
// Perform search & replace actions on the SQL query:
$translatedSQL = searchReplaceText($sqlSearchReplacePatterns, $translatedSQL, false);
$translatedSQL = str_replace('"',"'",$translatedSQL); // replace any remaining " with '
return $translatedSQL;
}
// --------------------------------------------------------------------
// Extract the 'SELECT' clause from an SQL query:
function extractSELECTclause($query)
{
$querySELECTclause = preg_replace("/^.*?SELECT (.+?) FROM .*?$/i", "\\1", $query);
return $querySELECTclause;
}
// --------------------------------------------------------------------
// Extract the 'WHERE' clause from an SQL query:
function extractWHEREclause($query)
{
// Note: we include the SQL commands SELECT/INSERT/UPDATE/DELETE/CREATE/ALTER/DROP/FILE in an attempt to sanitize a given WHERE clause from SQL injection attacks
$queryWHEREclause = preg_replace("/^.*? WHERE (.+?)(?= ORDER BY| LIMIT| GROUP BY| HAVING| PROCEDURE| FOR UPDATE| LOCK IN|[ ;]+(SELECT|INSERT|UPDATE|DELETE|CREATE|ALTER|DROP|FILE)\b|$).*?$/i", "\\1", $query);
return $queryWHEREclause;
}
// --------------------------------------------------------------------
// Extract the 'ORDER BY' clause from an SQL query:
function extractORDERBYclause($query)
{
// Note: we include the SQL commands SELECT/INSERT/UPDATE/DELETE/CREATE/ALTER/DROP/FILE in an attempt to sanitize a given ORDER BY clause from SQL injection attacks
$queryORDERBYclause = preg_replace("/^.*? ORDER BY (.+?)(?= LIMIT| GROUP BY| HAVING| PROCEDURE| FOR UPDATE| LOCK IN|[ ;]+(SELECT|INSERT|UPDATE|DELETE|CREATE|ALTER|DROP|FILE)\b|$).*?$/i", "\\1", $query);
return $queryORDERBYclause;
}
// --------------------------------------------------------------------
// This function walks a '$searchArray' and appends its items to the WHERE clause:
// (the array hierarchy will be maintained, i.e. if the '_query' item is itself
// an array of query items these sub-items will get properly nested in parentheses)
// Example '$searchArray':
// Array
// (
// [0] => Array
// (
// [_boolean] =>
// [_query] => location RLIKE "user@refbase.net"
// )
// [1] => Array
// (
// [_boolean] => AND
// [_query] => Array
// (
// [0] => Array
// (
// [_boolean] => OR
// [_query] => author RLIKE "steffens"
// )
// [1] => Array
// (
// [_boolean] => OR
// [_query] => title RLIKE "refbase"
// )
// [2] => Array
// (
// [_boolean] => OR
// [_query] => keywords RLIKE "refbase"
// )
// )
// )
// )
function appendToWhereClause($searchArray)
{
global $query;
foreach ($searchArray as $searchArrayItem)
{
if (!preg_match("/\($/", $query)) // add whitespace & any given boolean search operator if this item isn't the first one within a sub-array of query items
{
$query .= " ";
if (!empty($searchArrayItem["_boolean"]))
$query .= $searchArrayItem["_boolean"] . " ";
}
if (is_array($searchArrayItem["_query"])) // recursively parse any sub-arrays of query items and nest them in parentheses
{
$query .= "("; // NOTE: the parentheses must be on their own code lines to allow for correct recursion
$query .= appendToWhereClause($searchArrayItem["_query"]);
$query .= ")";
}
else
{
$query .= $searchArrayItem["_query"];
}
}
}
// -------------------------------------------------------------------------------------------------------------------
// Generate an URL pointing to a RSS feed or any of the supported export/citation formats for the given query:
// '$urlType' must be one of these: - RSS XML
// - export formats: ADS, BibTeX, Endnote, ISI, RIS, Atom XML, MODS XML, OAI_DC XML, ODF XML, SRW_DC XML, SRW_MODS XML, Word XML
// - citation formats: RTF, PDF, LaTeX, Markdown, ASCII, LaTeX .bbl
// - default format: html (session variable 'userDefaultView' specifies the default display type)
function generateURL($baseURL, $urlType, $queryParametersArray, $encodeAmpersands = false, $showRows = 0, $rowOffset = 0, $citeStyle = "", $citeOrder = "")
{
global $defaultCiteStyle; // defined in 'ini.inc.php'
// NOTE: This code block is a hack that fixes an inconsistency in the refbase API, where "RSS XML" is generated by 'rss.php'
// while all other formats are available via 'show.php'. Eventually, "RSS XML" should be also made available as proper
// export format so that it can be generated via 'show.php' URLs.
if (($baseURL == "show.php") AND ($urlType == "RSS XML"))
$baseURL = "rss.php";
if (empty($urlType))
$urlType = "html";
// NOTE: The record offset ('$rowOffset') as well as the number of records to be returned ('$showRows') will only work for "html"
// output, any of the citation formats or the export formats "Atom XML", "SRW_DC XML" and "SRW_MODS XML" - the other export formats will
// currently always export the entire result set. Also, 'rss.php' supports '$showRows', but not '$rowOffset', '$citeStyle' or '$citeOrder'.
if (!empty($rowOffset))
{
if (preg_match("#^((opensearch|sru|show|rss)\.php)$#i", $baseURL))
$queryParametersArray["startRecord"] = ($rowOffset + 1);
else
$queryParametersArray["rowOffset"] = $rowOffset;
}
if (!empty($showRows) AND ($showRows != $_SESSION['userRecordsPerPage']))
{
if (preg_match("#^((opensearch|sru)\.php)$#i", $baseURL))
$queryParametersArray["maximumRecords"] = $showRows;
else
$queryParametersArray["showRows"] = $showRows;
}
// Add parameters required by 'search.php' or the 'show.php' API:
if (preg_match("#^((search|show)\.php)$#i", $baseURL))
{
// - all formats:
if (!empty($citeOrder) AND ($citeOrder != "author")) // 'citeOrder=author' is the default sort order
$queryParametersArray["citeOrder"] = $citeOrder;
// - all formats that (may) contain formatted citations:
if (preg_match("/^(html|Atom XML|OAI_DC XML|SRW_DC XML|RTF|PDF|LaTeX|Markdown|ASCII|LaTeX \.bbl)$/i", $urlType))
{
if (!empty($citeStyle) AND ($citeStyle != $defaultCiteStyle))
$queryParametersArray["citeStyle"] = $citeStyle;
}
// - export formats:
if (preg_match("/^(ADS|BibTeX|Endnote|RIS|ISI|Atom XML|MODS XML|OAI_DC XML|ODF XML|SRW_DC XML|SRW_MODS XML|Word XML)$/i", $urlType))
{
if (!isset($queryParametersArray["exportType"]))
{
if (preg_match("/XML/i", $urlType))
$queryParametersArray["exportType"] = "xml";
else
$queryParametersArray["exportType"] = "file";
}
$queryParametersArray["submit"] = "Export";
$queryParametersArray["exportFormat"] = $urlType;
}
// - citation formats:
elseif (preg_match("/^(RTF|PDF|LaTeX|Markdown|ASCII|LaTeX \.bbl)$/i", $urlType))
{
$queryParametersArray["submit"] = "Cite";
$queryParametersArray["citeType"] = $urlType;
}
}
// Add parameters required by 'opensearch.php':
elseif ($baseURL == "opensearch.php")
{
$queryParametersArray["recordSchema"] = $urlType;
}
// Build query URL:
$queryURL = "";
if ($encodeAmpersands)
$ampersandChar = "&amp;"; // we need to encode the ampersand character (that delimits 'param=value' pairs) if the generated URL is to be included in HTML or XML output
else
$ampersandChar = "&";
foreach ($queryParametersArray as $varname => $value)
$queryURL .= $ampersandChar . $varname . "=" . rawurlencode($value);
$queryURL = trimTextPattern($queryURL, $ampersandChar, true, false); // remove again ampersand character from beginning of query URL
return $baseURL . "?" . $queryURL;
}
// --------------------------------------------------------------------
// Generate RSS XML data from a particular result set (upto the limit given in '$showRows'):
//
// TODO: include OpenSearch elements in RSS output
// (see examples at <http://www.opensearch.org/Specifications/OpenSearch/1.1#OpenSearch_response_elements>)
function generateRSS($result, $showRows, $rssChannelDescription)
{
global $officialDatabaseName; // these variables are defined in 'ini.inc.php'
global $databaseBaseURL;
global $feedbackEmail;
global $defaultCiteStyle;
global $contentTypeCharset;
global $logoImageURL;
global $transtab_refbase_html; // defined in 'transtab_refbase_html.inc.php'
// Note that we only convert those entities that are supported by XML (by use of the 'encodeHTMLspecialchars()' function).
// All other higher ASCII chars are left unencoded and valid feed output is only possible if the '$contentTypeCharset' variable is set correctly in 'ini.inc.php'.
// (The only exception is the item description which will contain HTML tags & entities that were defined by '$transtab_refbase_html' or by the 'reArrangeAuthorContents()' function)
// Define inline text markup to be used by the 'citeRecord()' function:
$markupPatternsArray = array("bold-prefix" => "<b>",
"bold-suffix" => "</b>",
"italic-prefix" => "<i>",
"italic-suffix" => "</i>",
"underline-prefix" => "<u>",
"underline-suffix" => "</u>",
"endash" => "&#8211;",
"emdash" => "&#8212;",
"ampersand" => "&", // ampersands in author contents get encoded in function 'reArrangeAuthorContents()' (since the last param in the 'citeRecord()' function call below is set to 'true')
"double-quote" => "&quot;",
"single-quote" => "'",
"less-than" => "&lt;",
"greater-than" => "&gt;",
"newline" => "\n<br>\n"
);
$currentDateTimeStamp = generateRFC2822TimeStamp(); // get the current date & time (in UNIX/RFC-2822 time stamp format => "date('r')" or "date('D, j M Y H:i:s O')")
// write RSS header:
$rssData = "<?xml version=\"1.0\" encoding=\"" . $contentTypeCharset . "\"?>"
. "\n<rss version=\"2.0\" xmlns:dc=\"http://purl.org/dc/elements/1.1/\">";
// write channel info:
$rssData .= "\n\t<channel>"
. "\n\t\t<title>" . encodeHTMLspecialchars($officialDatabaseName) . "</title>"
. "\n\t\t<link>" . $databaseBaseURL . "</link>"
. "\n\t\t<description>" . encodeHTMLspecialchars($rssChannelDescription) . "</description>"
. "\n\t\t<language>en</language>"
. "\n\t\t<pubDate>" . $currentDateTimeStamp . "</pubDate>"
. "\n\t\t<lastBuildDate>" . $currentDateTimeStamp . "</lastBuildDate>"
. "\n\t\t<webMaster>" . $feedbackEmail . "</webMaster>";
// write image data:
$rssData .= "\n\n\t\t<image>"
. "\n\t\t\t<url>" . $databaseBaseURL . $logoImageURL . "</url>"
. "\n\t\t\t<title>" . encodeHTMLspecialchars($officialDatabaseName) . "</title>"
. "\n\t\t\t<link>" . $databaseBaseURL . "</link>"
. "\n\t\t</image>";
// fetch results: upto the limit specified in '$showRows', fetch a row into the '$row' array and write out a RSS item:
for ($rowCounter=0; (($rowCounter < $showRows) && ($row = @ mysqli_fetch_array($result))); $rowCounter++)
{
$origTitle = $row['title']; // save the original title contents before applying any search & replace actions
// Perform search & replace actions on the text of the 'title' field:
// (the array '$transtab_refbase_html' in 'transtab_refbase_html.inc.php' defines which search & replace actions will be employed)
$row['title'] = searchReplaceText($transtab_refbase_html, $row['title'], true);
// this will provide for correct rendering of italic, super/sub-script and greek letters in item descriptions (which are enclosed by '<![CDATA[...]]>' to ensure well-formed XML);
// item titles are still served in raw format, though, since the use of HTML in item titles breaks many news readers
$citeStyleFile = getStyleFile($defaultCiteStyle); // fetch the name of the citation style file that's associated with the style given in '$defaultCiteStyle' (which, in turn, is defined in 'ini.inc.php')
// include the found citation style file *once*:
include_once "cite/" . $citeStyleFile; // instead of 'include_once' we could also use: 'if ($rowCounter == 0) { include "cite/" . $citeStyleFile; }'
// Generate a proper citation for this record, ordering attributes according to the chosen output style & record type:
$record = citeRecord($row, $defaultCiteStyle, "", $markupPatternsArray, true); // function 'citeRecord()' is defined in the citation style file given in '$citeStyleFile' (which, in turn, must reside in the 'styles' directory of the refbase root directory)
// To avoid advertising email adresses in public RSS output, we remove the email address from contents of the 'modified_by' field which
// get displayed in item descriptions. However, note that email adresses are NOT stripped from contents of the 'created_by' field
// since a valid RSS feed must include an email address in the '<author>' element.
// The following pattern does not attempt to do fancy parsing of email addresses but simply assumes the string format
// of the 'modified_by' field (table 'refs'). If you change the string format, you must modify this pattern as well!
$editorName = preg_replace("/(.+?) \([^)]+\)/", "\\1", $row['modified_by']);
// append a RSS item for the current record:
$rssData .= "\n\n\t\t<item>"
. "\n\t\t\t<title>" . encodeHTMLspecialchars($origTitle) . "</title>" // we avoid embedding HTML in the item title and use the raw title instead
. "\n\t\t\t<link>" . $databaseBaseURL . "show.php?record=" . $row['serial'] . "</link>"
. "\n\t\t\t<description><![CDATA[" . $record
. "\n\t\t\t<br><br>Edited by " . encodeHTMLspecialchars($editorName) . " on " . generateRFC2822TimeStamp($row['modified_date'], $row['modified_time']) . ".]]></description>"
. "\n\t\t\t<guid isPermaLink=\"true\">" . $databaseBaseURL . "show.php?record=" . $row['serial'] . "</guid>"
. "\n\t\t\t<pubDate>" . generateRFC2822TimeStamp($row['created_date'], $row['created_time']) . "</pubDate>"
. "\n\t\t\t<author>" . generateRFC2822EmailAddress($row['created_by']) . "</author>"
. "\n\t\t</item>";
}
// finish RSS data:
$rssData .= "\n\n\t</channel>"
. "\n</rss>\n";
return $rssData;
}
// --------------------------------------------------------------------
// Create new table with parsed table data
// This function will create a new table with separate rows for all sub-items
// (which are delimited by '$delim') from the given '$field' (from table 'refs'
// or 'user_data'). This is done to support the Browse view feature for fields
// that contain a string of multiple values separated by a delimiter.
// (for each of the multi-item fields this function is executed only once by
// 'update.php', thereafter 'modify.php' will keep these 'ref_...' tables in
// sync with the main tables)
// Note: The split pattern must be specified as perl-style regular expression
// (including the leading & trailing slashes) and may include mode
// modifiers (such as '/.../i' to perform a case insensitive match)
function createNewTableWithParsedTableData($fieldName, $delim)
{
global $loginUserID; // saved as session variable on login
global $tableRefs, $tableUserData; // defined in 'db.inc.php'
if (preg_match("/^(user_keys|user_notes|user_file|user_groups)$/", $fieldName)) // for all user-specific fields that can contain multiple items (we ignore the 'related' field here since it won't get used for Browse view)
{
$query = "SELECT $fieldName, record_id, user_id FROM $tableUserData"; // WHERE user_id = " . $loginUserID
$userIDTableSpec = "ref_user_id MEDIUMINT UNSIGNED NOT NULL, ";
}
else
{
$query = "SELECT $fieldName, serial FROM $tableRefs";
$userIDTableSpec = "";
}
$result = queryMySQLDatabase($query);
$fieldValuesArray = array(); // initialize array variable which will hold the splitted sub-items
// split field values on the given delimiter:
for ($i=0; $row = @ mysqli_fetch_array($result); $i++)
{
$fieldSubValuesArray = preg_split($delim, $row[$fieldName]); // split field contents on '$delim' (which is interpreted as perl-style regular expression!)
foreach ($fieldSubValuesArray as $fieldSubValue)
{
// // NOTE: we include empty values so that any Browse view query will also display the number of records where the given field is empty
// if (!empty($fieldSubValue))
// {
$fieldSubValue = trim($fieldSubValue);
if ($fieldName == "author")
$fieldSubValue = trimTextPattern($fieldSubValue, " *\(eds?\)", false, true); // remove any existing editor info from the 'author' string, i.e., kill any trailing " (ed)" or " (eds)"
// copy the individual item (as string, ready for database insertion) to the array:
if (preg_match("/^(user_keys|user_notes|user_file|user_groups)$/", $fieldName))
$fieldValuesArray[] = "(NULL, \"". addslashes($fieldSubValue) . "\", $row[record_id], $row[user_id])";
else
$fieldValuesArray[] = "(NULL, \"". addslashes($fieldSubValue) . "\", $row[serial])";
// }
}
}
// build correct 'ref_...' table and field names:
list($tableName, $fieldName) = buildRefTableAndFieldNames($fieldName);
// NOTE: the below query will only work if the current MySQL user is allowed to CREATE tables ('Create_priv = Y')
// therefore, the CREATE statements should be moved to 'update.sql'!
$queryArray[] = "CREATE TABLE " . $tableName . " ("
. $fieldName . "_id MEDIUMINT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY, "
. $fieldName . " VARCHAR(255), "
. "ref_id MEDIUMINT UNSIGNED NOT NULL, "
. $userIDTableSpec
. "INDEX (" . $fieldName . "_id, " . $fieldName . ", ref_id))";
// TODO: Sanitize with quote_smart
foreach ($fieldValuesArray as $fieldValue)
$queryArray[] = "INSERT INTO " . $tableName . " VALUES " . $fieldValue;
// inserting all values at once may cause 'URL too long' server errors:
// $fieldValuesString = implode(", ", $fieldValuesArray); // merge array
// $queryArray[] = "INSERT INTO " . $tableName . " VALUES " . $fieldValuesString;
// RUN the queries on the database through the connection:
foreach($queryArray as $query)
$result = queryMySQLDatabase($query);
return $tableName;
}
// --------------------------------------------------------------------
// Build correct 'ref_...' table and field names:
function buildRefTableAndFieldNames($fieldName)
{
if ($fieldName == "address")
{
$tableName = "ref_addresses";
$fieldName = "ref_address";
}
elseif (!preg_match("/s$/i", $fieldName)) // field name does NOT end with an 's' (such as in 'author')
{
$tableName = "ref_" . $fieldName . "s"; // e.g. 'ref_authors'
$fieldName = "ref_" . $fieldName; // e.g. 'ref_author'
}
else // field name ends with an 's' (such as in 'keywords')
{
$tableName = "ref_" . $fieldName; // e.g. 'ref_keywords'
$fieldName = "ref_" . preg_replace("/^(\w+)s$/i", "\\1", $fieldName); // strip 's' from end of field name -> produces e.g. 'ref_keyword'
}
return array($tableName, $fieldName);
}
// --------------------------------------------------------------------
?>