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

  1. <?php
  2. // Project: Web Reference Database (refbase) <http://www.refbase.net>
  3. // Copyright: Matthias Steffens <mailto:refbase@extracts.de> and the file's
  4. // original author(s).
  5. //
  6. // This code is distributed in the hope that it will be useful,
  7. // but WITHOUT ANY WARRANTY. Please see the GNU General Public
  8. // License for more details.
  9. //
  10. // File: ./includes/include.inc.php
  11. // Repository: $HeadURL: file:///svn/p/refbase/code/branches/bleeding-edge/includes/include.inc.php $
  12. // Author(s): Matthias Steffens <mailto:refbase@extracts.de>
  13. //
  14. // Created: 16-Apr-02, 10:54
  15. // Modified: $Date: 2018-02-16 21:28:11 +0000 (Fri, 16 Feb 2018) $
  16. // $Author: karnesky $
  17. // $Revision: 1420 $
  18. // This file contains important
  19. // functions that are shared
  20. // between all scripts.
  21. // Incorporate some include files:
  22. include 'initialize/db.inc.php'; // 'db.inc.php' is included to hide username and password
  23. include 'initialize/ini.inc.php'; // include common variables
  24. // include transliteration tables:
  25. include 'includes/transtab_unicode_ascii.inc.php'; // include unicode -> ascii transliteration table
  26. include 'includes/transtab_latin1_ascii.inc.php'; // include latin1 -> ascii transliteration table
  27. include 'includes/transtab_unicode_latin1.inc.php'; // include unicode -> latin1 transliteration table
  28. include 'includes/transtab_unicode_refbase.inc.php'; // include unicode -> refbase transliteration table
  29. if ($contentTypeCharset == "UTF-8") // variable '$contentTypeCharset' is defined in 'ini.inc.php'
  30. include_once 'includes/transtab_unicode_charset.inc.php'; // include unicode character case conversion tables
  31. else // we assume "ISO-8859-1" by default
  32. include_once 'includes/transtab_latin1_charset.inc.php'; // include latin1 character case conversion tables
  33. // --------------------------------------------------------------------
  34. // Untaint user data:
  35. function clean($input, $maxlength)
  36. {
  37. $input = substr($input, 0, $maxlength);
  38. $input = EscapeShellCmd($input);
  39. return ($input);
  40. }
  41. // --------------------------------------------------------------------
  42. // Start a session:
  43. function start_session($updateUserFormatsStylesTypesPermissions)
  44. {
  45. global $databaseBaseURL; // these variables are defined in 'ini.inc.php'
  46. global $defaultMainFields;
  47. global $filesBaseDir;
  48. global $filesBaseURL;
  49. global $loginEmail;
  50. global $loginUserID;
  51. global $loginFirstName;
  52. global $loginLastName;
  53. global $abbrevInstitution;
  54. global $lastLogin;
  55. global $referer; // '$referer' is made globally available from within this function
  56. global $connection;
  57. // Initialize the session:
  58. if (!isset($_SESSION["sessionID"]))
  59. {
  60. // Ensure that cookies are enabled:
  61. if (ini_get('session.use_cookies') == 0) // if 'session.use_cookies' is OFF for the current directory
  62. ini_set('session.use_cookies', 1); // enable storage of sessions within cookies
  63. session_start();
  64. $sessionID = session_id(); // get the current session ID
  65. if (!empty($sessionID))
  66. saveSessionVariable("sessionID", $sessionID);
  67. }
  68. // Set the system's locale information:
  69. list($systemLocaleCollate, $systemLocaleCType) = setSystemLocale();
  70. // Set the default timezone used by all date/time functions
  71. // Note: The 'date_default_timezone_set/date_default_timezone_get' functions are available since PHP 5.1.0
  72. if (function_exists("date_default_timezone_set") && function_exists("date_default_timezone_get"))
  73. @date_default_timezone_set(@date_default_timezone_get());
  74. // NOTE: Upon first connection to the MySQL server, function 'connectToMySQLDatabase()' will query the
  75. // MySQL server for the MySQL version and save it to a session variable
  76. // Extract session variables (only necessary if register globals is OFF!):
  77. if (isset($_SESSION['loginEmail']))
  78. {
  79. $loginEmail = $_SESSION['loginEmail'];
  80. $loginUserID = $_SESSION['loginUserID'];
  81. $loginFirstName = $_SESSION['loginFirstName'];
  82. $loginLastName = $_SESSION['loginLastName'];
  83. $abbrevInstitution = $_SESSION['abbrevInstitution'];
  84. $lastLogin = $_SESSION['lastLogin'];
  85. }
  86. elseif ($updateUserFormatsStylesTypesPermissions)
  87. {
  88. // If the user isn't logged in we set the available export formats, citation styles, document types and permissions to
  89. // the defaults which are specified in the 'formats', 'styles', 'types' and 'user_permissions' tables for 'user_id = 0'.
  90. // (a 'user_id' of zero is used within these tables to indicate the default settings if the user isn't logged in)
  91. // NOTE: As an exception, for anyone who isn't logged in, we don't load the default number of records from option
  92. // 'records_per_page' in table 'user_options', but instead use the value given in variable '$defaultNumberOfRecords'
  93. // in 'ini.inc.php'. Similarly, if the user isn't logged in, the list of "main fields" is taken from variable
  94. // '$defaultMainFields' in 'ini.inc.php' and not from option 'main_fields' in table 'user_options. Same holds true
  95. // for variable '$autoCompleteUserInput' vs. option 'show_auto_completions'.
  96. // Get all export formats that were selected by the admin to be visible if a user isn't logged in
  97. // and (if some formats were found) save them as semicolon-delimited string to the session variable 'user_export_formats':
  98. getVisibleUserFormatsStylesTypes(0, "format", "export");
  99. // Get all citation formats that were selected by the admin to be visible if a user isn't logged in
  100. // and (if some formats were found) save them as semicolon-delimited string to the session variable 'user_cite_formats':
  101. getVisibleUserFormatsStylesTypes(0, "format", "cite");
  102. // Get all citation styles that were selected by the admin to be visible if a user isn't logged in
  103. // and (if some styles were found) save them as semicolon-delimited string to the session variable 'user_styles':
  104. getVisibleUserFormatsStylesTypes(0, "style", "");
  105. // Get all document types that were selected by the admin to be visible if a user isn't logged in
  106. // and (if some types were found) save them as semicolon-delimited string to the session variable 'user_types':
  107. getVisibleUserFormatsStylesTypes(0, "type", "");
  108. // Get the user permissions for the current user
  109. // and save all allowed user actions as semicolon-delimited string to the session variable 'user_permissions':
  110. getPermissions(0, "user", true);
  111. // Get the default view for the current user
  112. // and save it to the session variable 'userDefaultView':
  113. getDefaultView(0);
  114. // Get the default number of records per page preferred by the current user
  115. // and save it to the session variable 'userRecordsPerPage':
  116. getDefaultNumberOfRecords(0);
  117. // Get the user's preference for displaying auto-completions
  118. // and save it to the session variable 'userAutoCompletions':
  119. getPrefAutoCompletions(0);
  120. // Get the list of "main fields" for the current user
  121. // and save the list of fields as comma-delimited string to the session variable 'userMainFields':
  122. getMainFields(0);
  123. }
  124. else // if ($updateUserFormatsStylesTypesPermissions == false)
  125. {
  126. // The scripts 'error.php', 'install.php' & 'update.php' use 'start_session(false);' so that they execute without errors
  127. // when there isn't any database yet. However, function 'buildQuickSearchElements()' (which builds the "Quick Search" form
  128. // in the page header) requires the session variable 'userMainFields' to be present. So we take the list of "main fields"
  129. // directly from the global variable '$defaultMainFields' and save it as session variable (we cannot use function
  130. // 'getMainFields()' here since this would require database access):
  131. if (!isset($_SESSION['userMainFields']))
  132. saveSessionVariable("userMainFields", $defaultMainFields);
  133. }
  134. // Set the referrer:
  135. if (isset($_REQUEST['referer']) AND !empty($_REQUEST['referer']) AND strpos($_REQUEST['referer'], '://')===false)
  136. $referer = $_REQUEST['referer']; // get the referring URL from the superglobal '$_REQUEST' variable (if any)
  137. elseif (isset($_SESSION['referer']) AND !empty($_SESSION['referer']))
  138. {
  139. $referer = $_SESSION['referer']; // get the referring URL from the superglobal '$_SESSION' variable (if any)
  140. deleteSessionVariable("referer");
  141. }
  142. elseif (isset($_SERVER['HTTP_REFERER']) AND !empty($_SERVER['HTTP_REFERER']))
  143. $referer = $_SERVER['HTTP_REFERER']; // get the referring URL from the superglobal '$_SERVER' variable (if any)
  144. 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
  145. $referer = "index.php"; // if all other attempts fail, we'll re-direct to the main page
  146. // Verify important variables from 'ini.inc.php':
  147. // - Ensure that the given paths/URLs end with a slash:
  148. $databaseBaseURL = checkPath($databaseBaseURL, "URL");
  149. $filesBaseDir = checkPath($filesBaseDir);
  150. $filesBaseURL = checkPath($filesBaseURL, "URL");
  151. }
  152. // --------------------------------------------------------------------
  153. // Create a new session variable:
  154. function saveSessionVariable($sessionVariableName, $sessionVariableContents)
  155. {
  156. // since PHP 4.1.0 or greater, adding variables directly to the '$_SESSION' variable
  157. // will register a session variable regardless whether register globals is ON or OFF!
  158. $_SESSION[$sessionVariableName] = $sessionVariableContents;
  159. }
  160. // --------------------------------------------------------------------
  161. // Remove a session variable:
  162. function deleteSessionVariable($sessionVariableName)
  163. {
  164. if (ini_get('register_globals') == 1) // register globals is ON for the current directory
  165. session_unregister($sessionVariableName); // clear the specified session variable
  166. else // register globals is OFF for the current directory
  167. unset($_SESSION[$sessionVariableName]); // clear the specified session variable
  168. }
  169. // --------------------------------------------------------------------
  170. // Connect to the MySQL database:
  171. // TODO: I18n
  172. function connectToMySQLDatabase()
  173. {
  174. global $hostName; // these variables are specified in 'db.inc.php'
  175. global $username;
  176. global $password;
  177. global $databaseName;
  178. global $contentTypeCharset; // defined in 'ini.inc.php'
  179. global $connection;
  180. // If a connection parameter is not available, then use our own connection to avoid any locking problems
  181. if (!isset($connection))
  182. {
  183. // (1) OPEN the database connection:
  184. // (variables are set by include file 'db.inc.php'!)
  185. if (!($connection = @ mysqli_connect($hostName, $username, $password, $databaseName)))
  186. if (mysqli_errno($connection) != 0) // this works around a stupid(?) behaviour of the Roxen webserver that returns 'errno: 0' on success! ?:-(
  187. showErrorMsg("The following error occurred while trying to connect to the host:");
  188. // Get the MySQL version and save it to a session variable:
  189. if (!isset($_SESSION['mysqlVersion']))
  190. saveSessionVariable("mysqlVersion", getMySQLversion());
  191. // (2) Set the connection character set (if connected to MySQL 4.1.x or greater):
  192. // more info at <http://dev.mysql.com/doc/refman/5.1/en/charset-connection.html>
  193. if (isset($_SESSION['mysqlVersion']) AND ($_SESSION['mysqlVersion'] >= 4.1))
  194. {
  195. // NOTE: "SET NAMES ..." requires MySQL 4.1 or greater
  196. if ($contentTypeCharset == "UTF-8")
  197. queryMySQLDatabase("SET NAMES utf8"); // set the character set for this connection to 'utf8'
  198. else
  199. queryMySQLDatabase("SET NAMES latin1"); // by default, we establish a 'latin1' connection
  200. }
  201. }
  202. }
  203. // --------------------------------------------------------------------
  204. // Query the MySQL database:
  205. // NOTE: This function requires an established MySQL $connection
  206. // TODO: I18n
  207. function queryMySQLDatabase($query)
  208. {
  209. global $connection;
  210. global $client;
  211. // (3) RUN the query on the database through the connection:
  212. if (!($result = @ mysqli_query($connection, $query)))
  213. if (mysqli_errno($connection) != 0) // this works around a stupid(?) behaviour of the Roxen webserver that returns 'errno: 0' on success! ?:-(
  214. {
  215. 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")
  216. showErrorMsg("Your query: " . encodeHTML($query) . "\n\ncaused the following error:");
  217. else
  218. showErrorMsg("Your query: '" . $query . "' caused the following error:");
  219. }
  220. return $result;
  221. }
  222. // --------------------------------------------------------------------
  223. // Disconnect from the MySQL database:
  224. // TODO: I18n
  225. function disconnectFromMySQLDatabase()
  226. {
  227. global $connection;
  228. if (isset($connection))
  229. // (5) CLOSE the database connection:
  230. if (!(mysqli_close($connection)))
  231. if (mysqli_errno($connection) != 0) // this works around a stupid(?) behaviour of the Roxen webserver that returns 'errno: 0' on success! ?:-(
  232. showErrorMsg("The following error occurred while trying to disconnect from the database:");
  233. }
  234. // --------------------------------------------------------------------
  235. // Get MySQL version:
  236. // NOTE: This function requires an established MySQL $connection
  237. function getMySQLversion()
  238. {
  239. // CONSTRUCT SQL QUERY:
  240. $query = "SELECT VERSION()";
  241. $result = queryMySQLDatabase($query); // RUN the query on the database through the connection
  242. $row = mysqli_fetch_row($result); // fetch the current row into the array $row (it'll be always *one* row, but anyhow)
  243. $mysqlVersionString = $row[0]; // extract the contents of the first (and only) row (returned version string will be something like "4.0.20-standard" etc.)
  244. $mysqlVersion = preg_replace("/^(\d+\.\d+).+/", "\\1", $mysqlVersionString); // extract main version number (e.g. "4.0") from version string
  245. return $mysqlVersion;
  246. }
  247. // --------------------------------------------------------------------
  248. // Get MySQL field info:
  249. // (i.e. fetch field (column) information from a given result resource; returns the
  250. // field property given in '$propertyName', else an array of all field properties;
  251. // see <http://php.net/manual/en/mysqli-result.fetch-field-direct.php>)
  252. function getMySQLFieldInfo($result, $fieldOffset, $propertyName = "")
  253. {
  254. $fieldInfoArray = array();
  255. // Get field (column) metadata:
  256. $fieldInfo = mysqli_fetch_field_direct($result, (int)$fieldOffset); // returns an object containing the field information
  257. // Copy object properties to an array:
  258. $fieldInfoArray["name"] = $fieldInfo->name; // column name
  259. $fieldInfoArray["table"] = $fieldInfo->table; // name of the table the column belongs to
  260. $fieldInfoArray["type"] = $fieldInfo->type; // the type of the column
  261. $fieldInfoArray["def"] = $fieldInfo->def; // default value of the column
  262. $fieldInfoArray["max_length"] = $fieldInfo->max_length; // maximum length of the column
  263. $fieldInfoArray["max_length"] = $fieldInfo->max_length; // maximum length of the column
  264. $fieldInfoArray["primary_key"] = $fieldInfo->primary_key; // 1 if the column is a primary key
  265. $fieldInfoArray["unique_key"] = $fieldInfo->unique_key; // 1 if the column is a unique key
  266. $fieldInfoArray["multiple_key"] = $fieldInfo->multiple_key; // 1 if the column is a non-unique key
  267. $fieldInfoArray["numeric"] = $fieldInfo->numeric; // 1 if the column is numeric
  268. $fieldInfoArray["blob"] = $fieldInfo->blob; // 1 if the column is a BLOB
  269. $fieldInfoArray["unsigned"] = $fieldInfo->unsigned; // 1 if the column is unsigned
  270. $fieldInfoArray["zerofill"] = $fieldInfo->zerofill; // 1 if the column is zero-filled
  271. if (!empty($propertyName) AND isset($fieldInfoArray[$propertyName]))
  272. return $fieldInfoArray[$propertyName];
  273. else
  274. return $fieldInfoArray;
  275. }
  276. // --------------------------------------------------------------------
  277. // Find out how many rows are available and (if there were rows found) seek to the current offset:
  278. // Note that this function will also (re-)assign values to the variables '$rowOffset', '$showRows',
  279. // '$rowsFound', '$previousOffset', '$nextOffset' and '$showMaxRow'.
  280. function seekInMySQLResultsToOffset($result, $rowOffset, $showRows, $displayType, $citeType)
  281. {
  282. // Find out how many rows are available:
  283. $rowsFound = @ mysqli_num_rows($result);
  284. if ($rowsFound > 0) // If there were rows found ...
  285. {
  286. // ... setup variables in order to facilitate "previous" & "next" browsing:
  287. // a) Set '$rowOffset' to zero if not previously defined, or if a wrong number (<=0) was given
  288. 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')
  289. $rowOffset = 0;
  290. // Adjust the '$showRows' value if not previously defined, or if a wrong number (<=0 or float) was given
  291. if (empty($showRows) || ($showRows <= 0) || !preg_match("/^[0-9]+$/", $showRows))
  292. $showRows = $_SESSION['userRecordsPerPage']; // get the default number of records per page preferred by the current user
  293. // Adjust '$rowOffset' if it's value exceeds the number of rows found:
  294. if ($rowOffset > ($rowsFound - 1))
  295. {
  296. if ($rowsFound > $showRows)
  297. $rowOffset = ($rowsFound - $showRows); // start display at first record of last page to be displayed
  298. else
  299. $rowOffset = 0; // start display at the very first record
  300. }
  301. 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
  302. {
  303. // 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
  304. // after the user pressed the 'Show'/'Hide' button within the 'displayOptions' form. But then, to avoid that browse links don't behave as expected,
  305. // we need to adjust the actual value of '$rowOffset' to an exact multiple of '$showRows':
  306. $offsetRatio = ($rowOffset / $showRows);
  307. if (!is_integer($offsetRatio)) // check whether the value of the '$offsetRatio' variable is not an integer
  308. { // if '$offsetRatio' is a float:
  309. $offsetCorrectionFactor = floor($offsetRatio); // get it's next lower integer
  310. if ($offsetCorrectionFactor != 0)
  311. $rowOffset = ($offsetCorrectionFactor * $showRows); // correct the current row offset to the closest multiple of '$showRows' *below* the current row offset
  312. else
  313. $rowOffset = 0;
  314. }
  315. }
  316. // b) The "Previous" page begins at the current offset LESS the number of rows per page
  317. $previousOffset = $rowOffset - $showRows;
  318. // c) The "Next" page begins at the current offset PLUS the number of rows per page
  319. $nextOffset = $rowOffset + $showRows;
  320. // d) Seek to the current offset
  321. mysqli_data_seek($result, $rowOffset); // move internal result pointer to the row number given in '$rowOffset'
  322. }
  323. else // set variables to zero in order to prevent 'Undefined variable...' messages when nothing was found ('$rowsFound = 0'):
  324. {
  325. $rowOffset = 0;
  326. $previousOffset = 0;
  327. $nextOffset = 0;
  328. }
  329. // Calculate the maximum result number on each page ('$showMaxRow' is required as parameter to the 'displayDetails()' function)
  330. if (($rowOffset + $showRows) < $rowsFound)
  331. $showMaxRow = ($rowOffset + $showRows); // maximum result number on each page
  332. else
  333. $showMaxRow = $rowsFound; // for the last results page, correct the maximum result number if necessary
  334. return array($result, $rowOffset, $showRows, $rowsFound, $previousOffset, $nextOffset, $showMaxRow);
  335. }
  336. // --------------------------------------------------------------------
  337. // Show error (prepares error output and redirects it to 'error.php' which displays the error message):
  338. // TODO: I18n
  339. function showErrorMsg($headerMsg)
  340. {
  341. global $client;
  342. global $connection;
  343. $errorNo = mysqli_errno($connection);
  344. $errorMsg = mysqli_error($connection);
  345. 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")
  346. // 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
  347. echo $headerMsg . "\n\nError " . $errorNo . ": " . encodeHTML($errorMsg) . "\n\n";
  348. else
  349. // in case of regular HTML output, '$errorMsg' gets HTML encoded in 'error.php'
  350. header("Location: error.php?errorNo=" . $errorNo . "&errorMsg=" . rawurlencode($errorMsg) . "&headerMsg=" . rawurlencode($headerMsg));
  351. exit;
  352. }
  353. // --------------------------------------------------------------------
  354. // Generate and return a message, and optionally save the message to a session variable:
  355. // Following optional variables can be passed with the '$message' and will be used for non-CLI clients:
  356. // - '$class' defines the name of the CSS class of the span element that encloses the message
  357. // - '$flavour' is the (valid!) name of an HTML phrase element (such as "strong" or "em") that's wrapped around the message
  358. // - '$sessionVariable' is the name of the session variable (such as "HeaderString") to which the message shall be saved
  359. // - '$prefix' is a string that's added at the beginning of the generated message string
  360. // - '$suffix' is a string that's appended at the end of the generated message string
  361. function returnMsg($message, $class = "", $flavour = "", $sessionVariable = "", $prefix = "", $suffix = "")
  362. {
  363. global $client;
  364. // Because we now sanitize the header message, we no longer output it as
  365. // HTML
  366. /*
  367. 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")
  368. {
  369. */
  370. $fullMsg = $message . "\n\n"; // for CLI clients, we just echo the message text
  371. echo $fullMsg;
  372. /* }
  373. else // return an HTML-formatted message:
  374. {
  375. $fullMsg = $prefix;
  376. if (!empty($flavour))
  377. $fullMsg .= '<' . $flavour . '>';
  378. if (!empty($class))
  379. $fullMsg .= '<span class="' . $class . '">' . $message . '</span>';
  380. else
  381. $fullMsg .= $message;
  382. if (!empty($flavour))
  383. $fullMsg .= '</' . $flavour . '>';
  384. $fullMsg .= $suffix;
  385. if (!empty($sessionVariable))
  386. saveSessionVariable($sessionVariable, $fullMsg); // write message to session variable
  387. }
  388. */
  389. return $fullMsg;
  390. }
  391. // --------------------------------------------------------------------
  392. // Show whether the user is logged in or not:
  393. // TODO: I18n
  394. function showLogin()
  395. {
  396. global $loginEmail;
  397. global $loginWelcomeMsg;
  398. global $loginFirstName;
  399. global $loginLastName;
  400. global $abbrevInstitution;
  401. global $loginUserID;
  402. global $loginStatus;
  403. global $loginLinks;
  404. global $adminLoginEmail; // ('$adminLoginEmail' is specified in 'ini.inc.php')
  405. global $loc; // '$loc' is made globally available in 'core.php'
  406. // $referer = $_SERVER["REQUEST_URI"]; // 'REQUEST_URI' does only seem to work for GET requests (but not for POST requests!) ?:-/
  407. // so, as a workaround, we build an appropriate query string from scratch (which will also work for POST requests):
  408. // --- BEGIN WORKAROUND ---
  409. global $formType;
  410. global $displayType;
  411. global $queryURL;
  412. global $showQuery;
  413. global $showLinks;
  414. global $showRows;
  415. global $rowOffset;
  416. global $citeStyle;
  417. global $citeOrder;
  418. global $orderBy;
  419. global $recordAction;
  420. global $serialNo;
  421. global $headerMsg;
  422. global $errorNo;
  423. global $errorMsg;
  424. // Get the path to the currently executing script, relative to the document root:
  425. $scriptURL = scriptURL();
  426. // Extract checkbox variable values from the request:
  427. if (isset($_REQUEST['marked']))
  428. $recordSerialsArray = $_REQUEST['marked']; // extract the values of all checked checkboxes (i.e., the serials of all selected records)
  429. else
  430. $recordSerialsArray = "";
  431. $recordSerialsString = ""; // initialize variable
  432. // join array elements:
  433. if (!empty($recordSerialsArray)) // the user did check some checkboxes
  434. $recordSerialsString = implode("&marked[]=", $recordSerialsArray); // prefix each record serial (except the first one) with "&marked[]="
  435. $recordSerialsString = "&marked[]=" . $recordSerialsString; // prefix also the very first record serial with "&marked[]="
  436. // based on the refering script we adjust the parameters that get included in the link:
  437. 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))
  438. $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
  439. // 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'.
  440. elseif (preg_match("#/user_options\.php#i", $scriptURL))
  441. $referer = $scriptURL . "?" . "userID=" . $loginUserID;
  442. elseif (preg_match("#/(record|receipt)\.php#i", $scriptURL))
  443. $referer = $scriptURL . "?" . "recordAction=" . $recordAction . "&serialNo=" . $serialNo . "&headerMsg=" . rawurlencode($headerMsg);
  444. elseif (preg_match("#/error\.php#i", $scriptURL))
  445. $referer = $scriptURL . "?" . "errorNo=" . $errorNo . "&errorMsg=" . rawurlencode($errorMsg) . "&headerMsg=" . rawurlencode($headerMsg);
  446. else
  447. $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);
  448. // --- END WORKAROUND -----
  449. // Is the user logged in?
  450. if (isset($_SESSION['loginEmail']))
  451. {
  452. $loginStatus = $loc["Welcome"];
  453. $loginWelcomeMsg = "<em>" . encodeHTML($loginFirstName) . " " . encodeHTML($loginLastName) . "</em>!";
  454. if ($loginEmail == $adminLoginEmail)
  455. $loginStatus .= " <span class=\"warning\">" . $loc["Admin"] . "</span>";
  456. $loginLinks = "";
  457. if ($loginEmail == $adminLoginEmail) // if the admin is logged in, add the 'Add User' & 'Manage Users' links:
  458. {
  459. $loginLinks .= "<a href=\"user_details.php\" title=\"add a user to the database\">Add User</a>&nbsp;&nbsp;|&nbsp;&nbsp;";
  460. $loginLinks .= "<a href=\"users.php\" title=\"manage user data\">Manage Users</a>&nbsp;&nbsp;|&nbsp;&nbsp;";
  461. }
  462. else // if a normal user is logged in, we add the 'My Refs' and 'Options' links instead:
  463. {
  464. $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;";
  465. if (isset($_SESSION['user_permissions']) AND preg_match("/allow_modify_options/", $_SESSION['user_permissions'])) // if the 'user_permissions' session variable contains 'allow_modify_options'...
  466. // ... include a link to 'user_receipt.php':
  467. $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;";
  468. }
  469. $loginLinks .= "<a href=\"user_logout.php?referer=" . rawurlencode($referer) . "\"" . addAccessKey("attribute", "login") . " title=\"" . $loc["LinkTitle_Logout"] . addAccessKey("title", "login") . "\">" . $loc["Logout"] . "</a>";
  470. }
  471. else
  472. {
  473. if (preg_match("#.*(record|import[^.]*)\.php#i", $scriptURL))
  474. $loginStatus = "<span class=\"warning\">" . $loc["Warning_LoginToSubmitForm"] . "!</span>";
  475. else
  476. $loginStatus = "";
  477. $loginWelcomeMsg = "";
  478. $loginLinks = "<a href=\"user_login.php?referer=" . rawurlencode($referer) . "\"" . addAccessKey("attribute", "login") . " title=\"" . $loc["LinkTitle_Login"] . addAccessKey("title", "login") . "\">" . $loc["Login"] . "</a>";
  479. }
  480. // Although the '$referer' variable gets included as GET parameter above, we'll also save the variable as session variable:
  481. // (this should help re-directing to the correct page if a user called 'user_login/logout.php' manually, i.e., without parameters)
  482. saveSessionVariable("referer", $referer);
  483. }
  484. // --------------------------------------------------------------------
  485. // Enable 'accesskey' attribute for the specified link/form element:
  486. // '$type' must be either "attribute" or "title", and '$key' must be the
  487. // name of an array key from variable '$accessKeys' (in 'ini.inc.php')
  488. function addAccessKey($type, $key)
  489. {
  490. global $accessKeys; // defined in 'ini.inc.php'
  491. $accessKeyString = "";
  492. if (isset($accessKeys) AND (!empty($accessKeys[$key]) OR ($accessKeys[$key] == "0")))
  493. {
  494. if ($type == "attribute") // add 'accesskey' attribute (like ' accesskey="h"') to the specified link or form element
  495. $accessKeyString = " accesskey=\"" . $accessKeys[$key] . "\"";
  496. elseif ($type == "title") // add access key hint (like ' [ctrl-h]') to the title attribute value of the specified link or form element
  497. $accessKeyString = " [ctrl-" . $accessKeys[$key] . "]";
  498. }
  499. return $accessKeyString;
  500. }
  501. // --------------------------------------------------------------------
  502. // Get the 'user_id' for the record entry in table 'auth' whose email matches that in '$emailAddress':
  503. function getUserID($emailAddress)
  504. {
  505. global $tableAuth; // defined in 'db.inc.php'
  506. connectToMySQLDatabase();
  507. // CONSTRUCT SQL QUERY:
  508. $query = "SELECT user_id FROM $tableAuth WHERE email = " . quote_smart($emailAddress);
  509. $result = queryMySQLDatabase($query); // RUN the query on the database through the connection
  510. $row = mysqli_fetch_array($result);
  511. return($row["user_id"]);
  512. }
  513. // --------------------------------------------------------------------
  514. // ADD RECORDS
  515. // Adds record(s) to the database (i.e., add one or more row entries to MySQL table 'refs'):
  516. // Notes: - the function will return the serial number(s) of all newly created record(s) in an array structure
  517. // - structure of '$importDataArray' (sample values given in "..."):
  518. // array(
  519. // ['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)
  520. // ['version'] => "1.0" // mandatory; the version of the given array structure
  521. // ['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'
  522. // [0] => array( // first record
  523. // [author] => "..." // - contents of 'author' field
  524. // [title] => "..." // - contents of 'title' field
  525. // ...
  526. // )
  527. // [1] => array( // second record
  528. // [author] => "..." // - contents of 'author' field
  529. // [title] => "..." // - contents of 'title' field
  530. // ...
  531. // )
  532. // ...
  533. // )
  534. // ['creator'] => "http://refbase.net" // optional; the (preferably unique) name of the calling script/importer, use an URI if possible
  535. // ['author'] => "Matthias Steffens" // optional; the name of the person who developed the script/importer and/or who can be contacted in case of problems
  536. // ['contact'] => "refbase@extracts.de" // optional; the contact address of the person specified under 'author', use an email address if possible
  537. // ['options'] => array( // optional; array with settings that control the behaviour of the 'addRecords()' function, currently there's only one option:
  538. // [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 @ ')
  539. // )
  540. // )
  541. function addRecords($importDataArray)
  542. {
  543. global $loginUserID;
  544. global $tableRefs, $tableUserData; // defined in 'db.inc.php'
  545. global $connection;
  546. connectToMySQLDatabase();
  547. $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
  548. $serialNumbersArray = array(); // initialize array variable which will hold the serial numbers of all imported records
  549. // Verify the structure of the '$importDataArray':
  550. 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
  551. {
  552. // 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)
  553. if (($importDataArray['type'] == "refbase") AND (in_array($importDataArray['version'], $recognizedArrayFormatsAndVersions['refbase'])))
  554. {
  555. $recordsArray = $importDataArray['records']; // get the array of records that shall be imported
  556. // First, setup some required variables:
  557. // 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)'):
  558. // 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)
  559. list ($currentDate, $currentTime, $currentUser) = getCurrentDateTimeUser();
  560. // LOOP OVER EACH RECORD:
  561. foreach ($recordsArray as $recordData) // for each record...
  562. {
  563. // Initialize some variables (in order to avoid "undefined index" messages when the particular array elements are not available):
  564. if (isset($recordData['author']))
  565. $author = $recordData['author'];
  566. else
  567. $author = "";
  568. if (isset($recordData['pages']))
  569. $pages = $recordData['pages'];
  570. else
  571. $pages = "";
  572. if (isset($recordData['volume']))
  573. $volume = $recordData['volume'];
  574. else
  575. $volume = "";
  576. if (isset($recordData['series_volume']))
  577. $seriesVolume = $recordData['series_volume'];
  578. else
  579. $seriesVolume = "";
  580. // Assign correct values to the calculation fields 'first_author', 'author_count', 'first_page', 'volume_numeric' and 'series_volume_numeric':
  581. list ($firstAuthor, $authorCount, $firstPage, $volumeNumeric, $seriesVolumeNumeric) = generateCalculationFieldContent($author, $pages, $volume, $seriesVolume);
  582. // CONSTRUCT SQL QUERY:
  583. // INSERT - construct a query to add data as new record
  584. $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
  585. if (!empty($recordData['author']))
  586. $queryRefs .= "author = " . quote_smart($recordData['author']) . ", ";
  587. if (!empty($firstAuthor))
  588. $queryRefs .= "first_author = " . quote_smart($firstAuthor) . ", ";
  589. if (!empty($authorCount))
  590. $queryRefs .= "author_count = " . quote_smart($authorCount) . ", ";
  591. if (!empty($recordData['title']))
  592. $queryRefs .= "title = " . quote_smart($recordData['title']) . ", ";
  593. if (!empty($recordData['year']))
  594. $queryRefs .= "year = " . quote_smart($recordData['year']) . ", ";
  595. if (!empty($recordData['publication']))
  596. $queryRefs .= "publication = " . quote_smart($recordData['publication']) . ", ";
  597. if (!empty($recordData['abbrev_journal']))
  598. $queryRefs .= "abbrev_journal = " . quote_smart($recordData['abbrev_journal']) . ", ";
  599. if (!empty($recordData['volume']))
  600. $queryRefs .= "volume = " . quote_smart($recordData['volume']) . ", ";
  601. if (!empty($volumeNumeric))
  602. $queryRefs .= "volume_numeric = " . quote_smart($volumeNumeric) . ", ";
  603. if (!empty($recordData['issue']))
  604. $queryRefs .= "issue = " . quote_smart($recordData['issue']) . ", ";
  605. if (!empty($recordData['pages']))
  606. $queryRefs .= "pages = " . quote_smart($recordData['pages']) . ", ";
  607. if (!empty($firstPage))
  608. $queryRefs .= "first_page = " . quote_smart($firstPage) . ", ";
  609. if (!empty($recordData['address']))
  610. $queryRefs .= "address = " . quote_smart($recordData['address']) . ", ";
  611. if (!empty($recordData['corporate_author']))
  612. $queryRefs .= "corporate_author = " . quote_smart($recordData['corporate_author']) . ", ";
  613. if (!empty($recordData['keywords']))
  614. $queryRefs .= "keywords = " . quote_smart($recordData['keywords']) . ", ";
  615. if (!empty($recordData['abstract']))
  616. $queryRefs .= "abstract = " . quote_smart($recordData['abstract']) . ", ";
  617. if (!empty($recordData['publisher']))
  618. $queryRefs .= "publisher = " . quote_smart($recordData['publisher']) . ", ";
  619. if (!empty($recordData['place']))
  620. $queryRefs .= "place = " . quote_smart($recordData['place']) . ", ";
  621. if (!empty($recordData['editor']))
  622. $queryRefs .= "editor = " . quote_smart($recordData['editor']) . ", ";
  623. if (!empty($recordData['language']))
  624. $queryRefs .= "language = " . quote_smart($recordData['language']) . ", ";
  625. if (!empty($recordData['summary_language']))
  626. $queryRefs .= "summary_language = " . quote_smart($recordData['summary_language']) . ", ";
  627. if (!empty($recordData['orig_title']))
  628. $queryRefs .= "orig_title = " . quote_smart($recordData['orig_title']) . ", ";
  629. if (!empty($recordData['series_editor']))
  630. $queryRefs .= "series_editor = " . quote_smart($recordData['series_editor']) . ", ";
  631. if (!empty($recordData['series_title']))
  632. $queryRefs .= "series_title = " . quote_smart($recordData['series_title']) . ", ";
  633. if (!empty($recordData['abbrev_series_title']))
  634. $queryRefs .= "abbrev_series_title = " . quote_smart($recordData['abbrev_series_title']) . ", ";
  635. if (!empty($recordData['series_volume']))
  636. $queryRefs .= "series_volume = " . quote_smart($recordData['series_volume']) . ", ";
  637. if (!empty($seriesVolumeNumeric))
  638. $queryRefs .= "series_volume_numeric = " . quote_smart($seriesVolumeNumeric) . ", ";
  639. if (!empty($recordData['series_issue']))
  640. $queryRefs .= "series_issue = " . quote_smart($recordData['series_issue']) . ", ";
  641. if (!empty($recordData['edition']))
  642. $queryRefs .= "edition = " . quote_smart($recordData['edition']) . ", ";
  643. if (!empty($recordData['issn']))
  644. $queryRefs .= "issn = " . quote_smart($recordData['issn']) . ", ";
  645. if (!empty($recordData['isbn']))
  646. $queryRefs .= "isbn = " . quote_smart($recordData['isbn']) . ", ";
  647. if (!empty($recordData['medium']))
  648. $queryRefs .= "medium = " . quote_smart($recordData['medium']) . ", ";
  649. if (!empty($recordData['area']))
  650. $queryRefs .= "area = " . quote_smart($recordData['area']) . ", ";
  651. if (!empty($recordData['expedition']))
  652. $queryRefs .= "expedition = " . quote_smart($recordData['expedition']) . ", ";
  653. if (!empty($recordData['conference']))
  654. $queryRefs .= "conference = " . quote_smart($recordData['conference']) . ", ";
  655. // the 'location' and 'call_number' fields are handled below
  656. if (!empty($recordData['approved']))
  657. $queryRefs .= "approved = " . quote_smart($recordData['approved']) . ", ";
  658. if (!empty($recordData['file']))
  659. $queryRefs .= "file = " . quote_smart($recordData['file']) . ", ";
  660. // the 'serial' field is handled below
  661. if (!empty($recordData['orig_record']))
  662. $queryRefs .= "orig_record = " . quote_smart($recordData['orig_record']) . ", ";
  663. if (!empty($recordData['type']))
  664. $queryRefs .= "type = " . quote_smart($recordData['type']) . ", ";
  665. if (!empty($recordData['thesis']))
  666. $queryRefs .= "thesis = " . quote_smart($recordData['thesis']) . ", ";
  667. if (!empty($recordData['notes']))
  668. $queryRefs .= "notes = " . quote_smart($recordData['notes']) . ", ";
  669. if (!empty($recordData['url']))
  670. $queryRefs .= "url = " . quote_smart($recordData['url']) . ", ";
  671. if (!empty($recordData['doi']))
  672. $queryRefs .= "doi = " . quote_smart($recordData['doi']) . ", ";
  673. if (!empty($recordData['contribution_id']))
  674. $queryRefs .= "contribution_id = " . quote_smart($recordData['contribution_id']) . ", ";
  675. if (!empty($recordData['online_publication']))
  676. $queryRefs .= "online_publication = " . quote_smart($recordData['online_publication']) . ", ";
  677. if (!empty($recordData['online_citation']))
  678. $queryRefs .= "online_citation = " . quote_smart($recordData['online_citation']) . ", ";
  679. if (!empty($queryRefs)) // go ahead, if some array elements did contain data
  680. {
  681. // we only honour the 'call_number' string if some other record data were passed as well:
  682. //
  683. // if the 'prefix_call_number' option is set to "true", any 'call_number' string will be prefixed with
  684. // the correct call number prefix of the currently logged-in user (e.g. 'IP� @ msteffens @ '):
  685. if ((isset($_SESSION['loginEmail'])) AND (isset($importDataArray['options']['prefix_call_number'])) AND ($importDataArray['options']['prefix_call_number'] == "true"))
  686. {
  687. $callNumberPrefix = getCallNumberPrefix(); // build a correct call number prefix for the currently logged-in user (e.g. 'IP� @ msteffens')
  688. if (!empty($recordData['call_number']))
  689. $queryRefs .= "call_number = " . quote_smart($callNumberPrefix . " @ " . $recordData['call_number']) . ", "; // add call number prefix to 'call_number' string
  690. else
  691. $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
  692. }
  693. else
  694. {
  695. if (!empty($recordData['call_number']))
  696. $queryRefs .= "call_number = " . quote_smart($recordData['call_number']) . ", ";
  697. }
  698. // if no specific cite key exists in '$recordData', any existing 'call_number' string gets also copied to the
  699. // user-specific 'cite_key' field (which will ensure that this original call number/cite key is retained as
  700. // cite key upon export); however, note that (depending on the user's settings) the cite key may get modified
  701. // or regenerated by function 'generateCiteKey()' below
  702. if (isset($_SESSION['loginEmail']) AND !empty($recordData['call_number']) AND empty($recordData['cite_key']))
  703. $recordData['cite_key'] = $recordData['call_number'];
  704. // for the 'location' field, we accept input from the '$recordData',
  705. // but if no data were given, we'll add the currently logged-in user to the 'location' field:
  706. if (!empty($recordData['location']))
  707. $queryRefs .= "location = " . quote_smart($recordData['location']) . ", ";
  708. elseif (isset($_SESSION['loginEmail']))
  709. $queryRefs .= "location = " . quote_smart($currentUser) . ", ";
  710. $queryRefs .= "serial = NULL, "; // inserting 'NULL' into an auto_increment PRIMARY KEY attribute allocates the next available key value
  711. // 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'),
  712. // otherwise we'll use the current date & time as well as the currently logged-in user name & email address:
  713. if (!empty($recordData['created_by']))
  714. $queryRefs .= "created_by = " . quote_smart($recordData['created_by']) . ", ";
  715. elseif (isset($_SESSION['loginEmail']))
  716. $queryRefs .= "created_by = " . quote_smart($currentUser) . ", ";
  717. if (!empty($recordData['created_date']) AND preg_match("/^\d{4}-\d{2}-\d{2}$/", $recordData['created_date']))
  718. $queryRefs .= "created_date = " . quote_smart($recordData['created_date']) . ", ";
  719. else
  720. $queryRefs .= "created_date = " . quote_smart($currentDate) . ", ";
  721. if (!empty($recordData['created_time']) AND preg_match("/^\d{2}:\d{2}:\d{2}$/", $recordData['created_time']))
  722. $queryRefs .= "created_time = " . quote_smart($recordData['created_time']) . ", ";
  723. else
  724. $queryRefs .= "created_time = " . quote_smart($currentTime) . ", ";
  725. if (!empty($recordData['modified_by']))
  726. $queryRefs .= "modified_by = " . quote_smart($recordData['modified_by']) . ", ";
  727. elseif (isset($_SESSION['loginEmail']))
  728. $queryRefs .= "modified_by = " . quote_smart($currentUser) . ", ";
  729. if (!empty($recordData['modified_date']) AND preg_match("/^\d{4}-\d{2}-\d{2}$/", $recordData['modified_date']))
  730. $queryRefs .= "modified_date = " . quote_smart($recordData['modified_date']) . ", ";
  731. else
  732. $queryRefs .= "modified_date = " . quote_smart($currentDate) . ", ";
  733. if (!empty($recordData['modified_time']) AND preg_match("/^\d{2}:\d{2}:\d{2}$/", $recordData['modified_time']))
  734. $queryRefs .= "modified_time = " . quote_smart($recordData['modified_time']) . "";
  735. else
  736. $queryRefs .= "modified_time = " . quote_smart($currentTime);
  737. $queryRefs = "INSERT INTO $tableRefs SET " . $queryRefs; // finalize the query by prefixing it with the actual MySQL command
  738. // ADD RECORD:
  739. // RUN the query on the database through the connection:
  740. $result = queryMySQLDatabase($queryRefs);
  741. // Get the record id that was created:
  742. $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
  743. // SQL INSERT statement! After any subsequent query it won't be possible to retrieve the auto_increment identifier value for THIS record!)
  744. // ADD USER DATA:
  745. if (isset($_SESSION['loginEmail']))
  746. {
  747. // Note: At the moment, the record in table 'user_data' will be always created for the currently logged-in user,
  748. // i.e. we don't try to match any custom data given in the 'location' field with users from table 'users'
  749. // in order to set the 'user_id' in table 'user_data' accordingly
  750. // This is a stupid hack that maps the names of the '$recordData' array keys to those used
  751. // by the '$formVars' array (which is required by function 'generateCiteKey()')
  752. // (eventually, the '$formVars' array should use the MySQL field names as names for its array keys)
  753. $formVars = buildFormVarsArray($recordData);
  754. // Generate or extract the cite key for this record:
  755. $citeKey = generateCiteKey($formVars);
  756. // Construct SQL query:
  757. $queryUserData = "INSERT INTO $tableUserData SET ";
  758. if (!empty($recordData['marked']) AND preg_match("/^(no|yes)$/", $recordData['marked']))
  759. $queryUserData .= "marked = " . quote_smart($recordData['marked']) . ", ";
  760. if (!empty($recordData['copy']) AND preg_match("/^(false|true|ordered|fetch)$/", $recordData['copy']))
  761. $queryUserData .= "copy = " . quote_smart($recordData['copy']) . ", ";
  762. else
  763. $queryUserData .= "copy = 'true', "; // by default, 'false' would get inserted if omitted; we insert 'true' here in order to be consistent with manual record additions
  764. if (!empty($recordData['selected']) AND preg_match("/^(no|yes)$/", $recordData['selected']))
  765. $queryUserData .= "selected = " . quote_smart($recordData['selected']) . ", ";
  766. if (!empty($recordData['user_keys']))
  767. $queryUserData .= "user_keys = " . quote_smart($recordData['user_keys']) . ", ";
  768. if (!empty($recordData['user_notes']))
  769. $queryUserData .= "user_notes = " . quote_smart($recordData['user_notes']) . ", ";
  770. if (!empty($recordData['user_file']))
  771. $queryUserData .= "user_file = " . quote_smart($recordData['user_file']) . ", ";
  772. if (!empty($recordData['user_groups']))
  773. $queryUserData .= "user_groups = " . quote_smart($recordData['user_groups']) . ", ";
  774. $queryUserData .= "cite_key = " . quote_smart($citeKey) . ", ";
  775. if (!empty($recordData['related']))
  776. $queryUserData .= "related = " . quote_smart($recordData['related']) . ", ";
  777. $queryUserData .= "record_id = " . quote_smart($serialNo) . ", "
  778. . "user_id = " . quote_smart($loginUserID) . ", " // '$loginUserID' is provided as session variable
  779. . "data_id = NULL"; // inserting 'NULL' into an auto_increment PRIMARY KEY attribute allocates the next available key value
  780. // RUN the query on the database through the connection:
  781. $result = queryMySQLDatabase($queryUserData);
  782. }
  783. // Append this record's serial number to the array of imported record serials:
  784. $serialNumbersArray[] = $serialNo;
  785. }
  786. // else: '$recordData' did not contain any data, so we skip this record
  787. }
  788. // (END LOOP OVER EACH RECORD)
  789. }
  790. // else: unknown array structure, return an empty '$serialNumbersArray'
  791. }
  792. // else: couldn't verify structure of '$importDataArray', return an empty '$serialNumbersArray'
  793. return $serialNumbersArray; // return list of serial numbers of all imported records
  794. }
  795. // --------------------------------------------------------------------
  796. // Assign correct values to the calculation fields 'first_author', 'author_count', 'first_page', 'volume_numeric' and 'series_volume_numeric':
  797. function generateCalculationFieldContent($author, $pages, $volume, $seriesVolume)
  798. {
  799. if (!empty($author))
  800. {
  801. // Standardize contents of the author field (which will ensure correct sorting upon Citation output):
  802. // - shorten author's full given name(s) to initial(s)
  803. // - remove any delimiters (such as dots and/or whitespace) from author's initials
  804. // Call the 'reArrangeAuthorContents()' function (defined in 'include.inc.php') in order to re-order contents of the author field. Required Parameters:
  805. // 1. input: contents of the author field
  806. // 2. input: boolean value that specifies whether the author's family name comes first (within one author) in the source string
  807. // ('true' means that the family name is followed by the given name (or initials), 'false' if it's the other way around)
  808. //
  809. // 3. input: pattern describing old delimiter that separates different authors
  810. // 4. output: for all authors except the last author: new delimiter that separates different authors
  811. // 5. output: for the last author: new delimiter that separates the last author from all other authors
  812. //
  813. // 6. input: pattern describing old delimiter that separates author name & initials (within one author)
  814. // 7. output: for the first author: new delimiter that separates author name & initials (within one author)
  815. // 8. output: for all authors except the first author: new delimiter that separates author name & initials (within one author)
  816. // 9. output: new delimiter that separates multiple initials (within one author)
  817. // 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)
  818. // 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)
  819. // 12. output: boolean value that specifies whether an author's full given name(s) shall be shortened to initial(s)
  820. //
  821. // 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
  822. // 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
  823. // 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
  824. //
  825. // 16. output: boolean value that specifies whether the re-ordered string shall be returned with higher ASCII chars HTML encoded
  826. $author = reArrangeAuthorContents($author, // 1.
  827. true, // 2.
  828. "/ *; */", // 3.
  829. "; ", // 4.
  830. "; ", // 5.
  831. "/ *, */", // 6.
  832. ", ", // 7.
  833. ", ", // 8.
  834. "", // 9.
  835. false, // 10.
  836. false, // 11.
  837. true, // 12.
  838. "", // 13.
  839. "", // 14.
  840. "", // 15.
  841. false); // 16.
  842. // 'first_author' field:
  843. $firstAuthor = preg_replace("/^([^;]+).*/i", "\\1", $author); // extract first author from 'author' field
  844. $firstAuthor = trim($firstAuthor); // remove leading & trailing whitespace (if any)
  845. $firstAuthor = preg_replace("/ *\(eds?\)$/i", "", $firstAuthor); // remove any existing editor info from the 'first_author' string, i.e., kill any trailing " (ed)" or " (eds)"
  846. // 'author_count' field:
  847. if (!preg_match("/;/", $author)) // if the 'author' field does NOT contain a ';' (which would delimit multiple authors) => single author
  848. $authorCount = "1"; // indicates a single author
  849. elseif (preg_match("/^[^;]+;[^;]+$/", $author)) // the 'author' field does contain exactly one ';' => two authors
  850. $authorCount = "2"; // indicates two authors
  851. elseif (preg_match("/^[^;]+;[^;]+;[^;]+/", $author)) // the 'author' field does contain at least two ';' => more than two authors
  852. $authorCount = "3"; // indicates three (or more) authors
  853. }
  854. else
  855. {
  856. $firstAuthor = "";
  857. $authorCount = "";
  858. }
  859. // 'first_page' field:
  860. if (!empty($pages))
  861. {
  862. if (preg_match("/([0-9]+)/", $pages)) // if the 'pages' field contains any numeric value(s)
  863. $firstPage = preg_replace("/^[^0-9]*([0-9]+).*/i", "\\1", $pages); // extract first page from 'pages' field
  864. else
  865. $firstPage = "";
  866. }
  867. else
  868. $firstPage = "";
  869. // 'volume_numeric' field:
  870. if (!empty($volume))
  871. {
  872. if (preg_match("/([0-9]+)/", $volume)) // if the 'volume' field contains any numeric value(s)
  873. $volumeNumeric = preg_replace("/^[^0-9]*([0-9]+).*/i", "\\1", $volume); // extract first number from 'volume' field
  874. else
  875. $volumeNumeric = "";
  876. }
  877. else
  878. $volumeNumeric = "";
  879. // 'series_volume_numeric' field:
  880. if (!empty($seriesVolume))
  881. {
  882. if (preg_match("/([0-9]+)/", $seriesVolume)) // if the 'series_volume' field contains any numeric value(s)
  883. $seriesVolumeNumeric = preg_replace("/^[^0-9]*([0-9]+).*/i", "\\1", $seriesVolume); // extract first number from 'series_volume' field
  884. else
  885. $seriesVolumeNumeric = "";
  886. }
  887. else
  888. $seriesVolumeNumeric = "";
  889. return array($firstAuthor, $authorCount, $firstPage, $volumeNumeric, $seriesVolumeNumeric);
  890. }
  891. // --------------------------------------------------------------------
  892. // Generic function that provides email sending capability:
  893. function sendEmail($emailRecipient, $emailSubject, $emailBody)
  894. {
  895. global $adminLoginEmail; // these variables are specified in 'ini.inc.php'
  896. global $contentTypeCharset;
  897. // Setup some additional headers:
  898. $emailHeaders = "From: " . $adminLoginEmail . "\n"
  899. . "Return-Path: " . $adminLoginEmail . "\n"
  900. . "X-Sender: " . $adminLoginEmail . "\n"
  901. . "X-Mailer: PHP\n"
  902. . "X-Priority: 3\n"
  903. . "Content-Type: text/plain; charset=" . $contentTypeCharset;
  904. // Send the email:
  905. mail($emailRecipient, $emailSubject, $emailBody, $emailHeaders);
  906. }
  907. // --------------------------------------------------------------------
  908. // Map MySQL field names to their localized names:
  909. //
  910. // TODO: - ensure that the names for field 'user_groups' in tables 'refs' and 'users' are
  911. // set correctly (user-specific groups of references vs. admin groups of users)
  912. // - add "DropDownFieldName_*" entries for unique field names of table 'users'
  913. function mapFieldNames($isDropDown = false)
  914. {
  915. global $loc; // '$loc' is made globally available in 'core.php'
  916. if ($isDropDown) // field names intended for inclusion into a dropdown form element:
  917. {
  918. $fieldNamesArray = array("author" => $loc["DropDownFieldName_Author"],
  919. // "author_count" => $loc[""],
  920. // "first_author" => $loc[""],
  921. "address" => $loc["DropDownFieldName_Address"],
  922. "corporate_author" => $loc["DropDownFieldName_CorporateAuthor"],
  923. "thesis" => $loc["DropDownFieldName_Thesis"],
  924. "title" => $loc["DropDownFieldName_Title"],
  925. "orig_title" => $loc["DropDownFieldName_OrigTitle"],
  926. "year" => $loc["DropDownFieldName_Year"],
  927. "publication" => $loc["DropDownFieldName_Publication"],
  928. "abbrev_journal" => $loc["DropDownFieldName_AbbrevJournal"],
  929. "editor" => $loc["DropDownFieldName_Editor"],
  930. "volume" => $loc["DropDownFieldName_Volume"],
  931. // "volume_numeric" => $loc[""],
  932. "issue" => $loc["DropDownFieldName_Issue"],
  933. "pages" => $loc["DropDownFieldName_Pages"],
  934. // "first_page" => $loc[""],
  935. "series_title" => $loc["DropDownFieldName_SeriesTitle"],
  936. "abbrev_series_title" => $loc["DropDownFieldName_AbbrevSeriesTitle"],
  937. "series_editor" => $loc["DropDownFieldName_SeriesEditor"],
  938. "series_volume" => $loc["DropDownFieldName_SeriesVolume"],
  939. // "series_volume_numeric" => $loc[""],
  940. "series_issue" => $loc["DropDownFieldName_SeriesIssue"],
  941. "publisher" => $loc["DropDownFieldName_Publisher"],
  942. "place" => $loc["DropDownFieldName_Place"],
  943. "edition" => $loc["DropDownFieldName_Edition"],
  944. "medium" => $loc["DropDownFieldName_Medium"],
  945. "issn" => $loc["DropDownFieldName_Issn"],
  946. "isbn" => $loc["DropDownFieldName_Isbn"],
  947. "language" => $loc["DropDownFieldName_Language"],
  948. "summary_language" => $loc["DropDownFieldName_SummaryLanguage"],
  949. "keywords" => $loc["DropDownFieldName_Keywords"],
  950. "abstract" => $loc["DropDownFieldName_Abstract"],
  951. "area" => $loc["DropDownFieldName_Area"],
  952. "expedition" => $loc["DropDownFieldName_Expedition"],
  953. "conference" => $loc["DropDownFieldName_Conference"],
  954. "doi" => $loc["DropDownFieldName_Doi"],
  955. "url" => $loc["DropDownFieldName_Url"],
  956. "file" => $loc["DropDownFieldName_File"],
  957. "notes" => $loc["DropDownFieldName_Notes"],
  958. "location" => $loc["DropDownFieldName_Location"],
  959. "call_number" => $loc["DropDownFieldName_CallNumber"],
  960. "serial" => $loc["DropDownFieldName_Serial"],
  961. "type" => $loc["DropDownFieldName_Type"],
  962. "approved" => $loc["DropDownFieldName_Approved"],
  963. "created_date" => $loc["DropDownFieldName_CreatedDate"],
  964. "created_time" => $loc["DropDownFieldName_CreatedTime"],
  965. "created_by" => $loc["DropDownFieldName_CreatedBy"],
  966. "modified_date" => $loc["DropDownFieldName_ModifiedDate"],
  967. "modified_time" => $loc["DropDownFieldName_ModifiedTime"],
  968. "modified_by" => $loc["DropDownFieldName_ModifiedBy"],
  969. "marked" => $loc["DropDownFieldName_Marked"],
  970. "copy" => $loc["DropDownFieldName_Copy"],
  971. "selected" => $loc["DropDownFieldName_Selected"],
  972. "user_keys" => $loc["DropDownFieldName_UserKeys"],
  973. "user_notes" => $loc["DropDownFieldName_UserNotes"],
  974. "user_file" => $loc["DropDownFieldName_UserFile"],
  975. "user_groups" => $loc["DropDownFieldName_UserGroups"],
  976. "cite_key" => $loc["DropDownFieldName_CiteKey"],
  977. // "related" => $loc[""]
  978. );
  979. }
  980. else // field names intended as title word or column heading:
  981. {
  982. $fieldNamesArray = array("author" => $loc["Author"],
  983. "author_count" => $loc["AuthorCount"],
  984. "first_author" => $loc["AuthorFirst"],
  985. "address" => $loc["Address"],
  986. "corporate_author" => $loc["CorporateAuthor"],
  987. "thesis" => $loc["Thesis"],
  988. "title" => $loc["Title"],
  989. "orig_title" => $loc["TitleOriginal"],
  990. "year" => $loc["Year"],
  991. "publication" => $loc["Publication"],
  992. "abbrev_journal" => $loc["JournalAbbr"],
  993. "editor" => $loc["Editor"],
  994. "volume" => $loc["Volume"],
  995. "volume_numeric" => $loc["VolumeNumeric"],
  996. "issue" => $loc["Issue"],
  997. "pages" => $loc["Pages"],
  998. "first_page" => $loc["PagesFirst"],
  999. "series_title" => $loc["TitleSeries"],
  1000. "abbrev_series_title" => $loc["TitleSeriesAbbr"],
  1001. "series_editor" => $loc["SeriesEditor"],
  1002. "series_volume" => $loc["SeriesVolume"],
  1003. "series_volume_numeric" => $loc["SeriesVolumeNumeric"],
  1004. "series_issue" => $loc["SeriesIssue"],
  1005. "publisher" => $loc["Publisher"],
  1006. "place" => $loc["PublisherPlace"],
  1007. "edition" => $loc["Edition"],
  1008. "medium" => $loc["Medium"],
  1009. "issn" => $loc["ISSN"],
  1010. "isbn" => $loc["ISBN"],
  1011. "language" => $loc["Language"],
  1012. "summary_language" => $loc["LanguageSummary"],
  1013. "keywords" => $loc["Keywords"],
  1014. "abstract" => $loc["Abstract"],
  1015. "area" => $loc["Area"],
  1016. "expedition" => $loc["Expedition"],
  1017. "conference" => $loc["Conference"],
  1018. "doi" => $loc["DOI"],
  1019. "url" => $loc["URL"],
  1020. "file" => $loc["File"],
  1021. "notes" => $loc["Notes"],
  1022. "location" => $loc["Location"],
  1023. "call_number" => $loc["CallNumber"],
  1024. "serial" => $loc["Serial"],
  1025. "type" => $loc["Type"],
  1026. "approved" => $loc["Approved"],
  1027. "created_date" => $loc["CreationDate"],
  1028. "created_time" => $loc["CreationTime"],
  1029. "created_by" => $loc["Creator"],
  1030. "modified_date" => $loc["ModifiedDate"],
  1031. "modified_time" => $loc["ModifiedTime"],
  1032. "modified_by" => $loc["Modifier"],
  1033. "marked" => $loc["Marked"],
  1034. "copy" => $loc["Copy"],
  1035. "selected" => $loc["Selected"],
  1036. "user_keys" => $loc["UserKeys"],
  1037. "user_notes" => $loc["UserNotes"],
  1038. "user_file" => $loc["UserFile"],
  1039. "user_groups" => $loc["UserGroups"], // see TODO note above
  1040. "cite_key" => $loc["CiteKey"],
  1041. "related" => $loc["Related"],
  1042. // field names from table 'users' (that aren't covered by any of the above):
  1043. "first_name" => $loc["FirstName"],
  1044. "last_name" => $loc["LastName"],
  1045. "institution" => $loc["Institution"],
  1046. "abbrev_institution" => $loc["InstitutionAbbr"],
  1047. "corporate_institution" => $loc["CorporateInstitution"],
  1048. "address_line_1" => $loc["AddressLine1"],
  1049. "address_line_2" => $loc["AddressLine2"],
  1050. "address_line_3" => $loc["AddressLine3"],
  1051. "zip_code" => $loc["ZipCode"],
  1052. "city" => $loc["City"],
  1053. "state" => $loc["State"],
  1054. "country" => $loc["Country"],
  1055. "phone" => $loc["Phone"],
  1056. "email" => $loc["Email"],
  1057. "last_login" => $loc["LastLogin"],
  1058. "logins" => $loc["Logins"],
  1059. "user_id" => $loc["UserID"],
  1060. // "user_groups" => $loc["UserGroups"], // see TODO note above
  1061. );
  1062. }
  1063. return $fieldNamesArray;
  1064. }
  1065. // --------------------------------------------------------------------
  1066. // BUILD FIELD NAME LINKS
  1067. // (i.e., build clickable column headers for each available column)
  1068. // TODO: I18n
  1069. function buildFieldNameLinks($href, $query, $newORDER, $result, $i, $showQuery, $showLinks, $rowOffset, $showRows, $wrapResults, $citeStyle, $HTMLbeforeLink, $HTMLafterLink, $formType, $submitType, $linkName, $orig_fieldname, $headerMsg, $viewType)
  1070. {
  1071. global $databaseBaseURL; // defined in 'ini.inc.php'
  1072. global $loc; // '$loc' is made globally available in 'core.php'
  1073. global $client;
  1074. // Setup the base URL:
  1075. 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
  1076. $baseURL = $databaseBaseURL;
  1077. else
  1078. $baseURL = "";
  1079. // Map MySQL field names to localized column names:
  1080. $fieldNamesArray = mapFieldNames();
  1081. // Get all field properties of the current MySQL field:
  1082. $fieldInfoArray = getMySQLFieldInfo($result, $i);
  1083. if (empty($orig_fieldname)) // if there's no fixed original fieldname specified (as is the case for all fields but the 'Links' column)
  1084. {
  1085. // Get the attribute name:
  1086. $orig_fieldname = $fieldInfoArray["name"];
  1087. }
  1088. if (empty($linkName)) // if there's no fixed link name specified (as is the case for all fields but the 'Links' column)...
  1089. {
  1090. if (isset($fieldNamesArray[$orig_fieldname]))
  1091. {
  1092. $linkName = $fieldNamesArray[$orig_fieldname]; // ...use the attribute's localized name as link name
  1093. }
  1094. else // ...use MySQL field name as fall back:
  1095. {
  1096. // Replace substrings with spaces:
  1097. $linkName = str_replace("_"," ",$orig_fieldname);
  1098. // Form words (i.e., make the first char of a word uppercase):
  1099. $linkName = ucwords($linkName);
  1100. }
  1101. }
  1102. // Setup some variables (in order to enable sorting by clicking on column titles)
  1103. // NOTE: Column sorting with any queries that include the 'LIMIT'... parameter
  1104. // will (technically) work. However, every new query will limit the selection to a *different* list of records!! ?:-/
  1105. if (empty($newORDER)) // if there's no fixed ORDER BY string specified (as is the case for all fields but the 'Links' column)
  1106. {
  1107. if ($fieldInfoArray["numeric"] == "1") // Check if the field's data type is numeric (if so we'll append " DESC" to the ORDER clause)
  1108. $newORDER = ("ORDER BY " . $orig_fieldname . " DESC"); // Build the appropriate ORDER BY clause (sort numeric fields in DESCENDING order)
  1109. else
  1110. $newORDER = ("ORDER BY " . $orig_fieldname); // Build the appropriate ORDER BY clause
  1111. }
  1112. if ($orig_fieldname == "pages") // when original field name = 'pages' then...
  1113. {
  1114. $newORDER = preg_replace("/ORDER BY pages/i", "ORDER BY first_page DESC", $newORDER); // ...sort by 'first_page' instead
  1115. $orig_fieldname = "first_page"; // adjust '$orig_fieldname' variable accordingly
  1116. }
  1117. if ($orig_fieldname == "volume") // when original field name = 'volume' then...
  1118. {
  1119. $newORDER = preg_replace("/ORDER BY volume/i", "ORDER BY volume_numeric DESC", $newORDER); // ...sort by 'volume_numeric' instead
  1120. $orig_fieldname = "volume_numeric"; // adjust '$orig_fieldname' variable accordingly
  1121. }
  1122. if ($orig_fieldname == "series_volume") // when original field name = 'series_volume' then...
  1123. {
  1124. $newORDER = preg_replace("/ORDER BY series_volume/i", "ORDER BY series_volume_numeric DESC", $newORDER); // ...sort by 'series_volume_numeric' instead
  1125. $orig_fieldname = "series_volume_numeric"; // adjust '$orig_fieldname' variable accordingly
  1126. }
  1127. if ($orig_fieldname == "marked") // when original field name = 'marked' then...
  1128. $newORDER = preg_replace("/ORDER BY marked/i", "ORDER BY marked DESC", $newORDER); // ...sort 'marked' column in DESCENDING order (so that 'yes' sorts before 'no')
  1129. if ($orig_fieldname == "last_login") // when original field name = 'last_login' (defined in 'users' table) then...
  1130. $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)
  1131. $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
  1132. // call the 'newORDERclause()' function to replace the ORDER clause:
  1133. $queryURLNewOrder = newORDERclause($newORDER, $query);
  1134. // in the link title, we'll report the field that is actually used for sorting:
  1135. if (isset($fieldNamesArray[$orig_fieldname]))
  1136. $linkTitleFieldName = $fieldNamesArray[$orig_fieldname];
  1137. else
  1138. $linkTitleFieldName = $linkName;
  1139. // figure out if clicking on the current field name will sort in ascending or descending order:
  1140. // (note that for 1st-level sort attributes, this value will be modified again below)
  1141. if (preg_match("/ORDER BY [^ ]+ DESC/i", $newORDER)) // if 1st-level sort is in descending order...
  1142. $linkTitleSortOrder = $loc["descendingOrder"]; // ...sorting will be conducted in DESCending order
  1143. else
  1144. $linkTitleSortOrder = $loc["ascendingOrder"]; // ...sorting will be conducted in ASCending order
  1145. // toggle sort order for the 1st-level sort attribute:
  1146. if (preg_match("/ORDER BY $orig_fieldname(?! DESC)/i", $query)) // if 1st-level sort is by this attribute (in ASCending order)...
  1147. {
  1148. $queryURLNewOrder = preg_replace("/(ORDER%20BY%20$orig_fieldname)(?!%20DESC)/i", "\\1%20DESC", $queryURLNewOrder); // ...change sort order to DESCending
  1149. $linkTitleSortOrder = $loc["descendingOrder"]; // adjust the link title attribute's sort info accordingly
  1150. }
  1151. elseif (preg_match("/ORDER BY $orig_fieldname DESC/i", $query)) // if 1st-level sort is by this attribute (in DESCending order)...
  1152. {
  1153. $queryURLNewOrder = preg_replace("/(ORDER%20BY%20$orig_fieldname)%20DESC/i", "\\1", $queryURLNewOrder); // ...change sort order to ASCending
  1154. $linkTitleSortOrder = $loc["ascendingOrder"]; // adjust the link title attribute's sort info accordingly
  1155. }
  1156. // build an informative string that get's displayed when a user mouses over a link:
  1157. $linkTitle = "\"" . $loc["LinkTitle_SortByField_Prefix"] . $linkTitleFieldName . $loc["LinkTitle_SortByField_Suffix"] . ", " . $linkTitleSortOrder . "\"";
  1158. // start the table header tag & print the attribute name as link:
  1159. $tableHeaderLink = $HTMLbeforeLink
  1160. . "<a href=\"" . $baseURL . $href
  1161. . "?sqlQuery=" . $queryURLNewOrder
  1162. . "&amp;submit=" . $submitType
  1163. . "&amp;citeStyle=" . rawurlencode($citeStyle)
  1164. . "&amp;orderBy=" . rawurlencode($orderBy)
  1165. . "&amp;headerMsg=" . rawurlencode($headerMsg)
  1166. . "&amp;showQuery=" . $showQuery
  1167. . "&amp;showLinks=" . $showLinks
  1168. . "&amp;formType=" . $formType
  1169. . "&amp;showRows=" . $showRows
  1170. . "&amp;rowOffset=" . $rowOffset
  1171. . "&amp;client=" . rawurlencode($client)
  1172. . "&amp;viewType=" . $viewType
  1173. . "\" title=" . $linkTitle . ">" . $linkName . "</a>";
  1174. // append sort indicator after the 1st-level sort attribute:
  1175. if (preg_match("/ORDER BY $orig_fieldname(?! DESC)(?=,| LIMIT|$)/i", $query)) // if 1st-level sort is by this attribute (in ASCending order)...
  1176. $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
  1177. elseif (preg_match("/ORDER BY $orig_fieldname DESC/i", $query)) // if 1st-level sort is by this attribute (in DESCending order)...
  1178. $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
  1179. $tableHeaderLink .= $HTMLafterLink; // append any necessary HTML
  1180. return $tableHeaderLink;
  1181. }
  1182. // --------------------------------------------------------------------
  1183. // Build SELECT clause:
  1184. // (if given, '$additionalFields' & '$customSELECTclause' must contain
  1185. // a string of comma-separated field names)
  1186. // TODO: add support for 'users.php' SELECT clauses
  1187. function buildSELECTclause($displayType, $showLinks, $additionalFields = "", $addUserSpecificFields = true, $addRequiredFields = true, $customSELECTclause = "", $browseByField = "")
  1188. {
  1189. global $defaultFieldsListViewMajor; // these variables are specified in 'ini.inc.php'
  1190. global $defaultFieldsListViewMinor;
  1191. global $additionalFieldsCitationView;
  1192. global $showAdditionalFieldsDetailsViewDefault;
  1193. global $showUserSpecificFieldsDetailsViewDefault;
  1194. if (empty($displayType))
  1195. $displayType = $_SESSION['userDefaultView']; // get the default view for the current user
  1196. $querySELECTclause = "SELECT ";
  1197. if (!empty($customSELECTclause)) // if given, honour any custom SQL SELECT clause:
  1198. {
  1199. $querySELECTclause .= $customSELECTclause;
  1200. }
  1201. else // build a new SELECT clause that's suitable for the given '$displayType':
  1202. {
  1203. // Details view:
  1204. if (preg_match("/^(Display)$/i", $displayType)) // select all fields required to display record details:
  1205. {
  1206. if ($showAdditionalFieldsDetailsViewDefault == "no") // omit additional fields:
  1207. {
  1208. $querySELECTclause .= "author, title, type, year, publication, abbrev_journal, volume, issue, pages, keywords, abstract";
  1209. }
  1210. else // display all fields:
  1211. {
  1212. $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";
  1213. if (isset($_SESSION['loginEmail']))
  1214. $querySELECTclause .= ", location"; // we only add the 'location' field if the user is logged in
  1215. }
  1216. $querySELECTclause .= ", call_number, serial";
  1217. if ($showUserSpecificFieldsDetailsViewDefault == "no")
  1218. $addUserSpecificFields = false;
  1219. }
  1220. // Edit mode & Export:
  1221. elseif (preg_match("/^(Edit|Export)$/i", $displayType)) // select all fields required to display record details (in edit mode) or to export a record:
  1222. {
  1223. $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";
  1224. if (isset($_SESSION['loginEmail']))
  1225. $querySELECTclause .= ", location"; // we only add the 'location' field if the user is logged in
  1226. $querySELECTclause .= ", contribution_id, online_publication, online_citation, created_date, created_time, created_by, modified_date, modified_time, modified_by, call_number, serial";
  1227. }
  1228. // Citation view & RSS output:
  1229. elseif (preg_match("/^(Cite|RSS)$/i", $displayType)) // select all fields required to build proper record citations:
  1230. {
  1231. $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";
  1232. if ($displayType == "RSS") // for RSS output, we add some additional fields:
  1233. $querySELECTclause .= ", created_date, created_time, created_by, modified_date, modified_time, modified_by";
  1234. if (!empty($additionalFieldsCitationView)) // append all fields from '$additionalFieldsCitationView' that aren't yet included in the SELECT clause
  1235. foreach ($additionalFieldsCitationView as $field)
  1236. if (!preg_match("/\b" . $field . "\b/", $querySELECTclause))
  1237. {
  1238. 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
  1239. $addUserSpecificFields = true;
  1240. else // append field:
  1241. $querySELECTclause .= ", " . $field;
  1242. }
  1243. }
  1244. // Browse view:
  1245. elseif (preg_match("/^Browse$/i", $displayType))
  1246. {
  1247. $querySELECTclause .= escapeSQL($browseByField) . ", COUNT(*) AS records";
  1248. }
  1249. // List view:
  1250. else // produce the default columnar output style:
  1251. {
  1252. $querySELECTclause .= $defaultFieldsListViewMajor;
  1253. if (!empty($defaultFieldsListViewMinor))
  1254. {
  1255. $querySELECTclause .= ", " . $defaultFieldsListViewMinor;
  1256. }
  1257. }
  1258. }
  1259. // All views (except Browse view):
  1260. if (!preg_match("/^Browse$/i", $displayType))
  1261. {
  1262. if (!empty($additionalFields))
  1263. {
  1264. if ($querySELECTclause != "SELECT ")
  1265. $querySELECTclause .= ", "; // add a comma as field separator, if other fields have already been added to the SELECT clause
  1266. $querySELECTclause .= $additionalFields;
  1267. }
  1268. // NOTE: Functions 'displayColumns()' and 'displayDetails()' (in 'search.php') apply some logic that prevents some or all of the
  1269. // below fields from getting displayed. This means that you must adopt these functions if you add or remove fields below.
  1270. if ($addUserSpecificFields)
  1271. {
  1272. if (isset($_SESSION['loginEmail'])) // if a user is logged in...
  1273. $querySELECTclause .= ", marked, copy, selected, user_keys, user_notes, user_file, user_groups, cite_key, related"; // add user-specific fields
  1274. }
  1275. if ($addRequiredFields)
  1276. {
  1277. // NOTE: Although it won't be visible the 'orig_record' & 'serial' columns get included in every search query
  1278. // (that's executed directly and not included into HTML as a web link or routed again thru other scripts).
  1279. // The 'orig_record' column is required in order to present visual feedback on duplicate records, and
  1280. // the 'serial' column is required in order to obtain unique checkbox names. For SQL queries passed to
  1281. // 'search.php' directly, function 'verifySQLQuery()' in 'include.inc.php' will add these columns.
  1282. $querySELECTclause .= ", orig_record, serial"; // add 'orig_record' and 'serial' columns
  1283. if ($showLinks == "1" OR (preg_match("/^(Edit|Export)$/i", $displayType)))
  1284. $querySELECTclause .= ", file, url, doi"; // add 'file', 'url' & 'doi' columns
  1285. if ($showLinks == "1" AND (!preg_match("/^(Edit|Export)$/i", $displayType)))
  1286. $querySELECTclause .= ", isbn, type"; // add 'isbn' & 'type columns (for export and edit mode, these columns have already been added above)
  1287. }
  1288. }
  1289. return $querySELECTclause;
  1290. }
  1291. // --------------------------------------------------------------------
  1292. // REPLACE ORDER CLAUSE IN SQL QUERY
  1293. function newORDERclause($newOrderBy, $query, $encodeQuery = true)
  1294. {
  1295. // replace any existing ORDER BY clause with the new one given in '$newOrderBy':
  1296. $newQuery = preg_replace("/ORDER BY .+?(?=LIMIT.*|GROUP BY.*|HAVING.*|PROCEDURE.*|FOR UPDATE.*|LOCK IN.*|$)/i", $newOrderBy, $query);
  1297. if ($encodeQuery)
  1298. $newQuery = rawurlencode($newQuery); // URL encode query
  1299. return $newQuery;
  1300. }
  1301. // --------------------------------------------------------------------
  1302. // REPLACE SELECT CLAUSE IN SQL QUERY
  1303. function newSELECTclause($newSelectClause, $query, $encodeQuery = true)
  1304. {
  1305. // replace any existing SELECT clause with the new one given in '$newSelectClause':
  1306. $newQuery = preg_replace("/SELECT .+?(?= FROM)/i", $newSelectClause, $query);
  1307. if ($encodeQuery)
  1308. $newQuery = rawurlencode($newQuery); // URL encode query
  1309. return $newQuery;
  1310. }
  1311. // --------------------------------------------------------------------
  1312. // BUILD BROWSE LINKS
  1313. // (i.e., build a TABLE row with links for "previous" & "next" browsing, as well as links to intermediate pages)
  1314. // TODO: - use divs + CSS styling (instead of a table-based layout) for _all_ output (not only for 'viewType=Mobile')
  1315. // - use function 'generateURL()' to build the link URLs
  1316. function buildBrowseLinks($href, $query, $NoColumns, $rowsFound, $showQuery, $showLinks, $showRows, $rowOffset, $previousOffset, $nextOffset, $wrapResults, $maxPageNo, $formType, $displayType, $citeStyle, $citeOrder, $orderBy, $headerMsg, $viewType)
  1317. {
  1318. global $databaseBaseURL; // these variables are defined in 'ini.inc.php'
  1319. global $displayResultsHeaderDefault;
  1320. global $displayResultsFooterDefault;
  1321. global $loc; // '$loc' is made globally available in 'core.php'
  1322. global $client;
  1323. // First, calculate the offset page number:
  1324. $pageOffset = ($rowOffset / $showRows);
  1325. // workaround for always rounding upward (since I don't know better! :-/):
  1326. if (preg_match("/[0-9]+\.[0-9+]/",$pageOffset)) // if the result number is not an integer..
  1327. $pageOffset = (int) $pageOffset + 1; // we convert the number into an integer and add 1
  1328. // set the offset page number to a multiple of $maxPageNo:
  1329. $pageOffset = $maxPageNo * (int) ($pageOffset / $maxPageNo);
  1330. // Plus, calculate the maximum number of pages needed:
  1331. $lastPage = ($rowsFound / $showRows);
  1332. // workaround for always rounding upward (since I don't know better! :-/):
  1333. if (preg_match("/[0-9]+\.[0-9+]/",$lastPage)) // if the result number is not an integer..
  1334. $lastPage = (int) $lastPage + 1; // we convert the number into an integer and add 1
  1335. // Setup the base URL:
  1336. 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
  1337. $baseURL = $databaseBaseURL;
  1338. else
  1339. $baseURL = "";
  1340. if (preg_match("/^Mobile$/i", $viewType))
  1341. {
  1342. $BrowseLinks = "\n<div class=\"resultnav\">";
  1343. }
  1344. else
  1345. {
  1346. // Start a <TABLE>:
  1347. $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\">";
  1348. // Start a <TABLE> row:
  1349. $BrowseLinks .= "\n<tr>";
  1350. }
  1351. if (preg_match("/^Mobile$/i", $viewType))
  1352. $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>";
  1353. elseif (preg_match("/^Print$/i", $viewType) OR preg_match("/^cli/i", $client))
  1354. $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>";
  1355. elseif (($href == "users.php") OR !isset($displayResultsFooterDefault[$displayType]) OR (isset($displayResultsFooterDefault[$displayType]) AND ($displayResultsFooterDefault[$displayType] != "hidden")))
  1356. {
  1357. $BrowseLinks .= "\n\t<td class=\"small\" align=\"left\" valign=\"bottom\" width=\"225\">"
  1358. . "\n\t\t<a href=\"JavaScript:checkall(true,'marked%5B%5D')\" title=\"" . $loc["LinkTitle_SelectAll"] . "\">" . $loc["SelectAll"] . "</a>&nbsp;&nbsp;&nbsp;"
  1359. . "\n\t\t<a href=\"JavaScript:checkall(false,'marked%5B%5D')\" title=\"" . $loc["LinkTitle_DeselectAll"] . "\">" . $loc["DeselectAll"] . "</a>"
  1360. . "\n\t</td>";
  1361. }
  1362. else // don't show the select/deselect links when the results footer is hidden
  1363. {
  1364. $BrowseLinks .= "\n\t<td class=\"small\" align=\"left\" valign=\"bottom\" width=\"225\">&nbsp;</td>";
  1365. }
  1366. if (preg_match("/^Mobile$/i", $viewType))
  1367. $BrowseLinks .= "\n\t<div class=\"pagenav\">";
  1368. else
  1369. $BrowseLinks .= "\n\t<td class=\"pagenav\" align=\"center\" valign=\"bottom\">";
  1370. // a) If there's a page range below the one currently shown,
  1371. // create a "[xx-xx]" link (linking directly to the previous range of pages):
  1372. if ($pageOffset > "0")
  1373. {
  1374. $previousRangeFirstPage = ($pageOffset - $maxPageNo + 1); // calculate the first page of the previous page range
  1375. $previousRangeLastPage = ($previousRangeFirstPage + $maxPageNo - 1); // calculate the last page of the previous page range
  1376. $BrowseLinks .= "\n\t\t<a href=\"" . $baseURL . $href
  1377. . "?sqlQuery=" . rawurlencode($query)
  1378. . "&amp;submit=" . $displayType
  1379. . "&amp;citeStyle=" . rawurlencode($citeStyle)
  1380. . "&amp;citeOrder=" . $citeOrder
  1381. . "&amp;orderBy=" . rawurlencode($orderBy)
  1382. . "&amp;headerMsg=" . rawurlencode($headerMsg)
  1383. . "&amp;showQuery=" . $showQuery
  1384. . "&amp;showLinks=" . $showLinks
  1385. . "&amp;formType=" . $formType
  1386. . "&amp;showRows=" . $showRows
  1387. . "&amp;rowOffset=" . (($pageOffset - $maxPageNo) * $showRows)
  1388. . "&amp;client=" . rawurlencode($client)
  1389. . "&amp;viewType=" . $viewType
  1390. . "\" title=\"" . $loc["LinkTitle_DisplayResultsPage"] . " " . $previousRangeFirstPage . " " . $loc["LinkTitle_DisplayLinksToResultsPages"] . " " . $previousRangeFirstPage . "&#8211;" . $previousRangeLastPage . "\">[" . $previousRangeFirstPage . "&#8211;" . $previousRangeLastPage . "] </a>";
  1391. }
  1392. // b) Are there any previous pages?
  1393. if ($rowOffset > 0)
  1394. // Yes, so create a previous link
  1395. $BrowseLinks .= "\n\t\t<a href=\"" . $baseURL . $href
  1396. . "?sqlQuery=" . rawurlencode($query)
  1397. . "&amp;submit=" . $displayType
  1398. . "&amp;citeStyle=" . rawurlencode($citeStyle)
  1399. . "&amp;citeOrder=" . $citeOrder
  1400. . "&amp;orderBy=" . rawurlencode($orderBy)
  1401. . "&amp;headerMsg=" . rawurlencode($headerMsg)
  1402. . "&amp;showQuery=" . $showQuery
  1403. . "&amp;showLinks=" . $showLinks
  1404. . "&amp;formType=" . $formType
  1405. . "&amp;showRows=" . $showRows
  1406. . "&amp;rowOffset=" . $previousOffset
  1407. . "&amp;client=" . rawurlencode($client)
  1408. . "&amp;viewType=" . $viewType
  1409. . "\"" . addAccessKey("attribute", "previous") . " title=\"" . $loc["LinkTitle_DisplayPreviousResultsPage"] . addAccessKey("title", "previous") . "\">&lt;&lt;</a>";
  1410. else
  1411. // No, there is no previous page so don't print a link
  1412. $BrowseLinks .= "\n\t\t&lt;&lt;";
  1413. // c) Output the page numbers as links:
  1414. // Count through the number of pages in the results:
  1415. for($x=($pageOffset * $showRows), $page=($pageOffset + 1);
  1416. $x<$rowsFound && $page <= ($pageOffset + $maxPageNo);
  1417. $x+=$showRows, $page++)
  1418. // Is this the current page?
  1419. if ($x < $rowOffset ||
  1420. $x > ($rowOffset + $showRows - 1))
  1421. // No, so print out a link
  1422. $BrowseLinks .= " \n\t\t<a href=\"" . $baseURL . $href
  1423. . "?sqlQuery=" . rawurlencode($query)
  1424. . "&amp;submit=" . $displayType
  1425. . "&amp;citeStyle=" . rawurlencode($citeStyle)
  1426. . "&amp;citeOrder=" . $citeOrder
  1427. . "&amp;orderBy=" . rawurlencode($orderBy)
  1428. . "&amp;headerMsg=" . rawurlencode($headerMsg)
  1429. . "&amp;showQuery=" . $showQuery
  1430. . "&amp;showLinks=" . $showLinks
  1431. . "&amp;formType=" . $formType
  1432. . "&amp;showRows=" . $showRows
  1433. . "&amp;rowOffset=" . $x
  1434. . "&amp;client=" . rawurlencode($client)
  1435. . "&amp;viewType=" . $viewType
  1436. . "\" title=\"" . $loc["LinkTitle_DisplayResultsPage"] . " " . $page . "\">" . $page . "</a>";
  1437. else
  1438. // Yes, so don't print a link
  1439. $BrowseLinks .= " \n\t\t<b>$page</b>"; // current page is set in <b>BOLD</b>
  1440. $BrowseLinks .= " ";
  1441. // d) Are there any Next pages?
  1442. if ($rowsFound > $nextOffset)
  1443. // Yes, so create a next link
  1444. $BrowseLinks .= "\n\t\t<a href=\"" . $baseURL . $href
  1445. . "?sqlQuery=" . rawurlencode($query)
  1446. . "&amp;submit=" . $displayType
  1447. . "&amp;citeStyle=" . rawurlencode($citeStyle)
  1448. . "&amp;citeOrder=" . $citeOrder
  1449. . "&amp;orderBy=" . rawurlencode($orderBy)
  1450. . "&amp;headerMsg=" . rawurlencode($headerMsg)
  1451. . "&amp;showQuery=" . $showQuery
  1452. . "&amp;showLinks=" . $showLinks
  1453. . "&amp;formType=" . $formType
  1454. . "&amp;showRows=" . $showRows
  1455. . "&amp;rowOffset=" . $nextOffset
  1456. . "&amp;client=" . rawurlencode($client)
  1457. . "&amp;viewType=" . $viewType
  1458. . "\"" . addAccessKey("attribute", "next") . " title=\"" . $loc["LinkTitle_DisplayNextResultsPage"] . addAccessKey("title", "next") . "\">&gt;&gt;</a>";
  1459. else
  1460. // No, there is no next page so don't print a link
  1461. $BrowseLinks .= "\n\t\t&gt;&gt;";
  1462. // e) If there's a page range above the one currently shown,
  1463. // create a "[xx-xx]" link (linking directly to the next range of pages):
  1464. if ($pageOffset < ($lastPage - $maxPageNo))
  1465. {
  1466. $nextRangeFirstPage = ($pageOffset + $maxPageNo + 1); // calculate the first page of the next page range
  1467. $nextRangeLastPage = ($nextRangeFirstPage + $maxPageNo - 1); // calculate the last page of the next page range
  1468. if ($nextRangeLastPage > $lastPage)
  1469. $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
  1470. $BrowseLinks .= "\n\t\t<a href=\"" . $baseURL . $href
  1471. . "?sqlQuery=" . rawurlencode($query)
  1472. . "&amp;submit=" . $displayType
  1473. . "&amp;citeStyle=" . rawurlencode($citeStyle)
  1474. . "&amp;citeOrder=" . $citeOrder
  1475. . "&amp;orderBy=" . rawurlencode($orderBy)
  1476. . "&amp;headerMsg=" . rawurlencode($headerMsg)
  1477. . "&amp;showQuery=" . $showQuery
  1478. . "&amp;showLinks=" . $showLinks
  1479. . "&amp;formType=" . $formType
  1480. . "&amp;showRows=" . $showRows
  1481. . "&amp;rowOffset=" . (($pageOffset + $maxPageNo) * $showRows)
  1482. . "&amp;client=" . rawurlencode($client)
  1483. . "&amp;viewType=" . $viewType
  1484. . "\" title=\"" . $loc["LinkTitle_DisplayResultsPage"] . " " . $nextRangeFirstPage . " " . $loc["LinkTitle_DisplayLinksToResultsPages"] . " " . $nextRangeFirstPage . "&#8211;" . $nextRangeLastPage . "\"> [" . $nextRangeFirstPage . "&#8211;" . $nextRangeLastPage . "]</a>";
  1485. }
  1486. if (preg_match("/^Mobile$/i", $viewType))
  1487. $BrowseLinks .= "\n\t</div>";
  1488. else
  1489. $BrowseLinks .= "\n\t</td>";
  1490. if (preg_match("/^Mobile$/i", $viewType))
  1491. $BrowseLinks .= "\n\t<div class=\"viewnav\">";
  1492. else
  1493. $BrowseLinks .= "\n\t<td class=\"viewnav\" align=\"right\" valign=\"bottom\" width=\"225\">";
  1494. // Add view links:
  1495. $viewLinksArray = array();
  1496. if (($href == "search.php") AND !preg_match("/^Browse$/i", $displayType) AND !preg_match("/^Mobile$/i", $viewType))
  1497. {
  1498. if (isset($_SESSION['user_permissions']) AND preg_match("/allow_list_view/", $_SESSION['user_permissions']))
  1499. {
  1500. if (preg_match("/^(Cite|Display)$/i", $displayType)) // display a link to List view:
  1501. {
  1502. // Replace current SELECT clause with one that's appropriate for List view:
  1503. if (isset($_SESSION['lastListViewQuery']))
  1504. $listViewSelectClause = "SELECT " . extractSELECTclause($_SESSION['lastListViewQuery']); // get SELECT clause from any previous List view query
  1505. else
  1506. $listViewSelectClause = buildSELECTclause("List", $showLinks, "", false, false); // produce the default columnar output style
  1507. $listViewQuery = newSELECTclause($listViewSelectClause, $query); // replace SELECT clause in current query and URL encode query
  1508. // f) create a 'List View' link that will show the currently displayed result set in List view:
  1509. $viewLinksArray[] = "<div class=\"leftview\"><a href=\"" . $baseURL . $href
  1510. . "?sqlQuery=" . $listViewQuery
  1511. . "&amp;submit=List"
  1512. . "&amp;citeStyle=" . rawurlencode($citeStyle)
  1513. . "&amp;citeOrder=" . $citeOrder
  1514. . "&amp;orderBy=" . rawurlencode($orderBy)
  1515. . "&amp;headerMsg=" . rawurlencode($headerMsg)
  1516. . "&amp;showQuery=" . $showQuery
  1517. . "&amp;showLinks=" . $showLinks
  1518. . "&amp;formType=" . $formType
  1519. . "&amp;showRows=" . $showRows
  1520. . "&amp;rowOffset=" . $rowOffset
  1521. . "&amp;client=" . rawurlencode($client)
  1522. . "&amp;viewType=" . $viewType
  1523. . "\"" . addAccessKey("attribute", "list") . " title=\"" . $loc["LinkTitle_DisplayListView"] . addAccessKey("title", "list") . "\">" . $loc["ListView"] . "</a></div>";
  1524. }
  1525. else
  1526. $viewLinksArray[] = "<div class=\"activeview\"><div class=\"leftview\">" . $loc["ListView"] . "</div></div>";
  1527. }
  1528. if (isset($_SESSION['user_permissions']) AND preg_match("/allow_cite/", $_SESSION['user_permissions']))
  1529. {
  1530. if (!preg_match("/^Cite$/i", $displayType)) // display a link to Citation view:
  1531. {
  1532. // Replace current SELECT clause with one that's appropriate for Citation view:
  1533. $citeViewSelectClause = buildSELECTclause("Cite", $showLinks, "", false, false); // select all fields required to build proper record citations
  1534. $citeViewQuery = newSELECTclause($citeViewSelectClause, $query); // replace SELECT clause in current query and URL encode query
  1535. // g) create a 'Citations' link that will show the currently displayed result set in Citation view:
  1536. $viewLinksArray[] = "<div class=\"middleview\"><a href=\"" . $baseURL . $href
  1537. . "?sqlQuery=" . $citeViewQuery
  1538. . "&amp;submit=Cite"
  1539. . "&amp;citeStyle=" . rawurlencode($citeStyle)
  1540. . "&amp;citeOrder=" . $citeOrder
  1541. . "&amp;orderBy=" . rawurlencode($orderBy)
  1542. . "&amp;headerMsg=" . rawurlencode($headerMsg)
  1543. . "&amp;showQuery=" . $showQuery
  1544. . "&amp;showLinks=" . $showLinks
  1545. . "&amp;formType=" . $formType
  1546. . "&amp;showRows=" . $showRows
  1547. . "&amp;rowOffset=" . $rowOffset
  1548. . "&amp;client=" . rawurlencode($client)
  1549. . "&amp;viewType=" . $viewType
  1550. . "\"" . addAccessKey("attribute", "cite") . " title=\"" . $loc["LinkTitle_DisplayCiteView"] . addAccessKey("title", "cite") . "\">" . $loc["Citations"] . "</a></div>";
  1551. }
  1552. else
  1553. $viewLinksArray[] = "<div class=\"activeview\"><div class=\"middleview\">" . $loc["Citations"] . "</div></div>";
  1554. }
  1555. if (isset($_SESSION['user_permissions']) AND preg_match("/allow_details_view/", $_SESSION['user_permissions']))
  1556. {
  1557. if (!preg_match("/^Display$/i", $displayType)) // display a link to Details view:
  1558. {
  1559. // Replace current SELECT clause with one that's appropriate for Details view:
  1560. if (isset($_SESSION['lastDetailsViewQuery']))
  1561. $detailsViewSelectClause = "SELECT " . extractSELECTclause($_SESSION['lastDetailsViewQuery']); // get SELECT clause from previous Details view query (if any)
  1562. else
  1563. $detailsViewSelectClause = buildSELECTclause("Display", $showLinks, "", false, false); // select all fields required to display record details
  1564. $detailsViewQuery = newSELECTclause($detailsViewSelectClause, $query); // replace SELECT clause in current query and URL encode query
  1565. // h) create a 'Details' link that will show the currently displayed result set in Details view:
  1566. $viewLinksArray[] = "<div class=\"rightview\"><a href=\"" . $baseURL . $href
  1567. . "?sqlQuery=" . $detailsViewQuery
  1568. . "&amp;submit=Display"
  1569. . "&amp;citeStyle=" . rawurlencode($citeStyle)
  1570. . "&amp;citeOrder=" . $citeOrder
  1571. . "&amp;orderBy=" . rawurlencode($orderBy)
  1572. . "&amp;headerMsg=" . rawurlencode($headerMsg)
  1573. . "&amp;showQuery=" . $showQuery
  1574. . "&amp;showLinks=" . $showLinks
  1575. . "&amp;formType=" . $formType
  1576. . "&amp;showRows=" . $showRows
  1577. . "&amp;rowOffset=" . $rowOffset
  1578. . "&amp;client=" . rawurlencode($client)
  1579. . "&amp;viewType=" . $viewType
  1580. . "\"" . addAccessKey("attribute", "details") . " title=\"" . $loc["LinkTitle_DisplayDetailsView"] . addAccessKey("title", "details") . "\">" . $loc["Details"] . "</a></div>";
  1581. }
  1582. else
  1583. $viewLinksArray[] = "<div class=\"activeview\"><div class=\"rightview\">" . $loc["Details"] . "</div></div>";
  1584. }
  1585. if (count($viewLinksArray) > 1)
  1586. $BrowseLinks .= "\n\t\t<div class=\"resultviews\">"
  1587. . "\n\t\t\t" . implode("\n\t\t\t&nbsp;|&nbsp;", $viewLinksArray)
  1588. . "\n\t\t</div>";
  1589. }
  1590. // Note: we omit 'Web/Print View' links for include mechanisms!
  1591. if (!preg_match("/^inc/i", $client))
  1592. {
  1593. $BrowseLinks .= "\n\t\t";
  1594. if (count($viewLinksArray) > 1)
  1595. $BrowseLinks .= "&nbsp;&nbsp;&nbsp;";
  1596. if (preg_match("/^(Print|Mobile)$/i", $viewType))
  1597. {
  1598. // i) create a 'Web View' link that will show the currently displayed result set in web view:
  1599. $BrowseLinks .= "<a class=\"toggleprint\" href=\"" . $baseURL . $href
  1600. . "?sqlQuery=" . rawurlencode($query)
  1601. . "&amp;submit=" . $displayType
  1602. . "&amp;citeStyle=" . rawurlencode($citeStyle)
  1603. . "&amp;citeOrder=" . $citeOrder
  1604. . "&amp;orderBy=" . rawurlencode($orderBy)
  1605. . "&amp;headerMsg=" . rawurlencode($headerMsg)
  1606. . "&amp;showQuery=" . $showQuery
  1607. . "&amp;showLinks=1"
  1608. . "&amp;formType=" . $formType
  1609. . "&amp;showRows=" . $showRows
  1610. . "&amp;rowOffset=" . $rowOffset
  1611. . "&amp;viewType=Web"
  1612. . "\"" . 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>";
  1613. }
  1614. else
  1615. {
  1616. if (isset($_SESSION['user_permissions']) AND preg_match("/allow_print_view/", $_SESSION['user_permissions'])) // if the 'user_permissions' session variable contains 'allow_print_view'...
  1617. // j) create a 'Print View' link that will show the currently displayed result set in print view:
  1618. $BrowseLinks .= "<a class=\"toggleprint\" href=\"" . $baseURL . $href
  1619. . "?sqlQuery=" . rawurlencode($query)
  1620. . "&amp;submit=" . $displayType
  1621. . "&amp;citeStyle=" . rawurlencode($citeStyle)
  1622. . "&amp;citeOrder=" . $citeOrder
  1623. . "&amp;orderBy=" . rawurlencode($orderBy)
  1624. . "&amp;headerMsg=" . rawurlencode($headerMsg)
  1625. . "&amp;showQuery=" . $showQuery
  1626. . "&amp;showLinks=0"
  1627. . "&amp;formType=" . $formType
  1628. . "&amp;showRows=" . $showRows
  1629. . "&amp;rowOffset=" . $rowOffset
  1630. . "&amp;viewType=Print"
  1631. . "\"" . 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>";
  1632. }
  1633. }
  1634. if (preg_match("/^Mobile$/i", $viewType))
  1635. {
  1636. $BrowseLinks .= "\n\t</div>"
  1637. . "\n</div>";
  1638. }
  1639. else
  1640. {
  1641. $BrowseLinks .= "\n\t</td>"
  1642. . "\n</tr>"
  1643. . "\n</table>";
  1644. }
  1645. return $BrowseLinks;
  1646. }
  1647. // --------------------------------------------------------------------
  1648. // BUILD QUICK SEARCH ELEMENTS
  1649. // (i.e., generate the "Quick Search" form)
  1650. function buildQuickSearchElements($query, $queryURL, $showQuery, $showLinks, $showRows, $citeStyle, $citeOrder, $displayType)
  1651. {
  1652. global $tableRefs; // defined in 'db.inc.php'
  1653. global $loc; // '$loc' is made globally available in 'core.php'
  1654. global $client;
  1655. if (!preg_match("/^SELECT/i", $queryURL) OR !preg_match("/%20FROM%20" . $tableRefs . "%20/i", $queryURL)) // only include SELECT queries that query table 'refs'
  1656. $queryURL = ""; // this excludes e.g. queries that query table 'users'
  1657. $encodedCiteStyle = rawurlencode($citeStyle);
  1658. $encodedClient = rawurlencode($client);
  1659. $accessKeyAttribute = addAccessKey("attribute", "qck_search");
  1660. $accessKeyTitle = addAccessKey("title", "qck_search");
  1661. // extract the first field from the 'WHERE' clause:
  1662. if (preg_match("/ WHERE [ ()]*(\w+)/i", $query))
  1663. $firstField = preg_replace("/.+ WHERE [ ()]*(\w+).*/i", "\\1", $query);
  1664. else
  1665. $firstField = "";
  1666. // build HTML elements that allow for search suggestions for text entered by the user:
  1667. if (isset($_SESSION['userAutoCompletions']) AND ($_SESSION['userAutoCompletions'] == "yes"))
  1668. $suggestElements = buildSuggestElements("quickSearchName", "quickSearchSuggestions", "quickSearchSuggestProgress", "id-quickSearchSelector-", "\t\t\t\t\t\t");
  1669. else
  1670. $suggestElements = "";
  1671. // add the "Quick Search" form:
  1672. $quickSearchForm = <<<EOF
  1673. <form action="search.php" method="GET" name="quickSearch">
  1674. <fieldset>
  1675. <legend>$loc[QuickSearch]:</legend>
  1676. <input type="hidden" name="formType" value="quickSearch">
  1677. <input type="hidden" name="originalDisplayType" value="$displayType">
  1678. <input type="hidden" name="sqlQuery" value="$queryURL">
  1679. <input type="hidden" name="showQuery" value="$showQuery">
  1680. <input type="hidden" name="showLinks" value="$showLinks">
  1681. <input type="hidden" name="showRows" value="$showRows">
  1682. <input type="hidden" name="client" value="$encodedClient">
  1683. <input type="hidden" name="citeStyle" value="$encodedCiteStyle">
  1684. <input type="hidden" name="citeOrder" value="$citeOrder">
  1685. <div id="queryField">
  1686. <label for="quickSearchSelector">$loc[Field]:</label>
  1687. <select id="quickSearchSelector" name="quickSearchSelector" title="$loc[DescriptionSelectFieldQuickSearchForm]">
  1688. EOF;
  1689. // build correct option tags:
  1690. $userMainFieldsArray = preg_split("/ *, */", $_SESSION['userMainFields']); // get the list of "main fields" preferred by the current user
  1691. $dropDownFieldNameArray = array("main_fields" => $loc["DropDownFieldName_MainFields"]);
  1692. foreach($userMainFieldsArray as $userMainField)
  1693. {
  1694. // generate the variable name of the correct '$loc' locale for this field:
  1695. $dropDownFieldNameLocale = preg_replace_callback("/_(\w)/",
  1696. function($matches){
  1697. foreach($matches as $match){
  1698. return ucfirst($match);
  1699. }
  1700. },
  1701. $userMainField);
  1702. $dropDownFieldNameLocale = "DropDownFieldName_" . ucfirst($dropDownFieldNameLocale);
  1703. // add this field's name and localized string to the array of fields that will be included in the "Quick Search" drop-down menu:
  1704. $dropDownFieldNameArray[$userMainField] = $loc[$dropDownFieldNameLocale];
  1705. }
  1706. $optionTags = buildSelectMenuOptions($dropDownFieldNameArray, "//", "\t\t\t\t\t\t\t", true);
  1707. if (!empty($firstField) AND in_array($firstField, $userMainFieldsArray)) // if the first field from the 'WHERE' clause is one of the main fields
  1708. $quickSearchForm .= preg_replace("/<option([^>]* value=\"$firstField\")/", "<option\\1 selected", $optionTags); // we select that field by adding the 'selected' parameter to the appropriate <option> tag
  1709. else
  1710. $quickSearchForm .= preg_replace("/<option([^>]*)>" . $loc["DropDownFieldName_MainFields"] . "/", "<option\\1 selected>" . $loc["DropDownFieldName_MainFields"], $optionTags); // select the 'main fields' menu entry ...
  1711. $quickSearchForm .= <<<EOF
  1712. </select>
  1713. <label for="quickSearchName">$loc[contains]:</label>
  1714. <input type="text" id="quickSearchName" name="quickSearchName" size="11"$accessKeyAttribute title="$loc[DescriptionEnterSearchString]$accessKeyTitle">$suggestElements
  1715. </div>
  1716. <div id="querySubmit">
  1717. <input type="submit" value="$loc[ButtonTitle_Search]" title="$loc[DescriptionSearchDB]">
  1718. </div>
  1719. </fieldset>
  1720. </form>
  1721. EOF;
  1722. return $quickSearchForm;
  1723. }
  1724. // --------------------------------------------------------------------
  1725. // BUILD REFINE SEARCH ELEMENTS
  1726. // (i.e., provide options to refine the search results)
  1727. function buildRefineSearchElements($href, $queryURL, $showQuery, $showLinks, $showRows, $citeStyle, $citeOrder, $dropDownFieldsArray, $dropDownFieldSelected, $displayType)
  1728. {
  1729. global $loc; // '$loc' is made globally available in 'core.php'
  1730. global $client;
  1731. $encodedCiteStyle = rawurlencode($citeStyle);
  1732. $encodedClient = rawurlencode($client);
  1733. $accessKeyAttribute = addAccessKey("attribute", "refine");
  1734. $accessKeyTitle = addAccessKey("title", "refine");
  1735. // build HTML elements that allow for search suggestions for text entered by the user:
  1736. if (($href == "search.php") AND (isset($_SESSION['userAutoCompletions']) AND ($_SESSION['userAutoCompletions'] == "yes")))
  1737. $suggestElements = buildSuggestElements("refineSearchName", "refineSearchSuggestions", "refineSearchSuggestProgress", "id-refineSearchSelector-", "\t\t\t\t\t");
  1738. else
  1739. $suggestElements = "";
  1740. $refineSearchForm = <<<EOF
  1741. <form action="$href" method="GET" name="refineSearch">
  1742. <fieldset>
  1743. <legend>$loc[SearchWithinResults]:</legend>
  1744. <input type="hidden" name="formType" value="refineSearch">
  1745. <input type="hidden" name="submit" value="$loc[ButtonTitle_Search]">
  1746. <input type="hidden" name="originalDisplayType" value="$displayType">
  1747. <input type="hidden" name="sqlQuery" value="$queryURL">
  1748. <input type="hidden" name="showQuery" value="$showQuery">
  1749. <input type="hidden" name="showLinks" value="$showLinks">
  1750. <input type="hidden" name="showRows" value="$showRows">
  1751. <input type="hidden" name="client" value="$encodedClient">
  1752. <input type="hidden" name="citeStyle" value="$encodedCiteStyle">
  1753. <input type="hidden" name="citeOrder" value="$citeOrder">
  1754. <div id="refineField">
  1755. <label for="refineSearchSelector">$loc[Field]:</label>
  1756. <select id="refineSearchSelector" name="refineSearchSelector" title="$loc[DescriptionSelectFieldRefineResultsForm]">
  1757. EOF;
  1758. // build correct option tags from the column items provided:
  1759. $optionTags = buildSelectMenuOptions($dropDownFieldsArray, "//", "\t\t\t\t\t\t", true);
  1760. $optionTags = preg_replace("/<option([^>]* value=\"$dropDownFieldSelected\")/", "<option\\1 selected", $optionTags); // add 'selected' attribute
  1761. $refineSearchForm .= $optionTags;
  1762. $refineSearchForm .= <<<EOF
  1763. </select>
  1764. <label for="refineSearchName">$loc[contains]:</label>
  1765. <input type="text" id="refineSearchName" name="refineSearchName" size="11"$accessKeyAttribute title="$loc[DescriptionEnterSearchString]$accessKeyTitle">$suggestElements
  1766. </div>
  1767. <div id="refineOpt">
  1768. <input type="checkbox" id="refineSearchExclude" name="refineSearchExclude" value="1" title="$loc[DescriptionExcludeResultsCheckboxRefineResultsForm]">
  1769. <label for="refineSearchExclude">$loc[ExcludeMatches]</label>
  1770. </div>
  1771. <div id="refineSubmit">
  1772. <input type="submit" name="submit" value="$loc[ButtonTitle_Search]" title="$loc[DescriptionSearchButtonRefineResultsForm]">
  1773. </div>
  1774. </fieldset>
  1775. </form>
  1776. EOF;
  1777. return $refineSearchForm;
  1778. }
  1779. // --------------------------------------------------------------------
  1780. // BUILD USER GROUP FORM ELEMENTS
  1781. // (i.e., provide options to show the user's personal reference groups -OR- the admin's user groups)
  1782. // Note: this function serves two purposes (which must not be confused!):
  1783. // - 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*)
  1784. // - 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*)
  1785. function buildGroupSearchElements($href, $queryURL, $query, $showQuery, $showLinks, $showRows, $citeStyle, $citeOrder, $displayType)
  1786. {
  1787. global $loc; // '$loc' is made globally available in 'core.php'
  1788. global $client;
  1789. if (preg_match("/.+user_groups RLIKE \"[()|^.;* ]+[^;]+?[()|$.;* ]+\"/i", $query)) // if the query does contain a 'WHERE' clause that searches for a particular user group
  1790. // TODO: improve the legibility & robustness of the below regex pattern (yes, it's ugly)
  1791. $currentGroup = preg_replace("/.+user_groups RLIKE \"(?:\[:(?:space|punct):\]|[()|^.;* \]\[])+([^;]+?(?:\[\^\[:space:\]\[:punct:\]\]\*)?)(?:\[:(?:space|punct):\]|[()|$.;* \]\[])+\".*/i", "\\1", $query); // extract the particular group name
  1792. else
  1793. $currentGroup = "";
  1794. // show the 'Show My Groups' form:
  1795. // - 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-
  1796. // - if a user is logged in AND the 'user_permissions' session variable contains 'allow_user_groups'
  1797. if (($href == "users.php") OR (isset($_SESSION['loginEmail']) AND (isset($_SESSION['user_permissions']) AND preg_match("/allow_user_groups/", $_SESSION['user_permissions']))))
  1798. {
  1799. if (($href == "search.php" AND isset($_SESSION['userGroups'])) OR ($href == "users.php" AND isset($_SESSION['adminUserGroups']))) // if the appropriate session variable is set
  1800. {
  1801. $groupSearchDisabled = "";
  1802. $groupSearchSelectorTitle = $loc["DescriptionSelectFieldGroupsForm"];
  1803. $groupSearchButtonTitle = $loc["DescriptionShowButtonGroupsForm"];
  1804. }
  1805. else
  1806. {
  1807. $groupSearchDisabled = " disabled"; // disable the 'Show My Groups' form if the session variable holding the user's groups isnt't available
  1808. $groupSearchSelectorTitle = "(" . $loc["DescriptionSelectFieldGroupsFormDisabled"] . ")";
  1809. $groupSearchButtonTitle = "(" . $loc["DescriptionShowButtonGroupsFormDisabled"] . ")";
  1810. }
  1811. // adjust the form & dropdown labels according to the calling script (which is either 'search.php' or 'users.php')
  1812. if ($href == "search.php")
  1813. {
  1814. $formLegend = $loc["ShowMyGroup"] . ":";
  1815. $dropdownLabel = $loc["My"] . ":";
  1816. }
  1817. elseif ($href == "users.php")
  1818. {
  1819. $formLegend = $loc["ShowUserGroup"] . ":";
  1820. $dropdownLabel = $loc["Users"] . ":";
  1821. }
  1822. else // currently, '$href' will be either 'search.php' or 'users.php', but anyhow
  1823. {
  1824. $formLegend = $loc["ShowGroup"] . ":";
  1825. $dropdownLabel = "";
  1826. }
  1827. $encodedCiteStyle = rawurlencode($citeStyle);
  1828. $encodedClient = rawurlencode($client);
  1829. $groupSearchForm = <<<EOF
  1830. <form action="$href" method="GET" name="groupSearch">
  1831. <fieldset>
  1832. <legend>$formLegend</legend>
  1833. <input type="hidden" name="formType" value="groupSearch">
  1834. <input type="hidden" name="originalDisplayType" value="$displayType">
  1835. <input type="hidden" name="sqlQuery" value="$queryURL">
  1836. <input type="hidden" name="showQuery" value="$showQuery">
  1837. <input type="hidden" name="showLinks" value="$showLinks">
  1838. <input type="hidden" name="showRows" value="$showRows">
  1839. <input type="hidden" name="client" value="$encodedClient">
  1840. <input type="hidden" name="citeStyle" value="$encodedCiteStyle">
  1841. <input type="hidden" name="citeOrder" value="$citeOrder">
  1842. <div id="groupSelect">
  1843. <label for="groupSearchSelector">$dropdownLabel</label>
  1844. <select id="groupSearchSelector" name="groupSearchSelector" title="$groupSearchSelectorTitle"$groupSearchDisabled>
  1845. EOF;
  1846. if (($href == "search.php" AND isset($_SESSION['userGroups'])) OR ($href == "users.php" AND isset($_SESSION['adminUserGroups']))) // if the appropriate session variable is set
  1847. {
  1848. // build properly formatted <option> tag elements from the items listed in the appropriate session variable:
  1849. if ($href == "search.php")
  1850. $optionTags = buildSelectMenuOptions($_SESSION['userGroups'], "/ *; */", "\t\t\t\t\t\t", false);
  1851. elseif ($href == "users.php")
  1852. $optionTags = buildSelectMenuOptions($_SESSION['adminUserGroups'], "/ *; */", "\t\t\t\t\t\t", false);
  1853. if (!empty($currentGroup)) // if the current SQL query contains a 'WHERE' clause that searches for a particular user group
  1854. $optionTags = preg_replace("/<option>(?=$currentGroup<\\/option>)/i", "<option selected>", $optionTags); // we select that group by adding the 'selected' parameter to the appropriate <option> tag
  1855. $groupSearchForm .= $optionTags;
  1856. }
  1857. else
  1858. $groupSearchForm .= "<option>($loc[NoGroupsAvl])</option>";
  1859. $groupSearchForm .= <<<EOF
  1860. </select>
  1861. </div>
  1862. <div id="groupSubmit">
  1863. <input type="submit" value="$loc[ButtonTitle_Show]" title="$groupSearchButtonTitle"$groupSearchDisabled>
  1864. </div>
  1865. </fieldset>
  1866. </form>
  1867. EOF;
  1868. }
  1869. else
  1870. $groupSearchForm = "";
  1871. return $groupSearchForm;
  1872. }
  1873. // --------------------------------------------------------------------
  1874. // BUILD DISPLAY OPTIONS FORM ELEMENTS
  1875. // (i.e., provide options to show/hide columns or change the number of records displayed per page)
  1876. function buildDisplayOptionsElements($href, $queryURL, $showQuery, $showLinks, $rowOffset, $showRows, $citeStyle, $citeOrder, $dropDownFieldsArray, $dropDownFieldSelected, $fieldsToDisplay, $displayType, $headerMsg)
  1877. {
  1878. global $loc; // '$loc' is made globally available in 'core.php'
  1879. global $client;
  1880. if ($displayType == "Browse")
  1881. {
  1882. $submitValue = $loc["ButtonTitle_Browse"];
  1883. $submitTitle = $loc["DescriptionShowButtonDisplayOptionsFormBrowseView"];
  1884. $selectorDivID = "optShowHideField";
  1885. $selectorID = "displayOptionsSelector";
  1886. $selectorLabel = $loc["Field"] . ":";
  1887. $selectorTitle = $loc["DescriptionSelectFieldDisplayOptionsFormBrowseView"];
  1888. $showRowsLabel = $loc["ShowRecordsPerPage_SuffixBrowseView"];
  1889. $showRowsTitle = $loc["DescriptionShowRecordsPerPageBrowseView"];
  1890. }
  1891. elseif ($displayType == "Cite")
  1892. {
  1893. $submitValue = $loc["ButtonTitle_Show"];
  1894. $submitTitle = $loc["DescriptionShowButtonDisplayOptionsFormCiteView"];
  1895. $selectorDivID = "optCiteStyle";
  1896. $selectorID = "citeStyle";
  1897. $selectorLabel = $loc["Style"] . ":";
  1898. $selectorTitle = $loc["DescriptionSelectStyleDisplayOptionsFormCiteView"];
  1899. $showRowsLabel = $loc["ShowRecordsPerPage_SuffixCiteView"];
  1900. $showRowsTitle = $loc["DescriptionShowRecordsPerPage"];
  1901. }
  1902. elseif ($displayType == "Display")
  1903. {
  1904. $submitValue = $loc["ButtonTitle_Show"];
  1905. $submitTitle = $loc["DescriptionShowButtonDisplayOptionsFormDetailsView"];
  1906. $selectorDivID = "optShowHideField";
  1907. $selectorID = "displayOptionsSelector";
  1908. $selectorLabel = $loc["Field"] . ":";
  1909. $selectorTitle = $loc["DescriptionSelectFieldDisplayOptionsFormDetailsView"];
  1910. $showRowsLabel = $loc["ShowRecordsPerPage_Suffix"];
  1911. $showRowsTitle = $loc["DescriptionShowRecordsPerPage"];
  1912. }
  1913. else
  1914. {
  1915. $submitValue = $loc["ButtonTitle_Show"];
  1916. $submitTitle = $loc["DescriptionShowButtonDisplayOptionsForm"];
  1917. $selectorDivID = "optShowHideField";
  1918. $selectorID = "displayOptionsSelector";
  1919. $selectorLabel = $loc["Field"] . ":";
  1920. $selectorTitle = $loc["DescriptionSelectFieldDisplayOptionsForm"];
  1921. $showRowsLabel = $loc["ShowRecordsPerPage_Suffix"];
  1922. $showRowsTitle = $loc["DescriptionShowRecordsPerPage"];
  1923. }
  1924. if (($displayType != "Cite") AND ($fieldsToDisplay < 2))
  1925. {
  1926. $hideButtonDisabled = " disabled"; // disable the 'Hide' button if there's currently only one field being displayed (except the links column)
  1927. $hideButtonTitle = "(" . $loc["DescriptionHideButtonDisplayOptionsFormOnlyOneField"] . ")";
  1928. }
  1929. else
  1930. {
  1931. $hideButtonDisabled = "";
  1932. if ($displayType == "Display")
  1933. $hideButtonTitle = $loc["DescriptionHideButtonDisplayOptionsFormDetailsView"];
  1934. else
  1935. $hideButtonTitle = $loc["DescriptionHideButtonDisplayOptionsForm"];
  1936. }
  1937. if (($displayType == "Cite") AND (!isset($_SESSION['user_styles'])))
  1938. $citeStyleDisabled = " disabled"; // for Citation view, disable the style popup if the session variable holding the user's styles isn't available
  1939. else
  1940. $citeStyleDisabled = "";
  1941. $encodedCiteStyle = rawurlencode($citeStyle);
  1942. $encodedClient = rawurlencode($client);
  1943. $encodedHeaderMsg = rawurlencode($headerMsg);
  1944. $accessKeyAttribute = addAccessKey("attribute", "max_rows");
  1945. $accessKeyTitle = addAccessKey("title", "max_rows");
  1946. // 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.
  1947. // 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'!
  1948. $displayOptionsForm = <<<EOF
  1949. <form action="$href" method="GET" name="displayOptions">
  1950. <fieldset>
  1951. <legend>$loc[DisplayOptions]:</legend>
  1952. <input type="hidden" name="formType" value="displayOptions">
  1953. <input type="hidden" name="submit" value="$submitValue">
  1954. <input type="hidden" name="originalDisplayType" value="$displayType">
  1955. <input type="hidden" name="sqlQuery" value="$queryURL">
  1956. <input type="hidden" name="showQuery" value="$showQuery">
  1957. <input type="hidden" name="showLinks" value="$showLinks">
  1958. <input type="hidden" name="rowOffset" value="$rowOffset">
  1959. <input type="hidden" name="showRows" value="$showRows">
  1960. <input type="hidden" name="client" value="$encodedClient">
  1961. <input type="hidden" name="citeStyle" value="$encodedCiteStyle">
  1962. <input type="hidden" name="citeOrder" value="$citeOrder">
  1963. <input type="hidden" name="headerMsg" value="$encodedHeaderMsg">
  1964. <div id="optMain">
  1965. <div id="$selectorDivID">
  1966. <label for="$selectorID">$selectorLabel</label>
  1967. <select id="$selectorID" name="$selectorID" title="$selectorTitle"$citeStyleDisabled>
  1968. EOF;
  1969. // build correct option tags from the column items provided:
  1970. $optionTags = buildSelectMenuOptions($dropDownFieldsArray, "//", "\t\t\t\t\t\t\t", true);
  1971. $optionTags = preg_replace("/<option([^>]* value=\"$dropDownFieldSelected\")/", "<option\\1 selected", $optionTags); // add 'selected' attribute
  1972. $displayOptionsForm .= $optionTags;
  1973. $displayOptionsForm .= <<<EOF
  1974. </select>
  1975. </div>
  1976. EOF;
  1977. $displayOptionsForm .= <<<EOF
  1978. <div id="optSubmit">
  1979. <input type="submit" name="submit" value="$submitValue" title="$submitTitle">
  1980. EOF;
  1981. if (!preg_match("/^(Browse|Cite)$/i", $displayType))
  1982. $displayOptionsForm .= "\n\t\t\t\t\t\t<input type=\"submit\" name=\"submit\" value=\"" . $loc["ButtonTitle_Hide"] . "\" title=\"$hideButtonTitle\"$hideButtonDisabled>";
  1983. $displayOptionsForm .= <<<EOF
  1984. </div>
  1985. </div>
  1986. <div id="optOther">
  1987. EOF;
  1988. if ($displayType == "Cite")
  1989. {
  1990. $displayOptionsForm .= <<<EOF
  1991. <div id="optCiteOrder">
  1992. <label for="citeOrder">$loc[SortBy]:</label>
  1993. <select id="citeOrder" name="citeOrder" title="$loc[DescriptionSelectOrderDisplayOptionsFormCiteView]">
  1994. EOF;
  1995. // build correct option tags for the "Sort by" ('citeOrder') dropdown menu (and select the currently chosen option):
  1996. $citeOrderItemsArray = array("author" => $loc["DropDownFieldName_Author"],
  1997. "year" => $loc["DropDownFieldName_Year"],
  1998. "type" => $loc["DropDownFieldName_Type"],
  1999. "type-year" => $loc["DropDownFieldName_TypeYear"],
  2000. "creation-date" => $loc["DropDownFieldName_CreationDate"]);
  2001. $citeOrderOptionTags = buildSelectMenuOptions($citeOrderItemsArray, "//", "\t\t\t\t\t\t\t", true);
  2002. if (isset($citeOrderItemsArray[$citeOrder]))
  2003. $citeOrderOptionTags = preg_replace("/<option([^>]*)>(" . $citeOrderItemsArray[$citeOrder] . ")<\\/option>/", "<option\\1 selected>\\2</option>", $citeOrderOptionTags); // add 'selected' attribute to the currently chosen 'citeOrder' option
  2004. else // add & select a "(custom order)" option (which indicates that the current sort order matches none of the above 'citeOrder' options):
  2005. $citeOrderOptionTags = "\n\t\t\t\t\t\t\t<option value=\"\" selected>(" . $loc["DropDownFieldName_Custom"] . ")</option>" . $citeOrderOptionTags;
  2006. $displayOptionsForm .= $citeOrderOptionTags;
  2007. $displayOptionsForm .= <<<EOF
  2008. </select>
  2009. </div>
  2010. EOF;
  2011. }
  2012. $displayOptionsForm .= <<<EOF
  2013. <div id="optRecsPerPage">
  2014. <input type="text" id="showRows" name="showRows" value="$showRows" size="4"$accessKeyAttribute title="$showRowsTitle$accessKeyTitle">
  2015. <label for="showRows">$showRowsLabel</label>
  2016. </div>
  2017. </div>
  2018. </fieldset>
  2019. </form>
  2020. EOF;
  2021. return $displayOptionsForm;
  2022. }
  2023. // --------------------------------------------------------------------
  2024. // BUILD SUGGEST ELEMENTS
  2025. // (i.e., provide HTML elements that will generate auto-completions or search suggestions for text entered by the user in text entry fields)
  2026. // requires the Prototype & script.aculo.us JavaScript frameworks: <http://www.prototypejs.org/> and <http://script.aculo.us/>
  2027. // more info about 'Ajax.Autocompleter': <http://github.com/madrobby/scriptaculous/wikis/ajax-autocompleter>
  2028. //
  2029. // 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
  2030. // '$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
  2031. // ('paramName'). This ID is required by the callback function to fetch the name of the currently selected refbase field.
  2032. 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")
  2033. {
  2034. global $contentTypeCharset; // defined in 'ini.inc.php'
  2035. $suggestElements = <<<EOF
  2036. $prefix<span id="$suggestProgressID" class="suggestProgress" style="display:none;">...</span>
  2037. $prefix<div id="$searchSuggestionsID" class="searchSuggestions" style="display:none;"></div>
  2038. $prefix<script language="JavaScript" type="text/javascript" charset="$contentTypeCharset">
  2039. $prefix// <![CDATA[
  2040. $prefix new Ajax.Autocompleter('$searchFieldID','$searchSuggestionsID','$suggestURL',{tokens:$tokens,frequency:$frequency,minChars:$minChars,indicator:'$suggestProgressID',paramName:'$CQLIndex$paramName',parameters:'$parameters',callback:$callBack});
  2041. $prefix// ]]>
  2042. $prefix</script>
  2043. EOF;
  2044. return $suggestElements;
  2045. }
  2046. // --------------------------------------------------------------------
  2047. // Build the database query from user input provided by the "Search within Results" or "Display Options" forms
  2048. // above the query results list (which, in turn, was returned by 'search.php' or 'users.php', respectively):
  2049. // TODO: - build the complete SQL query using functions 'buildFROMclause()' and 'buildORDERclause()'
  2050. function extractFormElementsRefineDisplay($queryTable, $displayType, $originalDisplayType, $query, $showLinks, $citeOrder, $userID)
  2051. {
  2052. global $tableRefs, $tableUserData, $tableUsers; // defined in 'db.inc.php'
  2053. global $loc; // '$loc' is made globally available in 'core.php'
  2054. $encodedDisplayType = encodeHTML($displayType); // note that we need to HTML encode '$displayType' for comparison with the HTML encoded locales
  2055. // extract form variables:
  2056. if ($encodedDisplayType == $loc["ButtonTitle_Search"]) // the user clicked the 'Search' button of the "Search within Results" form
  2057. {
  2058. $fieldSelector = $_REQUEST['refineSearchSelector']; // extract field name chosen by the user
  2059. $refineSearchName = $_REQUEST['refineSearchName']; // extract search text entered by the user
  2060. if (isset($_REQUEST['refineSearchExclude'])) // extract user option whether matched records should be included or excluded
  2061. $refineSearchActionCheckbox = $_REQUEST['refineSearchExclude']; // the user marked the checkbox next to "Exclude matches"
  2062. else
  2063. $refineSearchActionCheckbox = "0"; // the user did NOT mark the checkbox next to "Exclude matches"
  2064. }
  2065. 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
  2066. // (hitting <enter> within the 'ShowRows' text entry field of the "Display Options" form will act as if the user clicked the 'Browse'/'Show' button)
  2067. {
  2068. if (isset($_REQUEST['displayOptionsSelector']))
  2069. $fieldSelector = $_REQUEST['displayOptionsSelector']; // extract field name chosen by the user
  2070. else
  2071. $fieldSelector = "";
  2072. }
  2073. else
  2074. $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)
  2075. // extract the fields of the SELECT clause from the current SQL query:
  2076. $previousSelectClause = extractSELECTclause($query);
  2077. // ensure to add any required fields to the SELECT clause:
  2078. if ($queryTable == $tableRefs) // 'search.php':
  2079. $addRequiredFields = true;
  2080. elseif ($queryTable == $tableUsers) // 'users.php':
  2081. $addRequiredFields = false; // we'll add any required fields to the 'users.php' SELECT clause below
  2082. // TODO: this wouldn't be necessary if function 'buildSELECTclause()' would handle the requirements of 'users.php'
  2083. $additionalFields = "";
  2084. if ($encodedDisplayType == $loc["ButtonTitle_Search"])
  2085. {
  2086. // rebuild the current SELECT clause:
  2087. $newSelectClause = buildSELECTclause($originalDisplayType, $showLinks, $additionalFields, false, $addRequiredFields, $previousSelectClause);
  2088. // replace current SELECT clause:
  2089. $query = newSELECTclause($newSelectClause, $query, false);
  2090. if ($refineSearchName != "") // if the user typed a search string into the text entry field...
  2091. {
  2092. // Depending on the chosen output action, construct an appropriate SQL query:
  2093. if ($refineSearchActionCheckbox == "0") // if the user did NOT mark the checkbox next to "Exclude matches"
  2094. {
  2095. // for the fields 'marked=no', 'copy=false' and 'selected=no', force NULL values to be matched:
  2096. if (($fieldSelector == "marked" AND $refineSearchName == "no") OR ($fieldSelector == "copy" AND $refineSearchName == "false") OR ($fieldSelector == "selected" AND $refineSearchName == "no"))
  2097. $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
  2098. else // add default 'WHERE' clause:
  2099. $query = preg_replace("/ WHERE /i", " WHERE $fieldSelector RLIKE " . quote_smart($refineSearchName) . " AND ", $query); // ...add search field name & value to the SQL query
  2100. }
  2101. else // $refineSearchActionCheckbox == "1" // if the user marked the checkbox next to "Exclude matches"
  2102. {
  2103. $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
  2104. }
  2105. $query = preg_replace('/ AND serial RLIKE "\.\+"/i', '', $query); // remove any 'AND serial RLIKE ".+"' which isn't required anymore
  2106. }
  2107. // else, if the user did NOT type a search string into the text entry field, we simply keep the old WHERE clause...
  2108. }
  2109. elseif (preg_match("/^(" . $loc["ButtonTitle_Show"] . "|" . $loc["ButtonTitle_Hide"] . ")$/", $encodedDisplayType)) // the user clicked the 'Show'/'Hide' buttons of the "Display Options" form
  2110. {
  2111. if (preg_match("/^Cite$/i", $originalDisplayType)) // in case of Citation view, we regenerate the SELECT clause from scratch:
  2112. {
  2113. // generate a SELECT clause that's appropriate for Citation view (or Details view):
  2114. $newSelectClause = buildSELECTclause($originalDisplayType, $showLinks, $additionalFields, false, $addRequiredFields);
  2115. // rebuild the current ORDER clause:
  2116. if (preg_match("/^(author|year|type|type-year|creation-date)$/i", $citeOrder))
  2117. {
  2118. if ($citeOrder == "year") // sort records first by year (descending):
  2119. $newORDER = "ORDER BY year DESC, first_author, author_count, author, title";
  2120. elseif ($citeOrder == "type") // sort records first by record type and thesis type (descending):
  2121. $newORDER = "ORDER BY type DESC, thesis DESC, first_author, author_count, author, year, title";
  2122. elseif ($citeOrder == "type-year") // sort records first by record type and thesis type (descending), then by year (descending):
  2123. $newORDER = "ORDER BY type DESC, thesis DESC, year DESC, first_author, author_count, author, title";
  2124. elseif ($citeOrder == "creation-date") // sort records such that newly added/edited records get listed top of the list:
  2125. $newORDER = "ORDER BY created_date DESC, created_time DESC, modified_date DESC, modified_time DESC, serial DESC";
  2126. elseif ($citeOrder == "author") // supply the default ORDER BY pattern (which is suitable for citation in a journal etc.):
  2127. $newORDER = "ORDER BY first_author, author_count, author, year, title";
  2128. // replace current ORDER clause:
  2129. $query = newORDERclause($newORDER, $query, false);
  2130. }
  2131. // else if any other or no '$citeOrder' parameter is specified, we keep the current ORDER BY clause
  2132. // NOTE: this behaviour is different from functions 'extractFormElementsQueryResults()' and 'extractFormElementsExtract()'
  2133. // where we always use 'ORDER BY first_author, author_count, author, year, title' as default ORDER BY clause
  2134. // (to ensure correct sorting for output to bibliographic reference lists)
  2135. }
  2136. elseif (preg_match("/^Display$/i", $originalDisplayType)) // Details view
  2137. {
  2138. // NOTE: the below code for displaying & hiding of fields in Details view must be adopted if either layout or field names are changed!
  2139. $fieldsList = "";
  2140. if ($fieldSelector == "all fields")
  2141. {
  2142. // generate a SELECT clause that shows all fields in Details view:
  2143. $newSelectClause = buildSELECTclause($originalDisplayType, $showLinks, $additionalFields, true, $addRequiredFields);
  2144. }
  2145. else // add (or remove) the chosen fields from the SELECT clause:
  2146. {
  2147. if ($encodedDisplayType == $loc["ButtonTitle_Show"]) // if the user clicked the 'Show' button, add the chosen fields to the SELECT clause:
  2148. {
  2149. $matchField = "pages";
  2150. if ($fieldSelector == "keywords, abstract")
  2151. {
  2152. $fieldsList = ", keywords, abstract";
  2153. }
  2154. elseif ($fieldSelector == "additional fields")
  2155. {
  2156. $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";
  2157. if (isset($_SESSION['loginEmail']))
  2158. $fieldsList .= ", location"; // we only add the 'location' field if the user is logged in
  2159. if (preg_match("/\babstract\b/i", $previousSelectClause))
  2160. $matchField = "abstract";
  2161. }
  2162. elseif ($fieldSelector == "my fields")
  2163. {
  2164. $fieldsList = ", marked, copy, selected, user_keys, user_notes, user_file, user_groups, cite_key";
  2165. if (preg_match("/\bserial\b/i", $previousSelectClause))
  2166. $matchField = "serial";
  2167. elseif (preg_match("/\babstract\b/i", $previousSelectClause))
  2168. $matchField = "abstract";
  2169. }
  2170. if ((!empty($fieldsList)) AND (!preg_match("/\b" . $fieldsList . "\b/i", $previousSelectClause))) // if none of the chosen fields are currently displayed...
  2171. $previousSelectClause = preg_replace("/(?<=\b" . $matchField . "\b)/i", $fieldsList, $previousSelectClause); // ...add the chosen fields to the current SELECT clause:
  2172. }
  2173. if ($encodedDisplayType == $loc["ButtonTitle_Hide"]) // if the user clicked the 'Hide' button, remove the chosen fields from the SELECT clause:
  2174. {
  2175. if ($fieldSelector == "keywords, abstract")
  2176. $fieldsList = "\b(keywords|abstract)\b";
  2177. elseif ($fieldSelector == "additional fields")
  2178. $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";
  2179. elseif ($fieldSelector == "my fields")
  2180. $fieldsList = "\b(marked|copy|selected|user_keys|user_notes|user_file|user_groups|cite_key)\b";
  2181. if ((!empty($fieldsList)) AND (preg_match("/\b" . $fieldsList . "\b/i", $previousSelectClause))) // ...and any of the chosen fields *are* currently displayed...
  2182. {
  2183. // ...remove the chosen fields from the fields given in the current SELECT clause:
  2184. $previousSelectClause = preg_replace("/ *, *" . $fieldsList . " */i", "", $previousSelectClause); // all columns except the first
  2185. $previousSelectClause = preg_replace("/ *" . $fieldsList . " *, */i", "", $previousSelectClause); // all columns except the last
  2186. }
  2187. }
  2188. // rebuild the current SELECT clause, but include (or exclude) the chosen fields:
  2189. $newSelectClause = buildSELECTclause($originalDisplayType, $showLinks, $additionalFields, false, $addRequiredFields, $previousSelectClause);
  2190. }
  2191. }
  2192. else // otherwise, i.e. for List view, add (or remove) the chosen field from the SELECT clause:
  2193. {
  2194. if ($encodedDisplayType == $loc["ButtonTitle_Show"]) // if the user clicked the 'Show' button...
  2195. {
  2196. if (!preg_match("/\b" . $fieldSelector . "\b/i", $previousSelectClause)) // ...and the chosen field is *not* already displayed...
  2197. $additionalFields = $fieldSelector; // ...add the chosen field to the current SELECT clause
  2198. }
  2199. elseif ($encodedDisplayType == $loc["ButtonTitle_Hide"]) // if the user clicked the 'Hide' button...
  2200. {
  2201. if (preg_match("/\b" . $fieldSelector . "\b/i", $previousSelectClause)) // ...and the chosen field *is* currently displayed...
  2202. {
  2203. // ...remove the chosen field from the fields given in the current SELECT clause:
  2204. $previousSelectClause = preg_replace("/ *, *\b" . $fieldSelector . "\b */i", "", $previousSelectClause); // all columns except the first
  2205. $previousSelectClause = preg_replace("/ *\b" . $fieldSelector . "\b *, */i", "", $previousSelectClause); // all columns except the last
  2206. }
  2207. }
  2208. // rebuild the current SELECT clause, but include (or exclude) the chosen field:
  2209. $newSelectClause = buildSELECTclause("", $showLinks, $additionalFields, false, $addRequiredFields, $previousSelectClause);
  2210. }
  2211. // replace current SELECT clause:
  2212. $query = newSELECTclause($newSelectClause, $query, false);
  2213. }
  2214. // TODO: don't manipulate the SQL query in '$query' directly, but instead use functions 'extractSELECTclause()' and 'buildSELECTclause()' (similar as above)
  2215. elseif ($encodedDisplayType == $loc["ButtonTitle_Browse"]) // if the user clicked the 'Browse' button within the "Display Options" form...
  2216. {
  2217. $previousField = preg_replace("/^SELECT (\w+).+/i", "\\1", $query); // extract the field that was previously used in Browse view
  2218. if (!preg_match("/^" . $fieldSelector . "$/i", $previousField)) // if the user did choose another field in Browse view...
  2219. {
  2220. // ...modify the SQL query to show a summary for the new field that was chosen by the user:
  2221. // (NOTE: these replace patterns aren't 100% safe and may fail if the user has modified the query using 'sql_search.php'!)
  2222. $query = preg_replace("/^SELECT $previousField/i", "SELECT $fieldSelector", $query); // use the field that was chosen by the user for Browse view
  2223. $query = preg_replace("/GROUP BY $previousField/i", "GROUP BY $fieldSelector", $query); // group data by the field that was chosen by the user
  2224. $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
  2225. }
  2226. }
  2227. // re-establish the original display type:
  2228. // (resetting '$displayType' to its original value is required for Browse view; for List view, it does also correct incorrect
  2229. // display types such as 'Search' or 'Show'/'Hide' which stem from the submit buttons in the forms of the results header)
  2230. $displayType = $originalDisplayType;
  2231. // the following changes to the SQL query are performed for both forms ("Search within Results" and "Display Options"):
  2232. if ($queryTable == $tableRefs) // 'search.php':
  2233. {
  2234. // 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'
  2235. 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
  2236. if (!preg_match("/LEFT JOIN $tableUserData/i", $query)) // ...and if the 'LEFT JOIN...' statement isn't already part of the 'FROM' clause...
  2237. $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
  2238. }
  2239. elseif ($queryTable == $tableUsers) // 'users.php':
  2240. {
  2241. // TODO: this wouldn't be necessary if function 'buildSELECTclause()' would handle the requirements of 'users.php' (see also above)
  2242. $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)
  2243. // (which is required in order to obtain unique checkbox names as well as for use in the 'getUserID()' function)
  2244. }
  2245. return array($query, $displayType);
  2246. }
  2247. // --------------------------------------------------------------------
  2248. // SPLIT AND MERGE AGAIN
  2249. // This function takes a string and splits it on '$splitDelim' into an array,
  2250. // then re-joins the pieces inserting '$joinDelim' as separator).
  2251. // Note: The split pattern must be specified as perl-style regular expression
  2252. // (including the leading & trailing slashes) and may include mode
  2253. // modifiers (such as '/.../i' to perform a case insensitive match)
  2254. function splitAndMerge($splitDelim, $joinDelim, $sourceString)
  2255. {
  2256. // split the string on the specified delimiter (which is interpreted as perl-style regular expression!):
  2257. $piecesArray = preg_split($splitDelim, $sourceString);
  2258. // re-join the array with the specified separator:
  2259. $newString = implode($joinDelim, $piecesArray);
  2260. return $newString;
  2261. }
  2262. // --------------------------------------------------------------------
  2263. // EXTRACT PARTS FROM STRING
  2264. // This function takes a '$sourceString', splits it on '$splitDelim' and returns
  2265. // x parts from the beginning (if x > 0) or the end (if x < 0) of the string
  2266. // (x must be given in '$returnParts'); parts will be returned as a merged string
  2267. // using '$joinDelim' as delimiter.
  2268. // Note: The split pattern must be specified as perl-style regular expression
  2269. // (including the leading & trailing slashes) and may include mode
  2270. // modifiers (such as '/.../i' to perform a case insensitive match)
  2271. function extractPartsFromString($sourceString, $splitDelim, $joinDelim, $returnParts)
  2272. {
  2273. // split the string on the specified delimiter (which is interpreted as perl-style regular expression!):
  2274. $piecesArray = preg_split($splitDelim, $sourceString);
  2275. if ($returnParts > 0)
  2276. $spliceFromElementNo = 0; // splice from beginning of array
  2277. else // ($returnParts < 0)
  2278. {
  2279. $spliceFromElementNo = $returnParts; // splice from end of array
  2280. $returnParts = abs($returnParts);
  2281. }
  2282. // extracts parts from array:
  2283. $extractedPiecesArray = array_splice($piecesArray, $spliceFromElementNo, $returnParts); // 'array_splice()' returns array with extracted elements
  2284. // re-join the array with the specified separator:
  2285. $newString = implode($joinDelim, $extractedPiecesArray);
  2286. return $newString;
  2287. }
  2288. // --------------------------------------------------------------------
  2289. // RE-ARRANGE AUTHOR FIELD CONTENTS
  2290. // (this function separates contents of the author field into their functional parts, i.e.:
  2291. // {
  2292. // {author_name}, {author_initial(s)}
  2293. // }
  2294. // {
  2295. // {author_name}, {author_initial(s)}
  2296. // }
  2297. // {
  2298. // ...
  2299. // }
  2300. // then, these functional pieces will be joined again according to the separators specified)
  2301. // Note: this function assumes that:
  2302. // - within one author object, there's only *one* delimiter separating author name & initials!
  2303. // Note: The split pattern must be specified as perl-style regular expression
  2304. // (including the leading & trailing slashes) and may include mode
  2305. // modifiers (such as '/.../i' to perform a case insensitive match)
  2306. function reArrangeAuthorContents($authorContents, $familyNameFirst, $oldBetweenAuthorsDelim, $newBetweenAuthorsDelimStandard, $newBetweenAuthorsDelimLastAuthor, $oldAuthorsInitialsDelim, $newAuthorsInitialsDelimFirstAuthor, $newAuthorsInitialsDelimStandard, $betweenInitialsDelim, $initialsBeforeAuthorFirstAuthor, $initialsBeforeAuthorStandard, $shortenGivenNames, $numberOfAuthorsTriggeringEtAl, $includeNumberOfAuthors, $customStringAfterFirstAuthors, $encodeHTML)
  2307. {
  2308. 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'
  2309. $authorsArray = preg_split($oldBetweenAuthorsDelim, $authorContents); // get a list of all authors for this record
  2310. $authorCount = count($authorsArray); // check how many authors we have to deal with
  2311. $newAuthorContents = ""; // this variable will hold the final author string
  2312. $includeStringAfterFirstAuthor = false;
  2313. if (empty($numberOfAuthorsTriggeringEtAl))
  2314. $numberOfAuthorsTriggeringEtAl = $authorCount;
  2315. if (empty($includeNumberOfAuthors))
  2316. $includeNumberOfAuthors = $authorCount;
  2317. for ($i=0; $i < $authorCount; $i++)
  2318. {
  2319. $singleAuthorArray = preg_split($oldAuthorsInitialsDelim, $authorsArray[$i]); // for each author, extract author name & initials to separate list items
  2320. if (!$familyNameFirst) // if the family name comes *after* the given name (or initials) in the source string, put array elements in reverse order:
  2321. $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!)
  2322. if (isset($singleAuthorArray[1]))
  2323. {
  2324. if ($shortenGivenNames) // if we're supposed to abbreviate given names
  2325. {
  2326. // within initials, reduce all full first names (-> defined by a starting uppercase character, followed by one ore more lowercase characters)
  2327. // to initials, i.e., only retain their first character
  2328. $singleAuthorArray[1] = preg_replace("/([$upper])[$lower]+/$patternModifiers", "\\1", $singleAuthorArray[1]);
  2329. }
  2330. // within initials, remove any dots:
  2331. $singleAuthorArray[1] = preg_replace("/([$upper])\.+/$patternModifiers", "\\1", $singleAuthorArray[1]);
  2332. // within initials, remove any spaces *between* initials:
  2333. $singleAuthorArray[1] = preg_replace("/(?<=[-$upper]) +(?=[-$upper])/$patternModifiers", "", $singleAuthorArray[1]);
  2334. // within initials, add a space after a hyphen, but only if ...
  2335. if (preg_match("/ $/", $betweenInitialsDelim)) // ... the delimiter that separates initials ends with a space
  2336. $singleAuthorArray[1] = preg_replace("/-(?=[$upper])/$patternModifiers", "- ", $singleAuthorArray[1]);
  2337. // then, separate initials with the specified delimiter:
  2338. $singleAuthorArray[1] = preg_replace("/([$upper])(?=[^$lower]+|$)/$patternModifiers", "\\1$betweenInitialsDelim", $singleAuthorArray[1]);
  2339. }
  2340. if ((($i == 0) AND $initialsBeforeAuthorFirstAuthor) OR (($i > 0) AND $initialsBeforeAuthorStandard)) // put array elements in reverse order:
  2341. $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!)
  2342. // re-join author name & initials, using the specified delimiter, and copy the string to the end of an array:
  2343. if ($i == 0) // -> first author
  2344. $singleAuthorString = implode($newAuthorsInitialsDelimFirstAuthor, $singleAuthorArray);
  2345. else // $i > 0 // -> all authors except the first one
  2346. $singleAuthorString = implode($newAuthorsInitialsDelimStandard, $singleAuthorArray);
  2347. // append this author to the final author string:
  2348. if (($i == 0) OR ($i + 1) < $authorCount) // -> first author, or (for multiple authors) all authors except the last one
  2349. {
  2350. if ($i == 0) // -> first author
  2351. $newAuthorContents .= $singleAuthorString;
  2352. else // -> for multiple authors, all authors except the first or the last one
  2353. $newAuthorContents .= $newBetweenAuthorsDelimStandard . $singleAuthorString;
  2354. // 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':
  2355. if ((($i + 1) == $includeNumberOfAuthors) AND ($authorCount > $numberOfAuthorsTriggeringEtAl))
  2356. {
  2357. if (preg_match("/__NUMBER_OF_AUTHORS__/", $customStringAfterFirstAuthors))
  2358. $customStringAfterFirstAuthors = preg_replace("/__NUMBER_OF_AUTHORS__/", ($authorCount - $includeNumberOfAuthors), $customStringAfterFirstAuthors); // resolve placeholder
  2359. $includeStringAfterFirstAuthor = true;
  2360. break;
  2361. }
  2362. }
  2363. elseif (($authorCount > 1) AND (($i + 1) == $authorCount)) // -> last author (if multiple authors)
  2364. {
  2365. $newAuthorContents .= $newBetweenAuthorsDelimLastAuthor . $singleAuthorString;
  2366. }
  2367. }
  2368. // do some final clean up:
  2369. if ($encodeHTML)
  2370. $newAuthorContents = encodeHTML($newAuthorContents); // HTML encode higher ASCII characters within the newly arranged author contents
  2371. if ($includeStringAfterFirstAuthor)
  2372. $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
  2373. $newAuthorContents = preg_replace("/ +/", " ", $newAuthorContents); // remove double spaces (which occur e.g., when both, $betweenInitialsDelim & $newAuthorsInitialsDelim..., end with a space)
  2374. $newAuthorContents = preg_replace("/ +([,.;:?!()]|$)/", "\\1", $newAuthorContents); // remove excess spaces before [,.;:?!()] and from the end of the author string
  2375. return $newAuthorContents;
  2376. }
  2377. // --------------------------------------------------------------------
  2378. // EXTRACT AUTHOR'S LAST NAME
  2379. // This function takes the contents of the author field and will extract the
  2380. // last name of a particular author (specified by position) (e.g., setting
  2381. // '$authorPosition' to "1" will return the 1st author's last name).
  2382. // Note: this function assumes that:
  2383. // 1. within one author object, there's only *one* delimiter separating
  2384. // author name & initials!
  2385. // 2. author objects are stored in the db as
  2386. // "<author_name><author_initials_delimiter><author_initials>", i.e.,
  2387. // initials follow *after* the author's name!
  2388. // Required Parameters:
  2389. // 1. pattern describing delimiter that separates different authors
  2390. // 2. pattern describing delimiter that separates author name & initials
  2391. // (within one author)
  2392. // 3. position of the author whose last name shall be extracted (e.g.,
  2393. // "1" will return the 1st author's last name)
  2394. // 4. contents of the author field
  2395. // Note: The split patterns must be specified as perl-style regular expressions
  2396. // (including the leading & trailing slashes) and may include mode
  2397. // modifiers (such as '/.../i' to perform a case insensitive match)
  2398. function extractAuthorsLastName($oldBetweenAuthorsDelim, $oldAuthorsInitialsDelim, $authorPosition, $authorContents)
  2399. {
  2400. $authorsArray = preg_split($oldBetweenAuthorsDelim, $authorContents); // get a list of all authors for this record
  2401. $authorPosition = ($authorPosition-1); // php array elements start with "0", so we decrease the authors position by 1
  2402. $singleAuthor = $authorsArray[$authorPosition]; // for the author in question, extract the full author name (last name & initials)
  2403. $singleAuthorArray = preg_split($oldAuthorsInitialsDelim, $singleAuthor); // then, extract author name & initials to separate list items
  2404. $singleAuthorsLastName = $singleAuthorArray[0]; // extract this author's last name into a new variable
  2405. return $singleAuthorsLastName;
  2406. }
  2407. // --------------------------------------------------------------------
  2408. // EXTRACT AUTHOR'S GIVEN NAME
  2409. // This function takes the contents of the author field and will extract the
  2410. // initials/given name of a particular author (specified by position) (e.g.,
  2411. // setting '$authorPosition' to "1" will return the 1st author's initials/given
  2412. // name).
  2413. // Required Parameters:
  2414. // 1. pattern describing delimiter that separates different authors
  2415. // 2. pattern describing delimiter that separates author name &
  2416. // initials/given name (within one author)
  2417. // 3. position of the author whose last name shall be extracted
  2418. // (e.g., "1" will return the 1st author's initials/given name)
  2419. // 4. contents of the author field
  2420. // Note: The split patterns must be specified as perl-style regular expressions
  2421. // (including the leading & trailing slashes) and may include mode
  2422. // modifiers (such as '/.../i' to perform a case insensitive match)
  2423. function extractAuthorsGivenName($oldBetweenAuthorsDelim, $oldAuthorsInitialsDelim, $authorPosition, $authorContents)
  2424. {
  2425. $authorsArray = preg_split($oldBetweenAuthorsDelim, $authorContents); // get a list of all authors for this record
  2426. $authorPosition = ($authorPosition-1); // php array elements start with "0", so we decrease the authors position by 1
  2427. $singleAuthor = $authorsArray[$authorPosition]; // for the author in question, extract the full author name (last name & initials/given name)
  2428. $singleAuthorArray = preg_split($oldAuthorsInitialsDelim, $singleAuthor); // then, extract author name & initials/given name to separate list items
  2429. if (!empty($singleAuthorArray[1]))
  2430. $singleAuthorsGivenName = $singleAuthorArray[1]; // extract this author's initials/given name into a new variable
  2431. else
  2432. $singleAuthorsGivenName = '';
  2433. return $singleAuthorsGivenName;
  2434. }
  2435. // --------------------------------------------------------------------
  2436. // PARSE PLACEHOLDER STRING
  2437. // this function will parse a given placeholder string into its indiviual placeholders and replaces
  2438. // them with content from the given record
  2439. function parsePlaceholderString($formVars, $placeholderString, $fallbackPlaceholderString)
  2440. {
  2441. 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'
  2442. if (empty($placeholderString))
  2443. $placeholderString = $fallbackPlaceholderString; // if, for some odd reason, an empty placeholder string was given, we'll use the placeholder(s) given in '$fallbackPlaceholderString'
  2444. $placeholderPartsArray = preg_split("/[<>]/", $placeholderString); // split placeholder string into its individual components
  2445. $convertedPlaceholderArray = array(); // initialize array variable which will hold the transformed placeholder parts
  2446. foreach($placeholderPartsArray as $placeholderPart)
  2447. {
  2448. if (!empty($placeholderPart))
  2449. {
  2450. 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)
  2451. {
  2452. // extract any custom options given within a placeholder:
  2453. if (preg_match("/:\w+\[[^][]+\]:/i", $placeholderPart)) // the placeholder contains custom options
  2454. $options = preg_replace("/.*:\w+(\[[^][]+\]):.*/i", "\\1", $placeholderPart);
  2455. else
  2456. $options = "";
  2457. // extract any prefix given within a placeholder:
  2458. if (preg_match("/^[^:]+:\w+(\[[^][]*\])?:/i", $placeholderPart)) // the placeholder contains a prefix
  2459. $prefix = preg_replace("/^([^:]+):\w+(\[[^][]*\])?:.*/i", "\\1", $placeholderPart);
  2460. else
  2461. $prefix = "";
  2462. // extract any suffix given within a placeholder:
  2463. if (preg_match("/:\w+(\[[^][]*\])?:[^:]+$/i", $placeholderPart)) // the placeholder contains a suffix
  2464. $suffix = preg_replace("/.*:\w+(?:\[[^][]*\])?:([^:]+)$/i", "\\1", $placeholderPart);
  2465. else
  2466. $suffix = "";
  2467. // call dedicated functions for the different placeholders (if required):
  2468. // '<:serial:>' placeholder:
  2469. if (preg_match("/:serial:/i", $placeholderPart))
  2470. $convertedPlaceholderArray[] = $prefix . $formVars['serialNo'] . $suffix;
  2471. // '<:firstAuthor:>' placeholder:
  2472. elseif (preg_match("/:firstAuthor:/i", $placeholderPart))
  2473. {
  2474. if (!empty($formVars['authorName'])) // if the 'author' field isn't empty
  2475. {
  2476. $firstAuthor = $prefix;
  2477. // Call the 'extractAuthorsLastName()' function to extract the last name of a particular author (specified by position):
  2478. // (see function header for description of required parameters)
  2479. $firstAuthor .= extractAuthorsLastName("/ *; */",
  2480. "/ *, */",
  2481. 1,
  2482. $formVars['authorName']);
  2483. $firstAuthor .= $suffix;
  2484. $convertedPlaceholderArray[] = $firstAuthor;
  2485. }
  2486. }
  2487. // '<:secondAuthor:>' placeholder:
  2488. elseif (preg_match("/:secondAuthor:/i", $placeholderPart))
  2489. {
  2490. if (!empty($formVars['authorName']) AND preg_match("/;/", $formVars['authorName'])) // if the 'author' field does contain at least one ';' => at least two authors
  2491. {
  2492. $secondAuthor = $prefix;
  2493. // Call the 'extractAuthorsLastName()' function to extract the last name of a particular author (specified by position):
  2494. // (see function header for description of required parameters)
  2495. $secondAuthor .= extractAuthorsLastName("/ *; */",
  2496. "/ *, */",
  2497. 2,
  2498. $formVars['authorName']);
  2499. $secondAuthor .= $suffix;
  2500. $convertedPlaceholderArray[] = $secondAuthor;
  2501. }
  2502. }
  2503. // '<:authors:>' placeholder:
  2504. elseif (preg_match("/:authors(\[[^][]*\])?:/i", $placeholderPart))
  2505. {
  2506. if (!empty($formVars['authorName'])) // if the 'author' field isn't empty
  2507. {
  2508. $authors = $prefix;
  2509. $authors .= extractDetailsFromAuthors($formVars['authorName'], $options);
  2510. $authors .= $suffix;
  2511. $convertedPlaceholderArray[] = $authors;
  2512. }
  2513. }
  2514. // '<:title:>' placeholder:
  2515. elseif (preg_match("/:title(\[[^][]*\])?:/i", $placeholderPart))
  2516. {
  2517. if (!empty($formVars['titleName'])) // if the 'title' field isn't empty
  2518. {
  2519. $title = $prefix;
  2520. $title .= extractDetailsFromField("title", $formVars['titleName'], "/[^-$word]+/$patternModifiers", $options);
  2521. $title .= $suffix;
  2522. $convertedPlaceholderArray[] = $title;
  2523. }
  2524. }
  2525. // '<:year:>' placeholder:
  2526. elseif (preg_match("/:year(\[[^][]*\])?:/i", $placeholderPart))
  2527. {
  2528. if (!empty($formVars['yearNo']) AND preg_match("/\d+/i", $formVars['yearNo'])) // if the 'year' field contains a number
  2529. {
  2530. $year = $prefix;
  2531. $year .= extractDetailsFromYear($formVars['yearNo'], $options);
  2532. $year .= $suffix;
  2533. $convertedPlaceholderArray[] = $year;
  2534. }
  2535. }
  2536. // '<:publication:>' placeholder:
  2537. elseif (preg_match("/:publication(\[[^][]*\])?:/i", $placeholderPart))
  2538. {
  2539. if (!empty($formVars['publicationName'])) // if the 'publication' field isn't empty
  2540. {
  2541. $publication = $prefix;
  2542. $publication .= extractDetailsFromField("publication", $formVars['publicationName'], "/[^-$word]+/$patternModifiers", $options);
  2543. $publication .= $suffix;
  2544. $convertedPlaceholderArray[] = $publication;
  2545. }
  2546. }
  2547. // '<:abbrevJournal:>' placeholder:
  2548. elseif (preg_match("/:abbrevJournal(\[[^][]*\])?:/i", $placeholderPart))
  2549. {
  2550. if (!empty($formVars['abbrevJournalName'])) // if the 'abbrev_journal' field isn't empty
  2551. {
  2552. $abbrevJournal = $prefix;
  2553. $abbrevJournal .= extractDetailsFromField("abbrev_journal", $formVars['abbrevJournalName'], "/[^-$word]+/$patternModifiers", $options);
  2554. $abbrevJournal .= $suffix;
  2555. $convertedPlaceholderArray[] = $abbrevJournal;
  2556. }
  2557. }
  2558. // '<:volume:>' placeholder:
  2559. elseif (preg_match("/:volume:/i", $placeholderPart))
  2560. {
  2561. if (!empty($formVars['volumeNo'])) // if the 'volume' field isn't empty
  2562. $convertedPlaceholderArray[] = $prefix . $formVars['volumeNo'] . $suffix;
  2563. }
  2564. // '<:issue:>' placeholder:
  2565. elseif (preg_match("/:issue:/i", $placeholderPart))
  2566. {
  2567. if (!empty($formVars['issueNo'])) // if the 'issue' field isn't empty
  2568. $convertedPlaceholderArray[] = $prefix . $formVars['issueNo'] . $suffix;
  2569. }
  2570. // '<:pages:>' placeholder:
  2571. elseif (preg_match("/:pages:/i", $placeholderPart))
  2572. {
  2573. if (!empty($formVars['pagesNo'])) // if the 'pages' field isn't empty
  2574. $convertedPlaceholderArray[] = $prefix . $formVars['pagesNo'] . $suffix;
  2575. }
  2576. // '<:startPage:>' placeholder:
  2577. elseif (preg_match("/:startPage:/i", $placeholderPart))
  2578. {
  2579. if (!empty($formVars['pagesNo']) AND preg_match("/\d+/i", $formVars['pagesNo'])) // if the 'pages' field contains a number
  2580. {
  2581. $startPage = $prefix;
  2582. $startPage .= preg_replace("/^\D*?(\w*\d+\w*).*/i", "\\1", $formVars['pagesNo']); // extract starting page
  2583. $startPage .= $suffix;
  2584. $convertedPlaceholderArray[] = $startPage;
  2585. }
  2586. }
  2587. // '<:endPage:>' placeholder:
  2588. elseif (preg_match("/:endPage:/i", $placeholderPart))
  2589. {
  2590. if (!empty($formVars['pagesNo']) AND preg_match("/\d+/i", $formVars['pagesNo'])) // if the 'pages' field contains a number
  2591. {
  2592. $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
  2593. $endPage = $prefix;
  2594. $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)
  2595. $endPage .= $suffix;
  2596. $convertedPlaceholderArray[] = $endPage;
  2597. }
  2598. }
  2599. // '<:keywords:>' placeholder:
  2600. elseif (preg_match("/:keywords(\[[^][]*\])?:/i", $placeholderPart))
  2601. {
  2602. if (!empty($formVars['keywordsName'])) // if the 'keywords' field isn't empty
  2603. {
  2604. $keywords = $prefix;
  2605. $keywords .= extractDetailsFromField("keywords", $formVars['keywordsName'], "/ *[;,] */", $options);
  2606. $keywords .= $suffix;
  2607. $convertedPlaceholderArray[] = $keywords;
  2608. }
  2609. }
  2610. // '<:issn:>' placeholder:
  2611. elseif (preg_match("/:issn:/i", $placeholderPart))
  2612. {
  2613. if (!empty($formVars['issnName']))
  2614. $convertedPlaceholderArray[] = $prefix . $formVars['issnName'] . $suffix;
  2615. }
  2616. // '<:isbn:>' placeholder:
  2617. elseif (preg_match("/:isbn:/i", $placeholderPart))
  2618. {
  2619. if (!empty($formVars['isbnName']))
  2620. $convertedPlaceholderArray[] = $prefix . $formVars['isbnName'] . $suffix;
  2621. }
  2622. // '<:issn_isbn:>' placeholder:
  2623. elseif (preg_match("/:issn_isbn:/i", $placeholderPart))
  2624. {
  2625. if (!empty($formVars['issnName'])) // if both, an ISSN and ISBN number are present, the ISSN number will be preferred
  2626. $convertedPlaceholderArray[] = $prefix . $formVars['issnName'] . $suffix;
  2627. elseif (!empty($formVars['isbnName']))
  2628. $convertedPlaceholderArray[] = $prefix . $formVars['isbnName'] . $suffix;
  2629. }
  2630. // '<:area:>' placeholder:
  2631. elseif (preg_match("/:area(\[[^][]*\])?:/i", $placeholderPart))
  2632. {
  2633. if (!empty($formVars['areaName'])) // if the 'area' field isn't empty
  2634. {
  2635. $area = $prefix;
  2636. $area .= extractDetailsFromField("area", $formVars['areaName'], "/ *[;,] */", $options);
  2637. $area .= $suffix;
  2638. $convertedPlaceholderArray[] = $area;
  2639. }
  2640. }
  2641. // '<:notes:>' placeholder:
  2642. elseif (preg_match("/:notes(\[[^][]*\])?:/i", $placeholderPart))
  2643. {
  2644. if (!empty($formVars['notesName'])) // if the 'notes' field isn't empty
  2645. {
  2646. $notes = $prefix;
  2647. $notes .= extractDetailsFromField("notes", $formVars['notesName'], "/[^-$word]+/$patternModifiers", $options);
  2648. $notes .= $suffix;
  2649. $convertedPlaceholderArray[] = $notes;
  2650. }
  2651. }
  2652. // '<:userKeys:>' placeholder:
  2653. elseif (preg_match("/:userKeys(\[[^][]*\])?:/i", $placeholderPart))
  2654. {
  2655. if (!empty($formVars['userKeysName'])) // if the 'user_keys' field isn't empty
  2656. {
  2657. $userKeys = $prefix;
  2658. $userKeys .= extractDetailsFromField("user_keys", $formVars['userKeysName'], "/ *[;,] */", $options);
  2659. $userKeys .= $suffix;
  2660. $convertedPlaceholderArray[] = $userKeys;
  2661. }
  2662. }
  2663. // '<:citeKey:>' placeholder:
  2664. elseif (preg_match("/:citeKey:/i", $placeholderPart))
  2665. {
  2666. if (!empty($formVars['citeKeyName'])) // if the 'cite_key' field isn't empty
  2667. $convertedPlaceholderArray[] = $prefix . $formVars['citeKeyName'] . $suffix;
  2668. }
  2669. // '<:doi:>' placeholder:
  2670. elseif (preg_match("/:doi:/i", $placeholderPart))
  2671. {
  2672. if (!empty($formVars['doiName'])) // if the 'doi' field isn't empty
  2673. $convertedPlaceholderArray[] = $prefix . $formVars['doiName'] . $suffix;
  2674. }
  2675. // '<:recordIdentifier:>' placeholder:
  2676. elseif (preg_match("/:recordIdentifier:/i", $placeholderPart))
  2677. {
  2678. if (!empty($formVars['citeKeyName'])) // if the 'cite_key' field isn't empty
  2679. $convertedPlaceholderArray[] = $prefix . $formVars['citeKeyName'] . $suffix; // if available, we prefer the user-specific cite key as unique record identifier
  2680. else
  2681. $convertedPlaceholderArray[] = $prefix . $formVars['serialNo'] . $suffix; // otherwise we'll use the record's serial number
  2682. }
  2683. // '<:randomNumber:>' placeholder:
  2684. elseif (preg_match("/:randomNumber(\[[^][]*\])?:/i", $placeholderPart))
  2685. {
  2686. $randomString = $prefix;
  2687. $randomString .= generateRandomNumber($options);
  2688. $randomString .= $suffix;
  2689. $convertedPlaceholderArray[] = $randomString;
  2690. }
  2691. // else: un-recognized placeholders will be ignored
  2692. }
  2693. else // the part is assumed to be a literal string
  2694. {
  2695. $convertedPlaceholderArray[] = $placeholderPart; // add part as is to array of transformed placeholder parts
  2696. }
  2697. }
  2698. }
  2699. $convertedPlaceholderString = implode("", $convertedPlaceholderArray); // merge transformed placeholder parts
  2700. return $convertedPlaceholderString;
  2701. }
  2702. // --------------------------------------------------------------------
  2703. // EXTRACT AUTHOR NAMES AND GENERATE IDENTIFIER STRING
  2704. // this function takes the contents of the author field and generates an author identifier string (see comments below for
  2705. // some examples) which is used by the file name (and cite key) auto-generation feature when replacing the <:authors:> placeholder
  2706. function extractDetailsFromAuthors($authorString, $options)
  2707. {
  2708. global $extractDetailsAuthorsDefault; // defined in 'ini.inc.php'
  2709. $returnRawAuthorString = false;
  2710. if (empty($options)) // if '$options' is empty
  2711. $options = $extractDetailsAuthorsDefault; // load the default options
  2712. 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!)
  2713. {
  2714. // extract the individual options:
  2715. if (preg_match("/^\[-?\d+\|/i", $options)) // if the first option contains a number
  2716. {
  2717. $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: '[^]...]'
  2718. if ($useMaxNoOfAuthors == 0) // the special number '0' indicates that all authors shall be retrieved
  2719. $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)
  2720. elseif ($useMaxNoOfAuthors < 0) // negative numbers have currently no meaning and will be treated as if the corresponding positive number was given
  2721. $useMaxNoOfAuthors = abs($useMaxNoOfAuthors);
  2722. }
  2723. 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
  2724. $returnRawAuthorString = true;
  2725. $authorConnectorString = preg_replace("/\[-?\d*\|([^][|]*)\|[^][|]*\]/i", "\\1", $options);
  2726. $etalIdentifierString = preg_replace("/\[-?\d*\|[^][|]*\|([^][|]*)\]/i", "\\1", $options);
  2727. }
  2728. else // use yet another fallback if the given options contain a buggy syntax
  2729. {
  2730. $useMaxNoOfAuthors = 2;
  2731. $authorConnectorString = "+";
  2732. $etalIdentifierString = "_etal";
  2733. }
  2734. if ($returnRawAuthorString)
  2735. $authorDetails = $authorString; // return the raw author string without any modification
  2736. else
  2737. {
  2738. $authorDetails = ""; // initialize variable which will hold the author identifier string
  2739. // Add first author (plus additional authors if available up to the number of authors specified in '$useMaxNoOfAuthors');
  2740. // but if more authors are present as in '$useMaxNoOfAuthors', add the contents of '$etalIdentifierString' after the first(!) author ignoring all other authors.
  2741. // Example with '$extractDetailsAuthorsDefault = "[2|+|_etal]"':
  2742. // $authorString = "Steffens, M" -> $authorDetails = "Steffens"
  2743. // $authorString = "Steffens, M; Thomas, D" -> $authorDetails = "Steffens+Thomas"
  2744. // $authorString = "Steffens, M; Thomas, D; Dieckmann, GS" -> $authorDetails = "Steffens_etal"
  2745. // Example with '$extractDetailsAuthorsDefault = "[1|+|++]"':
  2746. // $authorString = "Steffens, M" -> $authorDetails = "Steffens"
  2747. // $authorString = "Steffens, M; Thomas, D" -> $authorDetails = "Steffens++"
  2748. // $authorString = "Steffens, M; Thomas, D; Dieckmann, GS" -> $authorDetails = "Steffens++"
  2749. for ($i=1; $i <= ($useMaxNoOfAuthors + 1); $i++)
  2750. {
  2751. if (preg_match("/^[^;]+(;[^;]+){" . ($i - 1) . "}/", $authorString)) // if the 'author' field does contain (at least) as many authors as specified in '$i'
  2752. {
  2753. if ($i>1)
  2754. {
  2755. if (preg_match("/^[^;]+(;[^;]+){" . $useMaxNoOfAuthors . "}/", $authorString)) // if the 'author' field does contain more authors as specified in '$useMaxNoOfAuthors'
  2756. {
  2757. $authorDetails .= $etalIdentifierString;
  2758. break;
  2759. }
  2760. else
  2761. $authorDetails .= $authorConnectorString;
  2762. }
  2763. // Call the 'extractAuthorsLastName()' function to extract the last name of a particular author (specified by position):
  2764. // (see function header for description of required parameters)
  2765. $authorDetails .= extractAuthorsLastName("/ *; */",
  2766. "/ *, */",
  2767. $i,
  2768. $authorString);
  2769. }
  2770. else
  2771. break;
  2772. }
  2773. }
  2774. return $authorDetails;
  2775. }
  2776. // --------------------------------------------------------------------
  2777. // EXTRACT YEAR
  2778. // this function takes the contents of the year field and returns the year in two-digit
  2779. // or four-digit format (depending on the given '$option' which must be either "[2]" or "[4]")
  2780. function extractDetailsFromYear($yearString, $options)
  2781. {
  2782. global $extractDetailsYearDefault; // defined in 'ini.inc.php'
  2783. if (empty($options)) // if '$options' is empty
  2784. $options = $extractDetailsYearDefault; // load the default option
  2785. if (preg_match("/^\[[24]\]$/i", $options)) // if the '$options' variable contains a recognized syntax
  2786. $yearDigitFormat = preg_replace("/^\[([24])\]$/i", "\\1", $options); // extract the individual option
  2787. else // use yet another fallback if the given option contains a buggy syntax
  2788. $yearDigitFormat = 4;
  2789. if (preg_match("/^\D*\d{4}/i", $yearString))
  2790. {
  2791. if ($yearDigitFormat == 2)
  2792. $yearDetails = preg_replace("/^\D*\d{2}(\d{2}).*/i", "\\1", $yearString);
  2793. else
  2794. $yearDetails = preg_replace("/^\D*(\d{4}).*/i", "\\1", $yearString);
  2795. }
  2796. else
  2797. $yearDetails = $yearString; // fallback
  2798. return $yearDetails;
  2799. }
  2800. // --------------------------------------------------------------------
  2801. // EXTRACT DETAILS FROM FIELD
  2802. // this function takes the contents of the title/publication/abbrev_journal/pages/keywords/area/notes/user_keys field
  2803. // and returns x words/items from the beginning (or end) of the string (depending on the given '$option' which must be
  2804. // of the form "[x]" where x is a number indicating how many words/items shall be returned; positive number: return x
  2805. // words/items from beginning of string, negative number: return x words/items from end of string)
  2806. function extractDetailsFromField($fieldName, $sourceString, $splitDelim, $options)
  2807. {
  2808. global $extractDetailsTitleDefault; // these variables are defined in 'ini.inc.php'
  2809. global $extractDetailsPublicationDefault;
  2810. global $extractDetailsAbbrevJournalDefault;
  2811. global $extractDetailsKeywordsDefault;
  2812. global $extractDetailsAreaDefault;
  2813. global $extractDetailsNotesDefault;
  2814. global $extractDetailsUserKeysDefault;
  2815. $returnRawSourceString = false;
  2816. if (empty($options)) // if '$options' is empty load the default option
  2817. {
  2818. if ($fieldName == "title")
  2819. $options = $extractDetailsTitleDefault;
  2820. elseif ($fieldName == "publication")
  2821. $options = $extractDetailsPublicationDefault;
  2822. elseif ($fieldName == "abbrev_journal")
  2823. $options = $extractDetailsAbbrevJournalDefault;
  2824. elseif ($fieldName == "pages")
  2825. $options = "[1]";
  2826. elseif ($fieldName == "keywords")
  2827. $options = $extractDetailsKeywordsDefault;
  2828. elseif ($fieldName == "area")
  2829. $options = $extractDetailsAreaDefault;
  2830. elseif ($fieldName == "notes")
  2831. $options = $extractDetailsNotesDefault;
  2832. elseif ($fieldName == "user_keys")
  2833. $options = $extractDetailsUserKeysDefault;
  2834. }
  2835. if (preg_match("/^\[(-?\d+(\|[^][|]*)?|-?\d*\|[^][|]*)\]$/i", $options)) // if the '$options' variable contains a recognized syntax
  2836. {
  2837. // extract the individual options:
  2838. if (preg_match("/^\[-?\d+/i", $options)) // if the first option contains a number
  2839. {
  2840. $extractNumberOfWords = preg_replace("/^\[(-?\d+)(\|[^][|]*)?\]$/i", "\\1", $options);
  2841. if ($extractNumberOfWords == 0) // the special number '0' indicates that all field items shall be retrieved
  2842. $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)
  2843. }
  2844. 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
  2845. $returnRawSourceString = true;
  2846. if (preg_match("/^\[-?\d*\|[^][|]+\]$/i", $options)) // if the second option contains some content
  2847. $joinDelim = preg_replace("/^\[-?\d*\|([^][|]+)\]$/i", "\\1", $options);
  2848. else
  2849. $joinDelim = "";
  2850. }
  2851. else // use yet another fallback if the given option contains a buggy syntax
  2852. {
  2853. $extractNumberOfWords = 1;
  2854. $joinDelim = "";
  2855. }
  2856. if (!($returnRawSourceString) AND preg_match($splitDelim, $sourceString))
  2857. $sourceStringDetails = extractPartsFromString($sourceString, $splitDelim, $joinDelim, $extractNumberOfWords);
  2858. else
  2859. $sourceStringDetails = $sourceString; // fallback
  2860. return $sourceStringDetails;
  2861. }
  2862. // --------------------------------------------------------------------
  2863. // GENERATE RANDOM NUMBER
  2864. // this function generates a random number taken from the range which is defined in '$options' (format: "[min|max]", e.g. "[0|9999]"),
  2865. // if '$options' is empty the maximum possible range will be used
  2866. function generateRandomNumber($options)
  2867. {
  2868. global $extractDetailsRandomNumberDefault; // defined in 'ini.inc.php'
  2869. if (empty($options)) // if '$options' is empty
  2870. $options = $extractDetailsRandomNumberDefault; // load the default options
  2871. if (preg_match("/^\[\d+\|\d+\]$/i", $options)) // if the '$options' variable contains a recognized syntax
  2872. {
  2873. // extract the individual options:
  2874. $minRandomNumber = preg_replace("/\[(\d+)\|.+/i", "\\1", $options); // extract first option which defines the minimum random number
  2875. $maxRandomNumber = preg_replace("/\[\d+\|(\d+)\]/i", "\\1", $options); // extract second option which defines the maximum random number
  2876. // generate random number:
  2877. $randomNumber = mt_rand($minRandomNumber, $maxRandomNumber);
  2878. }
  2879. else // no (or unrecognized) options
  2880. {
  2881. // generate random number:
  2882. $randomNumber = mt_rand(); // if called without the optional min, max arguments 'mt_rand()' returns a pseudo-random value between 0 and RAND_MAX
  2883. }
  2884. return $randomNumber;
  2885. }
  2886. // --------------------------------------------------------------------
  2887. // GET UPLOAD INFO
  2888. // Given the name of a file upload field, return a four (or five) element associative
  2889. // array containing information about the file. The element names are:
  2890. // name - original name of file on client
  2891. // type - MIME type of file (e.g.: 'image/gif')
  2892. // tmp_name - name of temporary file on server
  2893. // error - holds an error number >0 if something went wrong, otherwise 0
  2894. // (the 'error' element was added with PHP 4.2.0. Error code explanation: <http://www.php.net/file-upload.errors>)
  2895. // size - size of file in bytes
  2896. // depending what happend on upload, they will contain the following values (PHP 4.1 and above):
  2897. // no file upload upload exceeds 'upload_max_filesize' successful upload
  2898. // -------------- ------------------------------------ -----------------
  2899. // name "" [name] [name]
  2900. // type "" "" [type]
  2901. // tmp_name "" OR "none" "" [tmp_name]
  2902. // error 4 1 0
  2903. // size 0 0 [size]
  2904. // The function prefers the $_FILES array if it is available, falling back
  2905. // to $HTTP_POST_FILES and $HTTP_POST_VARS as necessary.
  2906. function getUploadInfo($name)
  2907. {
  2908. global $HTTP_POST_FILES, $HTTP_POST_VARS;
  2909. $uploadFileInfo = array(); // initialize array variable
  2910. // Look for information in PHP 4.1 $_FILES array first.
  2911. // Note: The entry in $_FILES might be present even if no file was uploaded (see above).
  2912. // Check the 'tmp_name' and/or the 'error' member to make sure there is a file.
  2913. if (isset($_FILES))
  2914. if (isset($_FILES[$name]))
  2915. $uploadFileInfo = ($_FILES[$name]);
  2916. // Look for information in PHP 4 $HTTP_POST_FILES array next.
  2917. // (Again, check the 'tmp_name' and/or the 'error' member to make sure there is a file.)
  2918. elseif (isset($HTTP_POST_FILES))
  2919. if (isset($HTTP_POST_FILES[$name]))
  2920. $uploadFileInfo = ($HTTP_POST_FILES[$name]);
  2921. // Look for PHP 3 style upload variables.
  2922. // Check the _name member, because $HTTP_POST_VARS[$name] might not
  2923. // actually be a file field.
  2924. elseif (isset($HTTP_POST_VARS[$name])
  2925. && isset($HTTP_POST_VARS[$name . "_name"]))
  2926. {
  2927. // Map PHP 3 elements to PHP 4-style element names
  2928. $uploadFileInfo["name"] = $HTTP_POST_VARS[$name . "_name"];
  2929. $uploadFileInfo["tmp_name"] = $HTTP_POST_VARS[$name];
  2930. $uploadFileInfo["size"] = $HTTP_POST_VARS[$name . "_size"];
  2931. $uploadFileInfo["type"] = $HTTP_POST_VARS[$name . "_type"];
  2932. }
  2933. 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
  2934. $uploadFileInfo["tmp_name"] = ""; // in order to standardize array output we replace 'none' with an empty string
  2935. return $uploadFileInfo;
  2936. }
  2937. // --------------------------------------------------------------------
  2938. // BUILD RELATED RECORDS LINK
  2939. // (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;
  2940. // clicking this link will show all records that match the serials or partial queries that were specified within the 'related' field)
  2941. function buildRelatedRecordsLink($relatedFieldString, $userID)
  2942. {
  2943. global $tableRefs, $tableUserData; // defined in 'db.inc.php'
  2944. // initialize some arrays:
  2945. $serialsArray = array(); // we'll use this array to hold all record serial numbers that we encounter
  2946. $queriesArray = array(); // this array will hold all sub-queries that were extracted from the 'related' field
  2947. // split the source string on any semi-colon ";" (optionally surrounded by whitespace) which works as our main delimiter:
  2948. $relatedFieldArray = preg_split("/ *; */", $relatedFieldString);
  2949. foreach ($relatedFieldArray as $relatedFieldArrayElement)
  2950. {
  2951. $relatedFieldArrayElement = trim($relatedFieldArrayElement); // remove any preceding or trailing whitespace
  2952. if (!empty($relatedFieldArrayElement))
  2953. {
  2954. if (is_numeric($relatedFieldArrayElement)) // if the current array element is a number, we assume its a serial number
  2955. $serialsArray[] = $relatedFieldArrayElement; // append the current array element to the end of the serials array
  2956. else
  2957. {
  2958. // replace any colon ":" (optionally surrounded by whitespace) with " RLIKE " and enclose the search value with quotes:
  2959. // (as an example, 'author:steffens, m' will be transformed to 'author RLIKE "steffens, m"')
  2960. if (preg_match("/:/",$relatedFieldArrayElement))
  2961. $relatedFieldArrayElement = preg_replace("/ *: *(.+)/"," RLIKE \"\\1\"",$relatedFieldArrayElement);
  2962. // else we assume '$relatedFieldArrayElement' to contain a valid 'WHERE' clause!
  2963. $queriesArray[] = $relatedFieldArrayElement; // append the current array element to the end of the queries array
  2964. }
  2965. }
  2966. }
  2967. if (!empty($serialsArray)) // if the 'related' field did contain any record serials
  2968. {
  2969. $serialsString = implode("|", $serialsArray);
  2970. $serialsString = "serial RLIKE " . quote_smart("^(" . $serialsString . ")$");
  2971. $queriesArray[] = $serialsString; // append the serial query to the end of the queries array
  2972. }
  2973. // re-join the queries array with an "OR" separator:
  2974. $queriesString = implode(" OR ", $queriesArray);
  2975. // build the full SQL query:
  2976. // TODO: build the complete SQL query using functions 'buildFROMclause()' and 'buildORDERclause()'
  2977. $relatedQuery = buildSELECTclause("", "", "", false, false);
  2978. // 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:
  2979. if (preg_match("/marked|copy|selected|user_keys|user_notes|user_file|user_groups|cite_key|related/",$queriesString))
  2980. $relatedQuery .= " FROM $tableRefs LEFT JOIN $tableUserData ON serial = record_id AND user_id = $userID";
  2981. else // we skip the 'LEFT JOIN...' part of the 'FROM' clause:
  2982. $relatedQuery .= " FROM $tableRefs";
  2983. $relatedQuery .= " WHERE " . $queriesString . " ORDER BY author, year DESC, publication"; // add 'WHERE' & 'ORDER BY' clause
  2984. // build the correct query URL:
  2985. $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)
  2986. return $relatedRecordsLink;
  2987. }
  2988. // --------------------------------------------------------------------
  2989. // LINKIFY FIELD ITEMS
  2990. // (this function generates 'show.php' HTML links for the items of the given field)
  2991. // NOTE: HTML links are only generated for fields that are supported by 'show.php', and
  2992. // only for those where it makes sense (e.g. the 'abstract' field doesn't get linked).
  2993. // The list of fields can be adopted in variable '$linkedFields' in 'ini.inc.php'.
  2994. function linkifyFieldItems($field, $fieldData, $userID = "", $localSearchReplaceActionsArray = array(), $encodingExceptionsArray = array(), $itemDelim = "/\s*[;]+\s*/", $joinDelim = "; ", $showQuery = "", $showLinks = "", $showRows = "", $citeStyle = "", $citeOrder = "", $wrapResults = "", $displayType = "", $viewType = "")
  2995. {
  2996. global $databaseBaseURL; // these variables are defined in 'ini.inc.php'
  2997. global $linkedFields;
  2998. global $loc; // '$loc' is made globally available in 'core.php'
  2999. global $client;
  3000. // Fields that may contain multiple items (delimited by '$itemDelim'):
  3001. static $multiItemFields = array("author", "keywords", "area", "notes", "location", "contribution_id",
  3002. "user_keys", "user_notes", "user_groups");
  3003. // Fields that require the 'userID' parameter:
  3004. // (note that, currently, 'show.php' only supports the fields 'marked',
  3005. // 'selected', 'user_keys', 'user_notes', 'user_groups' and 'cite_key')
  3006. static $userSpecificFields = array("marked", "copy", "selected", "user_keys", "user_notes",
  3007. "user_file", "user_groups", "cite_key");
  3008. // Display results in Details view when querying for one of the given fields:
  3009. // (see note below)
  3010. static $showFieldsInDetailsView = array("title", "serial", "cite_key");
  3011. // Check whether we're dealing with an unsupported field or if data are missing:
  3012. if (!in_array($field, $linkedFields) OR empty($fieldData) OR (in_array($field, $userSpecificFields) AND empty($userID)))
  3013. {
  3014. // Contents of unsupported fields won't get hotlinked but we'll have to HTML
  3015. // encode special chars and apply any field-specific search & replace actions:
  3016. if (!empty($fieldData))
  3017. return encodeField($field, $fieldData, $localSearchReplaceActionsArray, $encodingExceptionsArray);
  3018. else
  3019. return $fieldData;
  3020. }
  3021. else // supported field
  3022. {
  3023. $queryParametersArray = array();
  3024. // Add the 'userID' parameter for user-specific fields:
  3025. if (in_array($field, $userSpecificFields) AND !empty($userID))
  3026. $queryParametersArray["userID"] = $userID;
  3027. // Add query parameters if they're different from the defaults:
  3028. if (!empty($client))
  3029. $queryParametersArray["client"] = $client;
  3030. // NOTE: refbase usually tries to keep the user's current view. However, in
  3031. // case of the search links, I think it usually makes sense to present
  3032. // results in one of the two list-style views (i.e. List view or
  3033. // Citation view) -- and not in Details view, for example. Thus, we
  3034. // exclude Details view for regular fields. However, as an exception,
  3035. // fields listed in '$showFieldsInDetailsView' will be always displayed
  3036. // in Details view. This is done since the user most likely expects to
  3037. // see record details when he clicks on a record identifier (such as the
  3038. // 'serial' or 'cite_key' field) or the record's title.
  3039. if (in_array($field, $showFieldsInDetailsView))
  3040. $queryParametersArray["submit"] = "Display";
  3041. elseif (!empty($displayType) AND ($displayType != $_SESSION['userDefaultView']) AND (preg_match("/^(List|Cite)$/i", $displayType)))
  3042. $queryParametersArray["submit"] = $displayType;
  3043. if (!empty($viewType) AND ($viewType != "Web"))
  3044. $queryParametersArray["viewType"] = $viewType;
  3045. if ($showQuery == "1")
  3046. $queryParametersArray["showQuery"] = $showQuery;
  3047. if ($showLinks == "0")
  3048. $queryParametersArray["showLinks"] = $showLinks;
  3049. if ($wrapResults == "0")
  3050. $queryParametersArray["wrapResults"] = $wrapResults;
  3051. // We use absolute links for CLI clients, for include mechanisms, or when
  3052. // returning only a partial document structure:
  3053. if (preg_match("/^(cli|inc)/i", $client) OR ($wrapResults == "0"))
  3054. $baseURL = $databaseBaseURL;
  3055. else
  3056. $baseURL = "";
  3057. // Map MySQL field names to localized column names:
  3058. $fieldNamesArray = mapFieldNames();
  3059. $linkifiedFieldItems = array();
  3060. // Check whether we need to split field values for this field:
  3061. if (in_array($field, $multiItemFields))
  3062. $itemArray = preg_split($itemDelim, $fieldData);
  3063. else
  3064. $itemArray = array($fieldData);
  3065. // Linkify each field item:
  3066. foreach ($itemArray as $item)
  3067. {
  3068. $queryParametersArrayTemp = $queryParametersArray;
  3069. // Escape any meta characters:
  3070. $escapedItem = preg_quote($item, "");
  3071. // Adopt 'show.php' parameter name if it doesn't equal the field name:
  3072. if ($field == "serial")
  3073. $queryParametersArrayTemp["record"] = $escapedItem;
  3074. elseif ($field == "marked")
  3075. $queryParametersArrayTemp["ismarked"] = $escapedItem;
  3076. else
  3077. $queryParametersArrayTemp[$field] = $escapedItem;
  3078. // Generate item link:
  3079. $linkifiedFieldItems[] = "<a href=\"" . $baseURL . generateURL("show.php", "html", $queryParametersArrayTemp, true, $showRows, 0, $citeStyle, $citeOrder) . "\""
  3080. . " title=\"" . $loc["LinkTitle_SearchFieldItem_Prefix"] . $fieldNamesArray[$field] . $loc["LinkTitle_SearchFieldItem_Suffix"] . encodeHTML($item) . "\">"
  3081. . encodeField($field, $item, $localSearchReplaceActionsArray, $encodingExceptionsArray) // HTML encode special chars AND apply any field-specific search & replace actions
  3082. . "</a>";
  3083. }
  3084. return "<span class=\"itemlinks\">" . implode($joinDelim, $linkifiedFieldItems) . "</span>";
  3085. }
  3086. }
  3087. // --------------------------------------------------------------------
  3088. // MODIFY USER GROUPS
  3089. // add (remove) selected records to (from) the specified user group
  3090. // Note: this function serves two purposes (which must not be confused!):
  3091. // - 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*)
  3092. // - 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*)
  3093. function modifyUserGroups($queryTable, $displayType, $recordSerialsArray, $userID, $userGroup)
  3094. {
  3095. global $tableUserData, $tableUsers; // defined in 'db.inc.php'
  3096. connectToMySQLDatabase();
  3097. $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)
  3098. if ($queryTable == $tableUserData) // for the current user, get all entries within the 'user_data' table that refer to the selected records (listed in '$recordSerialsArray'):
  3099. $query = "SELECT record_id, user_groups FROM $tableUserData WHERE record_id RLIKE " . quote_smart("^(" . implode("|", $recordSerialsArray) . ")$") . " AND user_id = " . quote_smart($userID);
  3100. elseif ($queryTable == $tableUsers) // for the admin, get all entries within the 'users' table that refer to the selected records (listed in '$recordSerialsArray'):
  3101. $query = "SELECT user_id as record_id, user_groups FROM $tableUsers WHERE user_id RLIKE " . quote_smart("^(" . implode("|", $recordSerialsArray) . ")$");
  3102. // (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)
  3103. $result = queryMySQLDatabase($query); // RUN the query on the database through the connection
  3104. $foundSerialsArray = array(); // initialize array variable (which will hold the serial numbers of all found records)
  3105. $rowsFound = @ mysqli_num_rows($result);
  3106. if ($rowsFound > 0) // If there were rows found ...
  3107. {
  3108. while ($row = @ mysqli_fetch_array($result)) // for all rows found
  3109. {
  3110. $recordID = $row["record_id"]; // get the serial number of the current record
  3111. $foundSerialsArray[] = $recordID; // add this record's serial to the array of found serial numbers
  3112. $recordUserGroups = $row["user_groups"]; // extract the user groups that the current record belongs to
  3113. // ADD the specified user group to the 'user_groups' field:
  3114. if ($displayType == "Add" AND !preg_match("/(^|.*;) *$userGroupQuoted *(;.*|$)/", $recordUserGroups)) // if the specified group isn't listed already within the 'user_groups' field:
  3115. {
  3116. if (empty($recordUserGroups)) // and if the 'user_groups' field is completely empty
  3117. $recordUserGroups = $userGroup; // add the specified user group to the 'user_groups' field
  3118. else // if the 'user_groups' field does already contain some user content:
  3119. $recordUserGroups .= "; " . $userGroup; // append the specified user group to the 'user_groups' field
  3120. }
  3121. // REMOVE the specified user group from the 'user_groups' field:
  3122. elseif ($displayType == "Remove") // remove the specified group from the 'user_groups' field:
  3123. {
  3124. $recordUserGroups = preg_replace("/^ *$userGroupQuoted *(?=;|$)/", "", $recordUserGroups); // the specified group is listed at the very beginning of the 'user_groups' field
  3125. $recordUserGroups = preg_replace("/ *; *$userGroupQuoted *(?=;|$)/", "", $recordUserGroups); // the specified group occurs after some other group name within the 'user_groups' field
  3126. $recordUserGroups = preg_replace("/^ *; */i", "", $recordUserGroups); // remove any remaining group delimiters at the beginning of the 'user_groups' field
  3127. }
  3128. if ($queryTable == $tableUserData) // for the current record & user ID, update the matching entry within the 'user_data' table:
  3129. $queryUserData = "UPDATE $tableUserData SET user_groups = " . quote_smart($recordUserGroups) . " WHERE record_id = " . quote_smart($recordID) . " AND user_id = " . quote_smart($userID);
  3130. elseif ($queryTable == $tableUsers) // for the current user ID, update the matching entry within the 'users' table:
  3131. $queryUserData = "UPDATE $tableUsers SET user_groups = " . quote_smart($recordUserGroups) . " WHERE user_id = " . quote_smart($recordID);
  3132. $resultUserData = queryMySQLDatabase($queryUserData); // RUN the query on the database through the connection
  3133. }
  3134. }
  3135. if (($queryTable == $tableUserData) AND ($displayType == "Add"))
  3136. {
  3137. // 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:
  3138. $leftoverSerialsArray = array_diff($recordSerialsArray, $foundSerialsArray); // get all unique array elements of '$recordSerialsArray' which are not in '$foundSerialsArray'
  3139. 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)
  3140. {
  3141. if ($leftoverRecordID > 0) // function 'extractFormElementsQueryResults()' in 'search.php' assigns '$recordSerialsArray[]="0"' if '$recordSerialsArray' is empty
  3142. {
  3143. $foundSerialsArray[] = $leftoverRecordID; // add this record's serial to the array of found serial numbers
  3144. // for the current record & user ID, add a new entry (containing the specified group) to the 'user_data' table:
  3145. $queryUserData = "INSERT INTO $tableUserData SET "
  3146. . "user_groups = " . quote_smart($userGroup) . ", "
  3147. . "record_id = " . quote_smart($leftoverRecordID) . ", "
  3148. . "user_id = " . quote_smart($userID) . ", "
  3149. . "data_id = NULL"; // inserting 'NULL' into an auto_increment PRIMARY KEY attribute allocates the next available key value
  3150. $resultUserData = queryMySQLDatabase($queryUserData); // RUN the query on the database through the connection
  3151. }
  3152. }
  3153. }
  3154. // TODO!
  3155. // save an informative message:
  3156. // if (count($foundSerialsArray) == "1")
  3157. // $recordHeader = $loc["record"]; // use singular form if only one record was updated
  3158. // else
  3159. // $recordHeader = $loc["records"]; // use plural form if multiple records were updated
  3160. // $HeaderString = returnMsg("The groups of " . . " records were updated successfully!", "", "", "HeaderString");
  3161. getUserGroups($queryTable, $userID); // update the appropriate session variable
  3162. }
  3163. // --------------------------------------------------------------------
  3164. // Get all user groups specified by the current user (or admin)
  3165. // and (if some groups were found) save them as semicolon-delimited string to a session variable:
  3166. // Note: this function serves two purposes (which must not be confused!):
  3167. // - 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*)
  3168. // - 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*)
  3169. function getUserGroups($queryTable, $userID)
  3170. {
  3171. global $tableUserData, $tableUsers; // defined in 'db.inc.php'
  3172. connectToMySQLDatabase();
  3173. // CONSTRUCT SQL QUERY:
  3174. // Note: 'user_groups RLIKE ".+"' will cause the database to only return user data entries where the 'user_groups' field
  3175. // is neither NULL (=> 'user_groups IS NOT NULL') nor the empty string (=> 'user_groups NOT RLIKE "^$"')
  3176. if ($queryTable == $tableUserData)
  3177. // Find all unique 'user_groups' entries in the 'user_data' table belonging to the current user:
  3178. $query = "SELECT DISTINCT user_groups FROM $tableUserData WHERE user_id = " . quote_smart($userID) . " AND user_groups RLIKE \".+\"";
  3179. elseif ($queryTable == $tableUsers)
  3180. // Find all unique 'user_groups' entries in the 'users' table:
  3181. $query = "SELECT DISTINCT user_groups FROM $tableUsers WHERE user_groups RLIKE \".+\"";
  3182. $result = queryMySQLDatabase($query); // RUN the query on the database through the connection
  3183. $userGroupsArray = array(); // initialize array variable
  3184. $rowsFound = @ mysqli_num_rows($result);
  3185. if ($rowsFound > 0) // If there were rows found ...
  3186. {
  3187. while ($row = @ mysqli_fetch_array($result)) // for all rows found
  3188. {
  3189. // remove any meaningless delimiter(s) from the beginning or end of a field string:
  3190. $rowUserGroupsString = trimTextPattern($row["user_groups"], "( *; *)+", true, true);
  3191. // split the contents of the 'user_groups' field on the specified delimiter (which is interpreted as perl-style regular expression!):
  3192. $rowUserGroupsArray = preg_split("/ *; */", $rowUserGroupsString);
  3193. $userGroupsArray = array_merge($userGroupsArray, $rowUserGroupsArray); // append this row's group names to the array of found user groups
  3194. }
  3195. // remove duplicate group names from array:
  3196. $userGroupsArray = array_unique($userGroupsArray);
  3197. // sort in ascending order:
  3198. sort($userGroupsArray);
  3199. // join array of unique user groups with '; ' as separator:
  3200. $userGroupsString = implode('; ', $userGroupsArray);
  3201. // Write the resulting string of user groups into a session variable:
  3202. if ($queryTable == $tableUserData)
  3203. saveSessionVariable("userGroups", $userGroupsString);
  3204. elseif ($queryTable == $tableUsers)
  3205. saveSessionVariable("adminUserGroups", $userGroupsString);
  3206. }
  3207. else // no user groups found
  3208. { // delete any session variable (which is now outdated):
  3209. if ($queryTable == $tableUserData)
  3210. deleteSessionVariable("userGroups");
  3211. elseif ($queryTable == $tableUsers)
  3212. deleteSessionVariable("adminUserGroups");
  3213. }
  3214. }
  3215. // --------------------------------------------------------------------
  3216. // Get all user queries specified by the current user
  3217. // and (if some queries were found) save them as semicolon-delimited string to the session variable 'userQueries':
  3218. function getUserQueries($userID)
  3219. {
  3220. global $tableQueries; // defined in 'db.inc.php'
  3221. connectToMySQLDatabase();
  3222. // CONSTRUCT SQL QUERY:
  3223. // Find all unique query entries in the 'queries' table belonging to the current user:
  3224. // (query names should be unique anyhow, so the DISTINCT parameter wouldn't be really necessary)
  3225. $query = "SELECT DISTINCT(query_name),last_execution FROM $tableQueries WHERE user_id = " . quote_smart($userID) . " ORDER BY last_execution DESC";
  3226. // Note: we sort (in descending order) by the 'last_execution' field to get the last used query entries first;
  3227. // by that, the last used query will be always at the top of the popup menu within the 'Recall My Query' form
  3228. $result = queryMySQLDatabase($query); // RUN the query on the database through the connection
  3229. $userQueriesArray = array(); // initialize array variable
  3230. $rowsFound = @ mysqli_num_rows($result);
  3231. if ($rowsFound > 0) // If there were rows found ...
  3232. {
  3233. while ($row = @ mysqli_fetch_array($result)) // for all rows found
  3234. $userQueriesArray[] = $row["query_name"]; // append this row's query name to the array of found user queries
  3235. // join array of unique user queries with '; ' as separator:
  3236. $userQueriesString = implode('; ', $userQueriesArray);
  3237. // Write the resulting string of user queries into a session variable:
  3238. saveSessionVariable("userQueries", $userQueriesString);
  3239. }
  3240. else // no user queries found
  3241. deleteSessionVariable("userQueries"); // delete any 'userQueries' session variable (which is now outdated)
  3242. }
  3243. // --------------------------------------------------------------------
  3244. // Get all cite keys specified by the current user:
  3245. function getUserCiteKeys($userID)
  3246. {
  3247. global $tableRefs, $tableUserData; // defined in 'db.inc.php'
  3248. connectToMySQLDatabase();
  3249. // CONSTRUCT SQL QUERY:
  3250. // Find all cite keys in table 'user_data' belonging to the current user:
  3251. // (note that the SQL query is formulated such that only those records from table 'user_data' are returned
  3252. // which have a matching entry in table 'refs'; i.e. stray items from table 'user_data' are omitted)
  3253. $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";
  3254. $result = queryMySQLDatabase($query); // RUN the query on the database through the connection
  3255. $userCiteKeysArray = array(); // initialize array variable
  3256. $rowsFound = @ mysqli_num_rows($result);
  3257. if ($rowsFound > 0) // If there were rows found ...
  3258. {
  3259. while ($row = @ mysqli_fetch_array($result)) // for all rows found
  3260. {
  3261. // If this row's cite key already exists in the global array of found cite keys ('$citeKeysArray'),
  3262. // we'll uniquify it, otherwise we'll take it as is
  3263. $citeKey = ensureUniqueCiteKey($row["cite_key"]);
  3264. // We also append the original cite key to '$userCiteKeysArray' which holds all uniquified cite keys
  3265. // as array keys and the corresponding original cite key names (including duplicate items!) as array
  3266. // values
  3267. $userCiteKeysArray[$citeKey] = $row["cite_key"];
  3268. }
  3269. }
  3270. return $userCiteKeysArray;
  3271. }
  3272. // --------------------------------------------------------------------
  3273. // This function checks if the given cite key already exists in the global array of found cite keys ('$citeKeysArray').
  3274. // If the given cite key already exists, an incrementing number will be added to uniquify it (the number will be increased
  3275. // until the cite key is truly unique); after ensuring that a given cite key is unique, it's added to '$citeKeysArray' and
  3276. // returned:
  3277. // Note: the global '$citeKeysArray' does NOT contain all cite keys defined in the entire refbase database; instead it holds:
  3278. // - on import: for records that just have been imported, the list of cite keys for all imported records (generated
  3279. // according to the current user's prefs) -PLUS- the list of all of the user's existing cite keys
  3280. // - on export: for records that just have been exported, the list of cite keys (generated according to the current
  3281. // user's prefs) for all exported records
  3282. function ensureUniqueCiteKey($citeKey)
  3283. {
  3284. global $citeKeysArray; // '$citeKeysArray' is made globally available from within this function
  3285. if (!isset($citeKeysArray))
  3286. $citeKeysArray = array(); // initialize array variable
  3287. if (isset($citeKeysArray[$citeKey])) // if this cite key already exists
  3288. {
  3289. 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
  3290. $citeKey = preg_replace("/(?<=_)(\d+)$/e", "'\\1' + 1", $citeKey); // increment the old number of occurrence (that already exists in this cite key) by 1
  3291. else
  3292. $citeKey = $citeKey . "_2"; // append a number of occurrence to this cite key
  3293. $citeKey = ensureUniqueCiteKey($citeKey); // recurse, to check again whether the generated cite key already exists
  3294. }
  3295. else
  3296. {
  3297. $citeKeysArray[$citeKey] = $citeKey; // append the cite key to the array of known cite keys
  3298. }
  3299. return $citeKey;
  3300. }
  3301. // --------------------------------------------------------------------
  3302. // Get all available formats/styles/types:
  3303. function getAvailableFormatsStylesTypes($dataType, $formatType) // '$dataType' must be one of the following: 'format', 'style', 'type'; '$formatType' must be either '', 'export', 'import' or 'cite'
  3304. {
  3305. global $tableDepends, $tableFormats, $tableStyles, $tableTypes; // defined in 'db.inc.php'
  3306. connectToMySQLDatabase();
  3307. // CONSTRUCT SQL QUERY:
  3308. if ($dataType == "format")
  3309. $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";
  3310. elseif ($dataType == "style")
  3311. $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";
  3312. elseif ($dataType == "type")
  3313. $query = "SELECT type_name, type_id FROM $tableTypes WHERE type_enabled = 'true' ORDER BY order_by, type_name";
  3314. $result = queryMySQLDatabase($query); // RUN the query on the database through the connection
  3315. $availableFormatsStylesTypesArray = array(); // initialize array variable
  3316. $rowsFound = @ mysqli_num_rows($result);
  3317. if ($rowsFound > 0) // If there were rows found ...
  3318. while ($row = @ mysqli_fetch_array($result)) // for all rows found
  3319. $availableFormatsStylesTypesArray[$row[$dataType . "_id"]] = $row[$dataType . "_name"]; // append this row's format/style/type name to the array of found user formats/styles/types
  3320. return $availableFormatsStylesTypesArray;
  3321. }
  3322. // --------------------------------------------------------------------
  3323. // Get all formats/styles/types that are available and were enabled by the admin for the current user:
  3324. function getEnabledUserFormatsStylesTypes($userID, $dataType, $formatType, $returnIDsAsValues) // '$dataType' must be one of the following: 'format', 'style', 'type'; '$formatType' must be either '', 'export', 'import' or 'cite'
  3325. {
  3326. global $tableDepends, $tableFormats, $tableStyles, $tableTypes, $tableUserFormats, $tableUserStyles, $tableUserTypes; // defined in 'db.inc.php'
  3327. connectToMySQLDatabase();
  3328. // CONSTRUCT SQL QUERY:
  3329. if ($dataType == "format")
  3330. $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";
  3331. elseif ($dataType == "style")
  3332. $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";
  3333. elseif ($dataType == "type")
  3334. $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";
  3335. $result = queryMySQLDatabase($query); // RUN the query on the database through the connection
  3336. $enabledFormatsStylesTypesArray = array(); // initialize array variable
  3337. $rowsFound = @ mysqli_num_rows($result);
  3338. if ($rowsFound > 0) // If there were rows found ...
  3339. while ($row = @ mysqli_fetch_array($result)) // for all rows found
  3340. {
  3341. if ($returnIDsAsValues) // return format/style/type IDs as element values:
  3342. $enabledFormatsStylesTypesArray[] = $row[$dataType . "_id"]; // append this row's format/style/type ID to the array of found user formats/styles/types
  3343. else // return format/style/type names as element values and use the corresponding IDs as element keys:
  3344. $enabledFormatsStylesTypesArray[$row[$dataType . "_id"]] = $row[$dataType . "_name"]; // append this row's format/style/type name to the array of found user formats/styles/types
  3345. }
  3346. return $enabledFormatsStylesTypesArray;
  3347. }
  3348. // --------------------------------------------------------------------
  3349. // 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:
  3350. // 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:
  3351. function getVisibleUserFormatsStylesTypes($userID, $dataType, $formatType) // '$dataType' must be one of the following: 'format', 'style', 'type'; '$formatType' must be either '', 'export', 'import' or 'cite'
  3352. {
  3353. global $loginEmail;
  3354. global $adminLoginEmail; // ('$adminLoginEmail' is specified in 'ini.inc.php')
  3355. global $tableDepends, $tableFormats, $tableStyles, $tableTypes, $tableUserFormats, $tableUserStyles, $tableUserTypes; // defined in 'db.inc.php'
  3356. connectToMySQLDatabase();
  3357. // CONSTRUCT SQL QUERY:
  3358. if ($dataType == "format")
  3359. {
  3360. // Find all enabled+visible formats in table 'user_formats' belonging to the current user:
  3361. // Note: following conditions must be matched to have a format "enabled+visible" for a particular user:
  3362. // - 'formats' table: the 'format_enabled' field must contain 'true' for the given format
  3363. // (the 'formats' table gives the admin control over which formats are available to the database users)
  3364. // - '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'
  3365. // (the 'depends' table specifies whether there are any external tools required for a particular format and if these tools are available)
  3366. // - '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-
  3367. // the 'show_format' field must contain 'true' for the 'format_id' that matches the 'format_id' of the given format in table 'formats'
  3368. // (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)
  3369. $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";
  3370. }
  3371. elseif ($dataType == "style")
  3372. {
  3373. // Find all enabled+visible styles in table 'user_styles' belonging to the current user:
  3374. // (same conditions apply as for formats)
  3375. $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";
  3376. }
  3377. elseif ($dataType == "type")
  3378. {
  3379. // Find all enabled+visible types in table 'user_types' belonging to the current user:
  3380. // (opposed to formats & styles, we're not checking for any dependencies here)
  3381. $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";
  3382. }
  3383. $result = queryMySQLDatabase($query); // RUN the query on the database through the connection
  3384. $userFormatsStylesTypesArray = array(); // initialize array variable
  3385. // generate the name of the session variable:
  3386. if (!empty($formatType))
  3387. $sessionVariableName = "user_" . $formatType . "_" . $dataType . "s"; // yields 'user_export_formats' or 'user_cite_formats'
  3388. else
  3389. $sessionVariableName = "user_" . $dataType . "s"; // yields 'user_styles' or 'user_types'
  3390. $rowsFound = @ mysqli_num_rows($result);
  3391. if ($rowsFound > 0) // If there were rows found ...
  3392. {
  3393. while ($row = @ mysqli_fetch_array($result)) // for all rows found
  3394. $userFormatsStylesTypesArray[] = $row[$dataType . "_name"]; // append this row's format/style/type name to the array of found user formats/styles/types
  3395. // 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
  3396. if (($loginEmail != $adminLoginEmail) OR (($loginEmail == $adminLoginEmail) && ($userID == getUserID($loginEmail))))
  3397. {
  3398. // join array of unique user formats/styles/types with '; ' as separator:
  3399. $userFormatsStylesTypesString = implode('; ', $userFormatsStylesTypesArray);
  3400. // Write the resulting string of user formats/styles/types into a session variable:
  3401. saveSessionVariable($sessionVariableName, $userFormatsStylesTypesString);
  3402. }
  3403. }
  3404. else // no user formats/styles/types found
  3405. // 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
  3406. if (($loginEmail != $adminLoginEmail) OR (($loginEmail == $adminLoginEmail) && ($userID == getUserID($loginEmail))))
  3407. deleteSessionVariable($sessionVariableName); // delete any 'user_export_formats'/'user_cite_formats'/'user_styles'/'user_types' session variable (which is now outdated)
  3408. return $userFormatsStylesTypesArray;
  3409. }
  3410. // --------------------------------------------------------------------
  3411. // Get all formats/styles/types that are available (or enabled for the current user) and return them as properly formatted <option> tag elements.
  3412. // Note that this function will return two pretty different things, depending on who's logged in:
  3413. // - if the admin is logged in, it will return all *available* formats/styles/types as <option> tags
  3414. // (with those items being selected which were _enabled_ by the admin for the current user)
  3415. // - 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
  3416. // (with those items being selected which were chosen to be _visible_ by the current user)
  3417. function returnFormatsStylesTypesAsOptionTags($userID, $dataType, $formatType) // '$dataType' must be one of the following: 'format', 'style', 'type'; '$formatType' must be either '', 'export', 'import' or 'cite'
  3418. {
  3419. global $loginEmail;
  3420. global $adminLoginEmail; // ('$adminLoginEmail' is specified in 'ini.inc.php')
  3421. if ($loginEmail == $adminLoginEmail) // if the admin is logged in
  3422. $availableFormatsStylesTypesArray = getAvailableFormatsStylesTypes($dataType, $formatType); // get all available formats/styles/types
  3423. $enabledFormatsStylesTypesArray = getEnabledUserFormatsStylesTypes($userID, $dataType, $formatType, false); // get all formats/styles/types that were enabled by the admin for the current user
  3424. if ($loginEmail == $adminLoginEmail) // if the admin is logged in
  3425. {
  3426. $optionTags = buildSelectMenuOptions($availableFormatsStylesTypesArray, "/ *; */", "\t\t\t", true); // build properly formatted <option> tag elements from the items listed in '$availableFormatsStylesTypesArray'
  3427. $selectedFormatsStylesTypesArray = $enabledFormatsStylesTypesArray; // get all formats/styles/types that were enabled by the admin for the current user
  3428. }
  3429. else // if ($loginEmail != $adminLoginEmail) // if a normal user is logged in
  3430. {
  3431. $optionTags = buildSelectMenuOptions($enabledFormatsStylesTypesArray, "/ *; */", "\t\t\t", true); // build properly formatted <option> tag elements from the items listed in '$enabledFormatsStylesTypesArray'
  3432. $selectedFormatsStylesTypesArray = getVisibleUserFormatsStylesTypes($userID, $dataType, $formatType); // get all formats/styles/types that were chosen to be visible for the current user
  3433. }
  3434. 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)
  3435. $selectedFormatsStylesTypesArray[$itemKey] = preg_quote($itemValue);
  3436. $selectedFormatsStylesTypes = implode("|", $selectedFormatsStylesTypesArray); // merge array of formats/styles/types that shall be selected
  3437. $optionTags = preg_replace("/<option([^>]*)>($selectedFormatsStylesTypes)<\\/option>/", "<option\\1 selected>\\2</option>", $optionTags); // select all formats/styles/types that are listed within '$selectedFormatsStylesTypesArray'
  3438. return $optionTags;
  3439. }
  3440. // --------------------------------------------------------------------
  3441. // Fetch the name of the citation style file that's associated with the style given in '$citeStyle'
  3442. // 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!
  3443. // That said, this function assumes unique style names, i.e., there's no error checking for duplicates!
  3444. function getStyleFile($citeStyle)
  3445. {
  3446. global $tableStyles; // defined in 'db.inc.php'
  3447. connectToMySQLDatabase();
  3448. // CONSTRUCT SQL QUERY:
  3449. // get the 'style_spec' for the record entry in table 'styles' whose 'style_name' matches that in '$citeStyle':
  3450. $query = "SELECT style_spec FROM $tableStyles WHERE style_name = " . quote_smart($citeStyle);
  3451. $result = queryMySQLDatabase($query); // RUN the query on the database through the connection
  3452. $row = mysqli_fetch_array($result);
  3453. return($row["style_spec"]);
  3454. }
  3455. // --------------------------------------------------------------------
  3456. // Fetch the path/name of the format file that's associated with the format given in '$formatName'
  3457. function getFormatFile($formatName, $formatType) // '$formatType' must be either 'export', 'import' or 'cite'
  3458. {
  3459. global $tableFormats; // defined in 'db.inc.php'
  3460. connectToMySQLDatabase();
  3461. // CONSTRUCT SQL QUERY:
  3462. // get the 'format_spec' for the record entry in table 'formats' whose 'format_name' matches that in '$formatName':
  3463. $query = "SELECT format_spec FROM $tableFormats WHERE format_name = " . quote_smart($formatName) . " AND format_type = " . quote_smart($formatType);
  3464. $result = queryMySQLDatabase($query); // RUN the query on the database through the connection
  3465. $row = mysqli_fetch_array($result);
  3466. return($row["format_spec"]);
  3467. }
  3468. // --------------------------------------------------------------------
  3469. // Fetch the path of the external utility that's required for a particular import/export format
  3470. function getExternalUtilityPath($externalUtilityName)
  3471. {
  3472. global $tableDepends; // defined in 'db.inc.php'
  3473. connectToMySQLDatabase();
  3474. // CONSTRUCT SQL QUERY:
  3475. // get the path for the record entry in table 'depends' whose field 'depends_external' matches that in '$externalUtilityName':
  3476. $query = "SELECT depends_path FROM $tableDepends WHERE depends_external = " . quote_smart($externalUtilityName);
  3477. $result = queryMySQLDatabase($query); // RUN the query on the database through the connection
  3478. $row = mysqli_fetch_array($result);
  3479. return($row["depends_path"]);
  3480. }
  3481. // --------------------------------------------------------------------
  3482. // Get the user (or group) permissions for the current user
  3483. // and (optionally) save all allowed user actions as semicolon-delimited string to the session variable 'user_permissions':
  3484. function getPermissions($user_OR_groupID, $permissionType, $savePermissionsToSessionVariable) // '$permissionType' must be either 'user' or 'group'; '$savePermissionsToSessionVariable' must be either 'true' or 'false'
  3485. {
  3486. global $tableUserPermissions; // defined in 'db.inc.php'
  3487. // NOTE: the group permissions feature (table 'group_permissions') has not been implemented yet, i.e., currently, only '$permissionType=user' is recognized!
  3488. // global $tableGroupPermissions;
  3489. // if ($permissionType == "group")
  3490. // $tablePermissions = $tableGroupPermissions;
  3491. // else
  3492. $tablePermissions = $tableUserPermissions;
  3493. connectToMySQLDatabase();
  3494. // CONSTRUCT SQL QUERY:
  3495. // Fetch all permission settings from the 'user_permissions' (or 'group_permissions') table for the current user:
  3496. $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);
  3497. $result = queryMySQLDatabase($query); // RUN the query on the database through the connection
  3498. if (mysqli_num_rows($result) == 1) // interpret query result: Do we have exactly one row?
  3499. {
  3500. $userPermissionsArray = array(); // initialize array variables
  3501. $userPermissionsFieldNameArray = array();
  3502. $row = mysqli_fetch_array($result); // fetch the one row into the array '$row'
  3503. $fieldsFound = mysqli_num_fields($result); // count the number of fields
  3504. for ($i=0; $i<$fieldsFound; $i++)
  3505. {
  3506. // Fetch the current attribute name:
  3507. $fieldName = getMySQLFieldInfo($result, $i, "name");
  3508. $userPermissionsArray[$fieldName] = $row[$i]; // ... append this field's permission value using the field's permission name as key
  3509. if ($row[$i] == "yes") // if the current permission is set to 'yes'...
  3510. $userPermissionsFieldNameArray[] = $fieldName; // ... append this field's permission name (as value) to the array of allowed user actions
  3511. }
  3512. // join array of allowed user actions with '; ' as separator:
  3513. $allowedUserActionsString = implode('; ', $userPermissionsFieldNameArray);
  3514. if ($savePermissionsToSessionVariable)
  3515. // Write the resulting string of allowed user actions into a session variable:
  3516. saveSessionVariable("user_permissions", $allowedUserActionsString);
  3517. return $userPermissionsArray;
  3518. }
  3519. else
  3520. {
  3521. if ($savePermissionsToSessionVariable)
  3522. // 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.,
  3523. // 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)
  3524. deleteSessionVariable("user_permissions"); // therefore, we delete any existing 'user_permissions' session variable (which is now outdated)
  3525. return array();
  3526. }
  3527. }
  3528. // --------------------------------------------------------------------
  3529. // Returns language information:
  3530. // if empty($userID): get all languages that were setup and enabled by the admin
  3531. // if !empty($userID): get the preferred language for the user with the specified userID
  3532. function getLanguages($userID)
  3533. {
  3534. global $tableLanguages, $tableUsers; // defined in 'db.inc.php'
  3535. connectToMySQLDatabase();
  3536. // CONSTRUCT SQL QUERY:
  3537. if (empty($userID))
  3538. // Find all unique language entries in the 'languages' table that are enabled:
  3539. // (language names should be unique anyhow, so the DISTINCT parameter wouldn't be really necessary)
  3540. $query = "SELECT DISTINCT language_name FROM $tableLanguages WHERE language_enabled = 'true' ORDER BY $tableLanguages.language_name";
  3541. else
  3542. // Get the preferred language for the user with the user ID given in '$userID':
  3543. $query = "SELECT language AS language_name FROM $tableUsers WHERE user_id = " . quote_smart($userID);
  3544. $result = queryMySQLDatabase($query); // RUN the query on the database through the connection
  3545. $languagesArray = array(); // initialize array variable
  3546. $rowsFound = @ mysqli_num_rows($result);
  3547. if ($rowsFound > 0) // If there were rows found ...
  3548. {
  3549. while ($row = @ mysqli_fetch_array($result)) // for all rows found
  3550. $languagesArray[] = $row["language_name"]; // append this row's language name to the array of found languages
  3551. }
  3552. return $languagesArray;
  3553. }
  3554. // --------------------------------------------------------------------
  3555. // Return the current user's preferred interface language:
  3556. function getUserLanguage()
  3557. {
  3558. global $loginUserID; // saved as session variable on login
  3559. global $defaultLanguage; // defined in 'ini.inc.php'
  3560. if (isset($_SESSION['loginEmail'])) // if a user is logged in
  3561. {
  3562. // get the preferred language for the current user:
  3563. $userLanguagesArray = getLanguages($loginUserID);
  3564. $userLanguage = $userLanguagesArray[0];
  3565. }
  3566. else // NO user logged in
  3567. $userLanguage = $defaultLanguage; // use the default language
  3568. return $userLanguage;
  3569. }
  3570. // --------------------------------------------------------------------
  3571. // Get all user options for the current user:
  3572. function getUserOptions($userID)
  3573. {
  3574. global $tableUserOptions; // defined in 'db.inc.php'
  3575. connectToMySQLDatabase();
  3576. if (empty($userID))
  3577. $userID = 0;
  3578. // CONSTRUCT SQL QUERY:
  3579. // Fetch all options from table 'user_options' for the user with the user ID given in '$userID':
  3580. $query = "SELECT * FROM $tableUserOptions WHERE user_id = " . quote_smart($userID);
  3581. $result = queryMySQLDatabase($query); // RUN the query on the database through the connection
  3582. $userOptionsArray = array(); // initialize array variable
  3583. $rowsFound = @ mysqli_num_rows($result);
  3584. if ($rowsFound == 1) // Interpret query result: Do we have exactly one row?
  3585. $userOptionsArray = @ mysqli_fetch_array($result); // fetch the one row into the array '$userOptionsArray'
  3586. return $userOptionsArray;
  3587. }
  3588. // --------------------------------------------------------------------
  3589. // Get the list of "main fields" preferred by the current user:
  3590. // and save the list of fields as comma-delimited string to the session variable 'userMainFields'
  3591. // TODO: Make *one* generic function that can replace functions 'getMainFields()',
  3592. // 'getDefaultNumberOfRecords()' and 'getPrefAutoCompletions()'
  3593. function getMainFields($userID)
  3594. {
  3595. global $loginEmail;
  3596. global $adminLoginEmail; // these variables are defined in 'ini.inc.php'
  3597. global $defaultMainFields;
  3598. $userOptionsArray = array(); // initialize array variable
  3599. // Get all user options for the current user:
  3600. // note that if the user isn't logged in (userID=0), the list of "main fields" is taken from variable
  3601. // '$defaultMainFields' in 'ini.inc.php' and not from option 'main_fields' in table 'user_options
  3602. if ($userID != 0)
  3603. $userOptionsArray = getUserOptions($userID);
  3604. // Extract the list of "main fields":
  3605. if (!empty($userOptionsArray) AND !empty($userOptionsArray['main_fields']))
  3606. $userMainFieldsString = $userOptionsArray['main_fields']; // honour the logged in user's preferred list of "main fields" (if not empty or NULL)
  3607. else
  3608. $userMainFieldsString = $defaultMainFields; // by default, we take the list of "main fields" from the global variable '$defaultMainFields'
  3609. // 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
  3610. if (($loginEmail != $adminLoginEmail) OR (($loginEmail == $adminLoginEmail) && ($userID == getUserID($loginEmail))))
  3611. // Write the list of fields into a session variable:
  3612. saveSessionVariable("userMainFields", $userMainFieldsString);
  3613. $userMainFieldsArray = preg_split("/ *, */", $userMainFieldsString); // split the string of fields into its individual fields
  3614. return $userMainFieldsArray;
  3615. }
  3616. // --------------------------------------------------------------------
  3617. // 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)'):
  3618. // this information is used when adding/updating/deleting records in the database
  3619. function getCurrentDateTimeUser()
  3620. {
  3621. global $loginEmail;
  3622. global $loginFirstName;
  3623. global $loginLastName;
  3624. $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')
  3625. $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')
  3626. $currentUser = $loginFirstName . " " . $loginLastName . " (" . $loginEmail . ")"; // we use session variables to construct the user name, e.g. 'Matthias Steffens (refbase@extracts.de)'
  3627. return array($currentDate, $currentTime, $currentUser);
  3628. }
  3629. // --------------------------------------------------------------------
  3630. // Build a correct call number prefix for the currently logged-in user (e.g. 'IP� @ msteffens'):
  3631. function getCallNumberPrefix()
  3632. {
  3633. global $loginEmail;
  3634. global $abbrevInstitution;
  3635. // we use session variables to construct a correct call number prefix:
  3636. $loginEmailArray = preg_split("/@/", $loginEmail); // split the login email address at '@'
  3637. $loginEmailUserName = $loginEmailArray[0]; // extract the user name (which is the first element of the array '$loginEmailArray')
  3638. $callNumberPrefix = $abbrevInstitution . " @ " . $loginEmailUserName;
  3639. return $callNumberPrefix;
  3640. }
  3641. // --------------------------------------------------------------------
  3642. // Get the default view for the current user:
  3643. function getDefaultView($userID)
  3644. {
  3645. global $defaultView; // defined in 'ini.inc.php'
  3646. $userOptionsArray = array(); // initialize array variables
  3647. $viewsArray = array("List" => "allow_list_view",
  3648. "Cite" => "allow_cite",
  3649. "Display" => "allow_details_view",
  3650. "Browse" => "allow_browse_view");
  3651. $userDefaultView = $defaultView; // by default, we take the default view from the global variable '$defaultView'
  3652. // Note that if the user isn't logged in (userID=0), the default view is taken from variable '$defaultView'
  3653. // in 'ini.inc.php' and is not overridden by any of the '*_view' permissions ('allow_list_view', 'allow_details_view',
  3654. // 'allow_browse_view', 'allow_cite') in table 'user_permissions'
  3655. // Adopt the user's default view if he/she is NOT allowed to use the global default (given in '$defaultView'):
  3656. 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'
  3657. {
  3658. foreach ($viewsArray as $viewType => $viewPermission) // use the next allowed view as default view
  3659. {
  3660. if (preg_match("/" . $viewPermission . "/", $_SESSION['user_permissions']))
  3661. {
  3662. $userDefaultView = $viewType;
  3663. break;
  3664. }
  3665. }
  3666. }
  3667. // Write the name of the default view into a session variable:
  3668. saveSessionVariable("userDefaultView", $userDefaultView);
  3669. return $userDefaultView;
  3670. }
  3671. // --------------------------------------------------------------------
  3672. // Returns the total number of records in the database:
  3673. function getTotalNumberOfRecords()
  3674. {
  3675. global $tableRefs; // defined in 'db.inc.php'
  3676. connectToMySQLDatabase();
  3677. // CONSTRUCT SQL QUERY:
  3678. $query = "SELECT COUNT(serial) FROM $tableRefs"; // query the total number of records
  3679. $result = queryMySQLDatabase($query); // RUN the query on the database through the connection
  3680. $row = mysqli_fetch_row($result); // fetch the current row into the array $row (it'll be always *one* row, but anyhow)
  3681. $numberOfRecords = $row[0]; // extract the contents of the first (and only) row
  3682. return $numberOfRecords;
  3683. }
  3684. // --------------------------------------------------------------------
  3685. // Get the default number of records per page preferred by the current user:
  3686. // TODO: Make *one* generic function that can replace functions 'getMainFields()',
  3687. // 'getDefaultNumberOfRecords()' and 'getPrefAutoCompletions()'
  3688. function getDefaultNumberOfRecords($userID)
  3689. {
  3690. global $loginEmail;
  3691. global $adminLoginEmail; // these variables are defined in 'ini.inc.php'
  3692. global $defaultNumberOfRecords;
  3693. $userOptionsArray = array(); // initialize array variable
  3694. // Get all user options for the current user:
  3695. // note that if the user isn't logged in (userID=0), we don't load the default number of records from option
  3696. // 'records_per_page' in table 'user_options' (where 'user_id = 0'). Instead, we'll return as many records as
  3697. // defined in variable '$defaultNumberOfRecords' in 'ini.inc.php'.
  3698. if ($userID != 0)
  3699. $userOptionsArray = getUserOptions($userID);
  3700. // Extract the number of records that's to be returned by default:
  3701. if (!empty($userOptionsArray) AND !empty($userOptionsArray['records_per_page']))
  3702. $showRows = $userOptionsArray['records_per_page']; // honour the logged in user's preferred number of records (if not empty or NULL)
  3703. else
  3704. $showRows = $defaultNumberOfRecords; // by default, we take the number of records from the global variable '$defaultNumberOfRecords'
  3705. // 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
  3706. if (($loginEmail != $adminLoginEmail) OR (($loginEmail == $adminLoginEmail) && ($userID == getUserID($loginEmail))))
  3707. // Write results into a session variable:
  3708. saveSessionVariable("userRecordsPerPage", $showRows);
  3709. return $showRows;
  3710. }
  3711. // --------------------------------------------------------------------
  3712. // Get the user's preference for displaying auto-completions:
  3713. // TODO: Make *one* generic function that can replace functions 'getMainFields()',
  3714. // 'getDefaultNumberOfRecords()' and 'getPrefAutoCompletions()'
  3715. function getPrefAutoCompletions($userID)
  3716. {
  3717. global $loginEmail;
  3718. global $adminLoginEmail; // these variables are defined in 'ini.inc.php'
  3719. global $autoCompleteUserInput;
  3720. $userOptionsArray = array(); // initialize array variable
  3721. // Get all user options for the current user:
  3722. // note that if the user isn't logged in (userID=0), we don't load the pref setting from option
  3723. // 'show_auto_completions' in table 'user_options' (where 'user_id = 0'). Instead, we'll take
  3724. // the setting from variable '$autoCompleteUserInput' in 'ini.inc.php'.
  3725. if ($userID != 0)
  3726. $userOptionsArray = getUserOptions($userID);
  3727. // Extract the setting which defines whether auto-completions shall be displayed for text entered by the user:
  3728. if (!empty($userOptionsArray) AND !empty($userOptionsArray['show_auto_completions']))
  3729. $showAutoCompletions = $userOptionsArray['show_auto_completions']; // honour the logged in user's preference for displaying auto-completions (if not empty or NULL)
  3730. else
  3731. $showAutoCompletions = $autoCompleteUserInput; // by default, we take the pref setting from the global variable '$autoCompleteUserInput'
  3732. // 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
  3733. if (($loginEmail != $adminLoginEmail) OR (($loginEmail == $adminLoginEmail) && ($userID == getUserID($loginEmail))))
  3734. // Write results into a session variable:
  3735. saveSessionVariable("userAutoCompletions", $showAutoCompletions);
  3736. return $showAutoCompletions;
  3737. }
  3738. // --------------------------------------------------------------------
  3739. // Returns the date/time information (in format 'YYYY-MM-DD hh-mm-ss') when the database was last modified:
  3740. function getLastModifiedDateTime()
  3741. {
  3742. global $tableRefs; // defined in 'db.inc.php'
  3743. connectToMySQLDatabase();
  3744. // CONSTRUCT SQL QUERY:
  3745. $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
  3746. $result = queryMySQLDatabase($query); // RUN the query on the database through the connection
  3747. $row = mysqli_fetch_row($result); // fetch the current row into the array $row (it'll be always *one* row, but anyhow)
  3748. $lastModifiedDateTime = $row[0] . " " . $row[1];
  3749. return $lastModifiedDateTime;
  3750. }
  3751. // --------------------------------------------------------------------
  3752. // Update the specified user permissions for the selected user(s):
  3753. 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'
  3754. {
  3755. global $tableUserPermissions; // defined in 'db.inc.php'
  3756. connectToMySQLDatabase();
  3757. $permissionQueryArray = array();
  3758. // CONSTRUCT SQL QUERY:
  3759. // prepare the 'SET' part of the SQL query string:
  3760. foreach($userPermissionsArray as $permissionKey => $permissionValue)
  3761. $permissionQueryArray[] = $permissionKey . " = " . quote_smart($permissionValue);
  3762. if (!empty($userIDArray) AND !empty($permissionQueryArray))
  3763. {
  3764. // Update all specified permission settings in the 'user_permissions' table for the selected user(s):
  3765. $query = "UPDATE $tableUserPermissions SET " . implode(", ", $permissionQueryArray) . " WHERE user_id RLIKE " . quote_smart("^(" . implode("|", $userIDArray) . ")$");
  3766. $result = queryMySQLDatabase($query); // RUN the query on the database through the connection
  3767. return true;
  3768. }
  3769. else
  3770. return false;
  3771. }
  3772. // --------------------------------------------------------------------
  3773. // Generate or extract the cite key for the given record:
  3774. function generateCiteKey($formVars)
  3775. {
  3776. global $defaultCiteKeyFormat; // defined in 'ini.inc.php'
  3777. global $handleNonASCIICharsInCiteKeysDefault;
  3778. global $userOptionsArray; // '$userOptionsArray' is made globally available in file 'import_modify.php' as well as by functions 'generateExport()' and 'generateCitations()' in 'search.php'
  3779. // by default, we use any record-specific cite key that was entered manually by the user:
  3780. if (isset($formVars['citeKeyName']))
  3781. $citeKey = $formVars['citeKeyName'];
  3782. else
  3783. $citeKey = "";
  3784. // check if the user's options for auto-generation of cite keys command us to replace the manually entered cite key:
  3785. if (!empty($userOptionsArray))
  3786. {
  3787. if ($userOptionsArray['export_cite_keys'] == "yes") // if this user wants to include cite keys on import/export
  3788. {
  3789. if ($userOptionsArray['autogenerate_cite_keys'] == "yes") // if cite keys shall be auto-generated on import/export
  3790. {
  3791. 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
  3792. {
  3793. if ($userOptionsArray['use_custom_cite_key_format'] == "yes") // if the user wants to use a custom cite key format
  3794. $citeKeyFormat = $userOptionsArray['cite_key_format'];
  3795. else // use the default cite key format that was specified by the admin in 'ini.inc.php'
  3796. $citeKeyFormat = $defaultCiteKeyFormat;
  3797. // auto-generate a cite key according to the given naming scheme:
  3798. $citeKey = parsePlaceholderString($formVars, $citeKeyFormat, "<:authors:><:year:>");
  3799. }
  3800. }
  3801. }
  3802. else
  3803. $citeKey = ""; // by omitting a cite key Bibutils will take care of generation of cite keys for its export formats (BibTeX, Endnote, RIS)
  3804. }
  3805. // check how to handle non-ASCII characters:
  3806. if (!empty($userOptionsArray) AND !empty($userOptionsArray['nonascii_chars_in_cite_keys'])) // use the user's own setting
  3807. $handleNonASCIIChars = $userOptionsArray['nonascii_chars_in_cite_keys'];
  3808. else
  3809. $handleNonASCIIChars = $handleNonASCIICharsInCiteKeysDefault; // use the default setting that was specified by the admin in 'ini.inc.php'
  3810. // in addition to the handling of non-ASCII chars (given in '$handleNonASCIIChars') we'll
  3811. // strip additional characters from the generated cite keys: for cite keys, we only allow
  3812. // letters, digits, and the following characters: !$&*+-./:;<>?[]^_`|
  3813. // see e.g. the discussion of cite keys at: <http://search.cpan.org/~gward/btparse-0.34/doc/bt_language.pod>
  3814. if (!empty($citeKey))
  3815. $citeKey = handleNonASCIIAndUnwantedCharacters($citeKey, "[:alnum:]" . preg_quote("!$&*+-./:;<>?[]^_`|", "/"), $handleNonASCIIChars);
  3816. // ensure that each cite key is unique:
  3817. if (!empty($citeKey) AND !empty($userOptionsArray) AND ($userOptionsArray['export_cite_keys'] == "yes") AND ($userOptionsArray['uniquify_duplicate_cite_keys'] == "yes"))
  3818. // if the generated cite key already exists in the global array of found cite keys
  3819. // ('$citeKeysArray'), we'll uniquify it, otherwise we'll keep it as is:
  3820. $citeKey = ensureUniqueCiteKey($citeKey);
  3821. return $citeKey;
  3822. }
  3823. // --------------------------------------------------------------------
  3824. // Handle non-ASCII and unwanted characters:
  3825. // this function controls the handling of any non-ASCII chars and
  3826. // unwanted characters in file/directory names and cite keys
  3827. function handleNonASCIIAndUnwantedCharacters($fileDirCitekeyName, $allowedFileDirCitekeyNameCharacters, $handleNonASCIIChars)
  3828. {
  3829. // we treat non-ASCII characters in file/directory names and cite keys depending on the setting of variable '$handleNonASCIIChars':
  3830. if ($handleNonASCIIChars == "strip")
  3831. $fileDirCitekeyName = convertToCharacterEncoding("ASCII", "IGNORE", $fileDirCitekeyName); // remove any non-ASCII characters
  3832. elseif ($handleNonASCIIChars != "keep")
  3833. // 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;
  3834. // otherwise if '$handleNonASCIIChars = "transliterate"' (or when '$handleNonASCIIChars' contains an unrecognized/empty string)
  3835. // we'll transliterate most of the non-ASCII characters and strip all other non-ASCII chars that can't be converted into ASCII equivalents:
  3836. $fileDirCitekeyName = convertToCharacterEncoding("ASCII", "TRANSLIT", $fileDirCitekeyName);
  3837. // in addition, we remove all characters from the generated file/directory name or cite key which are not listed in variable '$allowedFileDirCitekeyNameCharacters':
  3838. if (!empty($allowedFileDirCitekeyNameCharacters))
  3839. $fileDirCitekeyName = preg_replace("/[^" . $allowedFileDirCitekeyNameCharacters . "]+/", "", $fileDirCitekeyName);
  3840. return $fileDirCitekeyName;
  3841. }
  3842. // --------------------------------------------------------------------
  3843. // this is a stupid hack that maps the names of the '$row' array keys to those used
  3844. // by the '$formVars' array (which is required by function 'generateCiteKey()')
  3845. // (eventually, the '$formVars' array should use the MySQL field names as names for its array keys)
  3846. function buildFormVarsArray($row)
  3847. {
  3848. $formVars = array(); // initialize array variable
  3849. if(isset($row['author']))
  3850. $formVars['authorName'] = $row['author'];
  3851. if(isset($row['title']))
  3852. $formVars['titleName'] = $row['title'];
  3853. if(isset($row['type']))
  3854. $formVars['typeName'] = $row['type'];
  3855. if(isset($row['year']))
  3856. $formVars['yearNo'] = $row['year'];
  3857. if(isset($row['publication']))
  3858. $formVars['publicationName'] = $row['publication'];
  3859. if(isset($row['abbrev_journal']))
  3860. $formVars['abbrevJournalName'] = $row['abbrev_journal'];
  3861. if(isset($row['volume']))
  3862. $formVars['volumeNo'] = $row['volume'];
  3863. if(isset($row['issue']))
  3864. $formVars['issueNo'] = $row['issue'];
  3865. if(isset($row['pages']))
  3866. $formVars['pagesNo'] = $row['pages'];
  3867. if(isset($row['corporate_author']))
  3868. $formVars['corporateAuthorName'] = $row['corporate_author'];
  3869. if(isset($row['thesis']))
  3870. $formVars['thesisName'] = $row['thesis'];
  3871. if(isset($row['address']))
  3872. $formVars['addressName'] = $row['address'];
  3873. if(isset($row['keywords']))
  3874. $formVars['keywordsName'] = $row['keywords'];
  3875. if(isset($row['abstract']))
  3876. $formVars['abstractName'] = $row['abstract'];
  3877. if(isset($row['publisher']))
  3878. $formVars['publisherName'] = $row['publisher'];
  3879. if(isset($row['place']))
  3880. $formVars['placeName'] = $row['place'];
  3881. if(isset($row['editor']))
  3882. $formVars['editorName'] = $row['editor'];
  3883. if(isset($row['language']))
  3884. $formVars['languageName'] = $row['language'];
  3885. if(isset($row['summary_language']))
  3886. $formVars['summaryLanguageName'] = $row['summary_language'];
  3887. if(isset($row['orig_title']))
  3888. $formVars['origTitleName'] = $row['orig_title'];
  3889. if(isset($row['series_editor']))
  3890. $formVars['seriesEditorName'] = $row['series_editor'];
  3891. if(isset($row['series_title']))
  3892. $formVars['seriesTitleName'] = $row['series_title'];
  3893. if(isset($row['abbrev_series_title']))
  3894. $formVars['abbrevSeriesTitleName'] = $row['abbrev_series_title'];
  3895. if(isset($row['series_volume']))
  3896. $formVars['seriesVolumeNo'] = $row['series_volume'];
  3897. if(isset($row['series_issue']))
  3898. $formVars['seriesIssueNo'] = $row['series_issue'];
  3899. if(isset($row['edition']))
  3900. $formVars['editionNo'] = $row['edition'];
  3901. if(isset($row['issn']))
  3902. $formVars['issnName'] = $row['issn'];
  3903. if(isset($row['isbn']))
  3904. $formVars['isbnName'] = $row['isbn'];
  3905. if(isset($row['medium']))
  3906. $formVars['mediumName'] = $row['medium'];
  3907. if(isset($row['area']))
  3908. $formVars['areaName'] = $row['area'];
  3909. if(isset($row['expedition']))
  3910. $formVars['expeditionName'] = $row['expedition'];
  3911. if(isset($row['conference']))
  3912. $formVars['conferenceName'] = $row['conference'];
  3913. if(isset($row['notes']))
  3914. $formVars['notesName'] = $row['notes'];
  3915. if(isset($row['approved']))
  3916. $formVars['approvedRadio'] = $row['approved'];
  3917. if(isset($row['location']))
  3918. $formVars['locationName'] = $row['location'];
  3919. if(isset($row['call_number']))
  3920. $formVars['callNumberName'] = $row['call_number'];
  3921. if(isset($row['serial']))
  3922. $formVars['serialNo'] = $row['serial'];
  3923. if(isset($row['online_publication']))
  3924. $formVars['onlinePublicationCheckBox'] = $row['online_publication'];
  3925. if(isset($row['online_citation']))
  3926. $formVars['onlineCitationName'] = $row['online_citation'];
  3927. if(isset($row['marked']))
  3928. $formVars['markedRadio'] = $row['marked'];
  3929. if(isset($row['copy']))
  3930. $formVars['copyName'] = $row['copy'];
  3931. if(isset($row['selected']))
  3932. $formVars['selectedRadio'] = $row['selected'];
  3933. if(isset($row['user_keys']))
  3934. $formVars['userKeysName'] = $row['user_keys'];
  3935. if(isset($row['user_notes']))
  3936. $formVars['userNotesName'] = $row['user_notes'];
  3937. if(isset($row['user_file']))
  3938. $formVars['userFileName'] = $row['user_file'];
  3939. if(isset($row['user_groups']))
  3940. $formVars['userGroupsName'] = $row['user_groups'];
  3941. if(isset($row['cite_key']))
  3942. $formVars['citeKeyName'] = $row['cite_key'];
  3943. if(isset($row['related']))
  3944. $formVars['relatedName'] = $row['related'];
  3945. if(isset($row['orig_record']))
  3946. $formVars['origRecord'] = $row['orig_record'];
  3947. if(isset($row['file']))
  3948. $formVars['fileName'] = $row['file'];
  3949. if(isset($row['url']))
  3950. $formVars['urlName'] = $row['url'];
  3951. if(isset($row['doi']))
  3952. $formVars['doiName'] = $row['doi'];
  3953. return $formVars;
  3954. }
  3955. // --------------------------------------------------------------------
  3956. // 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').
  3957. // 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)
  3958. function buildSelectMenuOptions($sourceData, $splitDelim, $prefix, $useArrayKeysAsValues)
  3959. {
  3960. if (is_string($sourceData)) // split the string on the specified delimiter (which is interpreted as regular expression!):
  3961. $sourceData = preg_split($splitDelim, $sourceData);
  3962. if ($useArrayKeysAsValues)
  3963. {
  3964. $optionTags = ""; // initialize variable
  3965. // copy each item as option tag element to the end of the '$optionTags' variable:
  3966. foreach ($sourceData as $itemID => $item)
  3967. {
  3968. if (!empty($item))
  3969. $optionTags .= "\n$prefix<option value=\"$itemID\">$item</option>";
  3970. else // empty items will also get an empty value:
  3971. $optionTags .= "\n$prefix<option value=\"\"></option>";
  3972. }
  3973. }
  3974. else
  3975. $optionTags = "\n$prefix<option>" . implode("</option>\n$prefix<option>", $sourceData) . "</option>";
  3976. return $optionTags;
  3977. }
  3978. // --------------------------------------------------------------------
  3979. // Produce a <select> list with unique items from the specified field
  3980. // Parameters:
  3981. // 1: Database connection
  3982. // 2. Table that contains values
  3983. // 3. The field name of the table's primary key
  3984. // 4. Table name of the user data table
  3985. // 5. The field name within the user data table that corresponds to the field in 3.
  3986. // 6. The field name of the user ID field within the user data table
  3987. // 7. The user ID of the currently logged in user (which must be provided as a session variable)
  3988. // 8. Attribute that contains values
  3989. // 9. <SELECT> element name
  3990. // 10. An additional non-database value (display string)
  3991. // 11. String that gets submitted instead of the display string given in 10.
  3992. // 12. Optional <OPTION SELECTED>
  3993. // 13. Restrict query to field... (keep empty if no restriction wanted)
  3994. // 14. ...where field contents are...
  3995. // 15. Split field contents into substrings? (yes = true, no = false)
  3996. // 16. POSIX-PATTERN to split field contents into substrings (in order to obtain actual values)
  3997. // 17. The type of the output format that shall be returned ("ARRAY", "HTML SELECT", "HTML UL" or "JSON")
  3998. // 18. The POSIX-PATTERN that matches those substrings from the field's contents that shall be included as search suggestions
  3999. // 19. Boolean that specifies whether search suggestions shall be wrapped into an enclosing HTML (or JSON) structure (yes if 'true')
  4000. function selectDistinct($connection, // 1.
  4001. $refsTableName, // 2.
  4002. $refsTablePrimaryKey, // 3.
  4003. $userDataTableName, // 4.
  4004. $userDataTablePrimaryKey, // 5.
  4005. $userDataTableUserID, // 6.
  4006. $userDataTableUserIDvalue, // 7.
  4007. $columnName, // 8.
  4008. $pulldownName, // 9.
  4009. $additionalOptionDisplay, // 10.
  4010. $additionalOption, // 11.
  4011. $defaultValue, // 12.
  4012. $RestrictToField, // 13.
  4013. $RestrictToFieldContents, // 14.
  4014. $SplitValues, // 15.
  4015. $SplitPattern, // 16.
  4016. $outputFormat = "HTML SELECT", // 17.
  4017. $searchSuggestionsPattern = "", // 18.
  4018. $wrapSearchSuggestions = true) // 19.
  4019. {
  4020. $defaultWithinResultSet = FALSE;
  4021. // Query to find distinct values of '$columnName' in '$refsTableName':
  4022. if (isset($_SESSION['loginEmail'])) // if a user is logged in
  4023. {
  4024. if ($RestrictToField == "")
  4025. $distinctQuery = "SELECT DISTINCT $columnName FROM $refsTableName LEFT JOIN $userDataTableName ON $refsTablePrimaryKey = $userDataTablePrimaryKey AND $userDataTableUserID = $userDataTableUserIDvalue ORDER BY $columnName";
  4026. else
  4027. $distinctQuery = "SELECT DISTINCT $columnName FROM $refsTableName LEFT JOIN $userDataTableName ON $refsTablePrimaryKey = $userDataTablePrimaryKey AND $userDataTableUserID = $userDataTableUserIDvalue WHERE $RestrictToField RLIKE $RestrictToFieldContents ORDER BY $columnName";
  4028. }
  4029. else // if NO user is logged in
  4030. {
  4031. if ($RestrictToField == "")
  4032. $distinctQuery = "SELECT DISTINCT $columnName FROM $refsTableName ORDER BY $columnName";
  4033. else
  4034. $distinctQuery = "SELECT DISTINCT $columnName FROM $refsTableName WHERE $RestrictToField RLIKE $RestrictToFieldContents ORDER BY $columnName";
  4035. }
  4036. // Run the distinctQuery on the database through the connection:
  4037. $resultId = queryMySQLDatabase($distinctQuery);
  4038. // Retrieve all distinct values:
  4039. $i = 0;
  4040. $resultBuffer = array();
  4041. while ($row = @ mysqli_fetch_array($resultId))
  4042. {
  4043. if ($SplitValues) // if desired, split field contents into substrings
  4044. {
  4045. // split field data on the pattern specified in '$SplitPattern':
  4046. $splittedFieldData = preg_split("#" . $SplitPattern . "#", $row[$columnName]);
  4047. // ... copy all array elements to end of '$resultBuffer':
  4048. foreach($splittedFieldData as $element)
  4049. {
  4050. $element = trim($element);
  4051. // NOTE: in case of OpenSearch search suggestions, we only include those substrings
  4052. // that match the regular expression given in '$searchSuggestionsPattern'
  4053. if (empty($searchSuggestionsPattern) OR (!empty($searchSuggestionsPattern) AND !empty($element) AND preg_match("/" . $searchSuggestionsPattern . "/i", $element)))
  4054. $resultBuffer[$i++] = $element;
  4055. }
  4056. }
  4057. else // copy field data (as is) to end of '$resultBuffer':
  4058. {
  4059. $element = trim($row[$columnName]);
  4060. if (empty($searchSuggestionsPattern) OR (!empty($searchSuggestionsPattern) AND !empty($element)))
  4061. $resultBuffer[$i++] = $element;
  4062. }
  4063. }
  4064. if ($SplitValues) // (otherwise, data are already DISTINCT and ORDERed BY!)
  4065. {
  4066. if (!empty($resultBuffer))
  4067. {
  4068. // remove duplicate values from array:
  4069. $resultBuffer = array_unique($resultBuffer);
  4070. // sort in ascending order:
  4071. sort($resultBuffer);
  4072. }
  4073. }
  4074. if ($outputFormat == "ARRAY") // return data as a PHP array:
  4075. {
  4076. return $resultBuffer;
  4077. }
  4078. else // return data as HTML or JSON:
  4079. {
  4080. $outputData = "";
  4081. if ($outputFormat == "HTML SELECT") // output data in an HTML select widget:
  4082. {
  4083. // Start the HTML select widget:
  4084. if ($wrapSearchSuggestions)
  4085. $outputData = "\n\t\t<select name=\"$pulldownName\">";
  4086. $optionTags = ""; // initialize variable
  4087. // Add any additional option element:
  4088. if (!empty($additionalOptionDisplay) AND !empty($additionalOption))
  4089. $optionTags .= "\n\t\t\t<option value=\"$additionalOption\">$additionalOptionDisplay</option>";
  4090. // Build correct option tags from the provided database values:
  4091. $optionTags .= buildSelectMenuOptions($resultBuffer, "//", "\t\t\t", false);
  4092. $outputData .= preg_replace("/<option([^>]*)>($defaultValue)<\\/option>/", "<option\\1 selected>\\2</option>", $optionTags); // add 'selected' attribute
  4093. if ($wrapSearchSuggestions)
  4094. $outputData .= "\n\t\t</select>";
  4095. }
  4096. elseif (($outputFormat == "HTML UL") AND !empty($resultBuffer)) // output data in an unordered HTML list:
  4097. {
  4098. $outputData = "<li>" . implode("</li><li>", $resultBuffer) . "</li>";
  4099. if ($wrapSearchSuggestions)
  4100. $outputData = "<ul>" . $outputData . "</ul>";
  4101. }
  4102. elseif (($outputFormat == "JSON") AND !empty($resultBuffer)) // output data in JSON format:
  4103. {
  4104. $outputData = '"' . implode('", "', $resultBuffer) . '"'; // for PHP 5 >= 5.2.0 and UTF-8 data, function 'json_encode()' could be used instead
  4105. if ($wrapSearchSuggestions)
  4106. $outputData = "[" . $outputData . "]";
  4107. }
  4108. return $outputData;
  4109. }
  4110. }
  4111. // --------------------------------------------------------------------
  4112. // Returns values from the given field & table:
  4113. function getFieldContents($tableName, $columnName, $userID = "", $queryWhereClause = "", $orderBy = "", $getDistinctValues = true)
  4114. {
  4115. global $tableRefs, $tableUserData; // defined in 'db.inc.php'
  4116. connectToMySQLDatabase();
  4117. if ($getDistinctValues)
  4118. $distinct = "DISTINCT ";
  4119. else
  4120. $distinct = "";
  4121. // CONSTRUCT SQL QUERY:
  4122. $query = "SELECT " . $distinct . $columnName
  4123. . " FROM " . $tableName;
  4124. if (($tableName == $tableRefs) AND isset($_SESSION['loginEmail']) AND !empty($userID)) // when querying table 'refs', and if a user is logged in...
  4125. $query .= " LEFT JOIN " . $tableUserData . " ON serial = record_id AND user_id = " . quote_smart($userID);
  4126. if (!empty($queryWhereClause))
  4127. $query .= " WHERE " . $queryWhereClause;
  4128. if (!empty($orderBy))
  4129. $query .= " ORDER BY " . $orderBy;
  4130. $result = queryMySQLDatabase($query); // RUN the query on the database through the connection
  4131. $fieldContentsArray = array(); // initialize array variable
  4132. $rowsFound = @ mysqli_num_rows($result);
  4133. if ($rowsFound > 0) // If there were rows found ...
  4134. {
  4135. while ($row = @ mysqli_fetch_array($result)) // for all rows found
  4136. $fieldContentsArray[] = $row[$columnName]; // append this row's field value to the array of extracted field values
  4137. }
  4138. return $fieldContentsArray;
  4139. }
  4140. // --------------------------------------------------------------------
  4141. // Remove a text pattern from the beginning and/or end of a string:
  4142. // This function is used to remove leading and/or trailing delimiters from a string.
  4143. // Notes: - '$removePattern' must be specified as perl-style regular expression!
  4144. // - set both variables '$trimLeft' & '$trimRight' to 'true' if you want your text pattern to get removed from BOTH sides of the source string;
  4145. // if you only want to trim the LEFT side of your source string: set '$trimLeft = true' & '$trimRight = false';
  4146. // if you only want to trim the RIGHT side of your source string: set '$trimLeft = false' & '$trimRight = true';
  4147. // Example: if '$removePattern' = ' *; *' and both, '$trimLeft' and '$trimRight', are set to 'true',
  4148. // the string '; red; green; yellow; ' would be transformed to 'red; green; yellow'.
  4149. function trimTextPattern($sourceString, $removePattern, $trimLeft, $trimRight)
  4150. {
  4151. if ($trimLeft)
  4152. $sourceString = preg_replace("/^" . $removePattern . "/", "", $sourceString); // remove text pattern from beginning of source string
  4153. if ($trimRight)
  4154. $sourceString = preg_replace("/" . $removePattern . "$/", "", $sourceString); // remove text pattern from end of source string
  4155. return $sourceString; // return the trimmed source string
  4156. }
  4157. // --------------------------------------------------------------------
  4158. // Quote variable to make safe (and escape special characters in a string for use in a SQL statement):
  4159. function quote_smart($value)
  4160. {
  4161. // Remove slashes from value if 'magic_quotes_gpc = On':
  4162. $value = stripSlashesIfMagicQuotes($value);
  4163. // Remove any leading or trailing whitespace:
  4164. $value = trim($value);
  4165. // Quote & escape special chars if not a number or a numeric string:
  4166. if (!is_numeric($value))
  4167. {
  4168. $value = "\"" . escapeSQL($value) . "\"";
  4169. }
  4170. // Quote numbers with leading zeros (which would otherwise get stripped):
  4171. elseif (preg_match("/^0+\d+$/", $value))
  4172. {
  4173. $value = "\"" . $value . "\"";
  4174. }
  4175. return $value;
  4176. }
  4177. // --------------------------------------------------------------------
  4178. // Get the path to the currently executing script, relative to the document root:
  4179. function scriptURL()
  4180. {
  4181. if (isset($_SERVER['SCRIPT_NAME']))
  4182. {
  4183. $pathToScript = $_SERVER['SCRIPT_NAME'];
  4184. }
  4185. else
  4186. {
  4187. $pathToScript = $_SERVER['PHP_SELF'];
  4188. // Sanitize PHP_SELF:
  4189. if (preg_match('#\.php.+#', $pathToScript))
  4190. {
  4191. // Remove anything after the PHP file extension:
  4192. $pathToScript = preg_replace('#(?<=\.php).+#', '', $pathToScript);
  4193. }
  4194. }
  4195. // NOTE: When a 'show.php' URL is called from within another script via function 'fetchDataFromURL()'
  4196. // (as is the case for 'index.php'), '$_SERVER['SCRIPT_NAME']' and '$_SERVER['PHP_SELF']' do seem
  4197. // to return double slashes for path separators (e.g. "/refs//search.php"). I don't know why this
  4198. // happens. The line below fixes this:
  4199. $pathToScript = preg_replace('#//+#', '/', $pathToScript);
  4200. return $pathToScript;
  4201. }
  4202. // --------------------------------------------------------------------
  4203. // Verify the given path:
  4204. //
  4205. // NOTES: - Currently, this function just ensures that the given '$path' (i.e. a
  4206. // file directory path or URL) ends with a slash. However, it would be
  4207. // nice if this function would also check whether the given path or URL
  4208. // exists, and issue a warning if not.
  4209. // - '$type' must be either "path" or "URL"
  4210. //
  4211. // TODO: - Should we attempt to auto-guess the '$type' form the given '$path'?
  4212. // - Make sure that the correct path separator gets used on Windows
  4213. function checkPath($path, $type = "path", $addTrailingSlash = true)
  4214. {
  4215. // if (preg_match("/Windows/i", getenv("OS")) // should we query 'HTTP_USER_AGENT' instead?
  4216. // $pathSeparator = '\\'; // is this actually necessary? (I vaguely remember that PHP also uses '/' on Windows)
  4217. // else
  4218. $pathSeparator = '/';
  4219. if ($addTrailingSlash)
  4220. $path = preg_replace('#(?<!^|/)$#', $pathSeparator, $path);
  4221. return $path;
  4222. }
  4223. // --------------------------------------------------------------------
  4224. // Removes slashes from the input string if 'magic_quotes_gpc = On':
  4225. function stripSlashesIfMagicQuotes($sourceString)
  4226. {
  4227. $magicQuotes = ini_get("magic_quotes_gpc"); // check the value of the 'magic_quotes_gpc' directive in 'php.ini'
  4228. if ($magicQuotes) // magic_quotes_gpc = On
  4229. $sourceString = convertSlashes($sourceString);
  4230. return $sourceString;
  4231. }
  4232. // --------------------------------------------------------------------
  4233. // Fix escape sequences within a string (i.e., remove 'unwanted' slashes):
  4234. function convertSlashes($sourceString)
  4235. {
  4236. // $sourceString = stripslashes($sourceString);
  4237. // Note that function 'stripslashes()' cannot be used here since it may remove too many slashes!
  4238. // As an example, assume a user input in 'show.php' like this:
  4239. //
  4240. // <my cite_key> ... <is within list> ... Mock++1997Bacteria
  4241. //
  4242. // 'Mock++1997Bacteria' gets preg_quote()d in 'show.php' to 'Mock\+\+1997Bacteria'. This
  4243. // is necessary to escape any potential grep metacharacters inside the user's cite keys.
  4244. //
  4245. // So, for an input of '^(Mock\+\+1997Bacteria)$', following scenario will occur with 'magic_quotes_gpc = On':
  4246. //
  4247. // Case 1 ('convertSlashes()' uses 'stripslashes()'):
  4248. // 'show.php' -> 'quote_smart()' -> 'stripSlashesIfMagicQuotes()' -> 'convertSlashes()': ^(Mock++1997Bacteria)$ -> this step incorrectly strips the slashes!
  4249. // 'show.php' -> 'quote_smart()' -> 'escapeSQL()': ^(Mock++1997Bacteria)$
  4250. // 'show.php' -> 'quote_smart()': "^(Mock++1997Bacteria)$"
  4251. // 'search.php' receives: \"^(Mock++1997Bacteria)$\"
  4252. // 'search.php' -> 'verifySQLQuery()' -> 'stripSlashesIfMagicQuotes()' -> 'convertSlashes()': "^(Mock++1997Bacteria)$"
  4253. //
  4254. // Case 2 ('convertSlashes()' uses 'str_replace'):
  4255. // 'show.php' -> 'quote_smart()' -> 'stripSlashesIfMagicQuotes()' -> 'convertSlashes()': ^(Mock\+\+1997Bacteria)$
  4256. // 'show.php' -> 'quote_smart()' -> 'escapeSQL()': ^(Mock\\+\\+1997Bacteria)$
  4257. // 'show.php' -> 'quote_smart()': "^(Mock\\+\\+1997Bacteria)$"
  4258. // 'search.php' receives: \"^(Mock\\\\+\\\\+1997Bacteria)$\"
  4259. // 'search.php' -> 'verifySQLQuery()' -> 'stripSlashesIfMagicQuotes()' -> 'convertSlashes()': "^(Mock\\+\\+1997Bacteria)$"
  4260. //
  4261. // This means that 'stripslashes()' fails while the code below seems to work:
  4262. $sourceString = str_replace('\"', '"', $sourceString); // replace any \" with "
  4263. $sourceString = str_replace("\\'", "'", $sourceString); // replace any \' with '
  4264. $sourceString = str_replace("\\\\", "\\", $sourceString);
  4265. // $sourceString = preg_replace('/(\\\\)+/i', '\\\\', $sourceString); // instead of the previous line, this would kinda work if SQL strings aren't quote_smart()ed
  4266. return $sourceString;
  4267. }
  4268. // --------------------------------------------------------------------
  4269. // Perform search & replace actions on the given text input:
  4270. // ('$includesSearchPatternDelimiters' must be a boolean value that specifies whether the leading and trailing slashes
  4271. // are included within the search pattern ['true'] or not ['false'])
  4272. function searchReplaceText($searchReplaceActionsArray, $sourceString, $includesSearchPatternDelimiters)
  4273. {
  4274. // this allows to use '$loc' within the replacement pattern
  4275. // e.g. like this: array("/(.+)/e" => "\$loc['\\1']")
  4276. global $loc; // '$loc' is made globally available in 'core.php'
  4277. // apply the search & replace actions defined in '$searchReplaceActionsArray' to the text passed in '$sourceString':
  4278. foreach ($searchReplaceActionsArray as $searchString => $replaceString)
  4279. {
  4280. if (!$includesSearchPatternDelimiters)
  4281. $searchString = "/" . $searchString . "/"; // add search pattern delimiters
  4282. if (preg_match($searchString, $sourceString))
  4283. $sourceString = preg_replace($searchString, $replaceString, $sourceString);
  4284. }
  4285. return $sourceString;
  4286. }
  4287. // --------------------------------------------------------------------
  4288. // Perform case transformations on the given text input:
  4289. // ('$transformation' must be either 'lower', 'upper', 'title' or 'heading')
  4290. //
  4291. // NOTE: For UTF-8, the PHP functions 'strtolower()' and 'strtoupper()' will only work correctly
  4292. // if the server has locales installed which support UTF-8! More info is available at:
  4293. // <http://www.phpwact.org/php/i18n/charsets>
  4294. // <http://www.phpwact.org/php/i18n/utf-8>
  4295. //
  4296. // TODO: Implement function 'changeCase()' so that it always works for UTF-8
  4297. // See e.g. functions 'utf8_strtolower()' and 'utf8_strtoupper()' at
  4298. // <http://dev.splitbrain.org/view/darcs/dokuwiki/inc/utf8.php>
  4299. function changeCase($transformation, $sourceString)
  4300. {
  4301. if (preg_match("/lower/i", $transformation)) // change source text to lower case
  4302. $sourceString = strtolower($sourceString);
  4303. elseif (preg_match("/upper/i", $transformation)) // change source text to upper case
  4304. $sourceString = strtoupper($sourceString);
  4305. elseif (preg_match("/title/i", $transformation)) // change source text to title case
  4306. $sourceString = preg_replace("/\b(\w)(\w+)/e", "strtoupper('\\1').strtolower('\\2')", $sourceString); // the 'e' modifier allows to execute PHP code within the replacement pattern
  4307. 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)
  4308. $sourceString = preg_replace("/\b(\w)(\w{3,})/e", "strtoupper('\\1').'\\2'", $sourceString); // the 'e' modifier allows to execute PHP code within the replacement pattern
  4309. return $sourceString;
  4310. }
  4311. // --------------------------------------------------------------------
  4312. // Sets the system's locale information:
  4313. // On *NIX systems, use "locale -a" on the command line to display all locales
  4314. // supported on your system. See <http://www.php.net/setlocale> for more information.
  4315. function setSystemLocale($charSet = "", $systemLocales = "NONE")
  4316. {
  4317. global $contentTypeCharset; // these variables are defined in 'ini.inc.php'
  4318. global $convertExportDataToUTF8;
  4319. if (empty($charSet))
  4320. $charSet = $contentTypeCharset;
  4321. if ($systemLocales == "NONE") {
  4322. if ($charSet == "UTF-8")
  4323. $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');
  4324. else // we assume "ISO-8859-1" by default
  4325. $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');
  4326. }
  4327. setlocale(LC_COLLATE, $systemLocales); // set locale for string comparison (including pattern matching)
  4328. setlocale(LC_CTYPE, $systemLocales); // set locale for character classification and conversion, for example 'strtoupper()'
  4329. // get the current settings without affecting them:
  4330. $systemLocaleCollate = setlocale(LC_COLLATE, "0");
  4331. $systemLocaleCType = setlocale(LC_CTYPE, "0");
  4332. return array($systemLocaleCollate, $systemLocaleCType);
  4333. }
  4334. // --------------------------------------------------------------------
  4335. // Sets the mimetype & character encoding in the header:
  4336. function setHeaderContentType($contentType, $contentTypeCharset)
  4337. {
  4338. header('Content-type: ' . $contentType . '; charset=' . $contentTypeCharset);
  4339. }
  4340. // --------------------------------------------------------------------
  4341. // Set HTTP status response
  4342. // From user 'Ciantic' found at <http://www.php.net/header> (24-Dec-2005 03:07)
  4343. // This contains all HTTP status responses defined in RFC2616 section 6.1.1
  4344. // See <http://www.w3.org/Protocols/rfc2616/rfc2616-sec6.html#sec6.1.1>
  4345. function setHTTPStatus($statusCode)
  4346. {
  4347. // HTTP Protocol defined status codes:
  4348. static $http = array(100 => "HTTP/1.1 100 Continue",
  4349. 101 => "HTTP/1.1 101 Switching Protocols",
  4350. 200 => "HTTP/1.1 200 OK",
  4351. 201 => "HTTP/1.1 201 Created",
  4352. 202 => "HTTP/1.1 202 Accepted",
  4353. 203 => "HTTP/1.1 203 Non-Authoritative Information",
  4354. 204 => "HTTP/1.1 204 No Content",
  4355. 205 => "HTTP/1.1 205 Reset Content",
  4356. 206 => "HTTP/1.1 206 Partial Content",
  4357. 300 => "HTTP/1.1 300 Multiple Choices",
  4358. 301 => "HTTP/1.1 301 Moved Permanently",
  4359. 302 => "HTTP/1.1 302 Found",
  4360. 303 => "HTTP/1.1 303 See Other",
  4361. 304 => "HTTP/1.1 304 Not Modified",
  4362. 305 => "HTTP/1.1 305 Use Proxy",
  4363. 307 => "HTTP/1.1 307 Temporary Redirect",
  4364. 400 => "HTTP/1.1 400 Bad Request",
  4365. 401 => "HTTP/1.1 401 Unauthorized",
  4366. 402 => "HTTP/1.1 402 Payment Required",
  4367. 403 => "HTTP/1.1 403 Forbidden",
  4368. 404 => "HTTP/1.1 404 Not Found",
  4369. 405 => "HTTP/1.1 405 Method Not Allowed",
  4370. 406 => "HTTP/1.1 406 Not Acceptable",
  4371. 407 => "HTTP/1.1 407 Proxy Authentication Required",
  4372. 408 => "HTTP/1.1 408 Request Time-out",
  4373. 409 => "HTTP/1.1 409 Conflict",
  4374. 410 => "HTTP/1.1 410 Gone",
  4375. 411 => "HTTP/1.1 411 Length Required",
  4376. 412 => "HTTP/1.1 412 Precondition Failed",
  4377. 413 => "HTTP/1.1 413 Request Entity Too Large",
  4378. 414 => "HTTP/1.1 414 Request-URI Too Large",
  4379. 415 => "HTTP/1.1 415 Unsupported Media Type",
  4380. 416 => "HTTP/1.1 416 Requested range not satisfiable",
  4381. 417 => "HTTP/1.1 417 Expectation Failed",
  4382. 500 => "HTTP/1.1 500 Internal Server Error",
  4383. 501 => "HTTP/1.1 501 Not Implemented",
  4384. 502 => "HTTP/1.1 502 Bad Gateway",
  4385. 503 => "HTTP/1.1 503 Service Unavailable",
  4386. 504 => "HTTP/1.1 504 Gateway Time-out");
  4387. header($http[$statusCode]);
  4388. }
  4389. // --------------------------------------------------------------------
  4390. // This function takes the URL given in '$sourceURL' and retrieves the returned data:
  4391. function fetchDataFromURL($sourceURL)
  4392. {
  4393. global $errors;
  4394. $handle = fopen($sourceURL, "r"); // fetch data from URL in read mode
  4395. $sourceData = "";
  4396. if ($handle)
  4397. {
  4398. while (!feof($handle))
  4399. {
  4400. $sourceData .= fread($handle, 4096); // read data in chunks
  4401. }
  4402. fclose($handle);
  4403. }
  4404. else
  4405. {
  4406. $errorMessage = "Error occurred: Failed to open " . $sourceURL; // network error
  4407. if (!isset($errors["sourceText"]))
  4408. $errors["sourceText"] = $errorMessage;
  4409. else
  4410. $errors["sourceText"] = $errors["sourceText"] . "<br>" . $errorMessage;
  4411. }
  4412. return $sourceData;
  4413. }
  4414. // --------------------------------------------------------------------
  4415. // Send '$dataString' as POST request (using the 'application/x-www-form-urlencoded'
  4416. // content type) to the given '$host'/'$path':
  4417. function sendPostRequest($host, $path, $referer, $dataString)
  4418. {
  4419. $port = 80; // server port to be used with the connection
  4420. $timeout = 600; // connection time out in seconds
  4421. $result = "";
  4422. // build header:
  4423. $header = "POST " . $path . " HTTP/1.0\r\n" // "HTTP/1.1" would return data with "Transfer-Encoding: chunked"
  4424. . "Host: " . $host . "\r\n"
  4425. . "Referer: " . $referer . "\r\n"
  4426. . "Content-Type: application/x-www-form-urlencoded\r\n"
  4427. . "Content-Length: ". strlen($dataString) ."\r\n"
  4428. . "\r\n";
  4429. // open connection:
  4430. // see <http://www.php.net/fsockopen>
  4431. $fp = fsockopen($host, $port, $errorNo, $errorMsg, $timeout);
  4432. if (!$fp)
  4433. {
  4434. $result = "Error $errorNo : $errorMsg";
  4435. }
  4436. else
  4437. {
  4438. // POST data:
  4439. fputs($fp, $header . $dataString);
  4440. // read result:
  4441. while (!feof($fp))
  4442. $result .= fgets($fp, 1024);
  4443. // close connection:
  4444. fclose($fp);
  4445. }
  4446. return $result;
  4447. }
  4448. // --------------------------------------------------------------------
  4449. // Detect character encoding:
  4450. // NOTE: - Currently, this function only distinguishes between ISO-8859-1 and UTF-8!
  4451. function detectCharacterEncoding($sourceString, $detectOrder = "")
  4452. {
  4453. // Method A:
  4454. // Function 'mb_detect_encoding()' requires PHP with multi-byte support (i.e., PHP must
  4455. // be compiled with the '--enable-mbstring' configure option).
  4456. // (see: <http://php.net/manual/en/function.mb-detect-encoding.php>)
  4457. // $charSet = "";
  4458. // if (empty($detectOrder))
  4459. // Set the default character encoding detection order:
  4460. // (see: <http://www.php.net/mb-detect-order>)
  4461. // $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
  4462. // $detectOrder = "UTF-8, ISO-8859-1"; // in case of refbase, we currently hardcode the detection order
  4463. // Detect the character encoding of the given '$sourceString' with the given '$detectOrder':
  4464. // $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>
  4465. // Method B:
  4466. // Based on function 'detectUTF8()' by user <chris at w3style dot co dot uk>
  4467. // at <http://php.net/manual/en/function.mb-detect-encoding.php>
  4468. // (see also: <http://w3.org/International/questions/qa-forms-utf-8.html>)
  4469. // Check if a string contains UTF-8 characters:
  4470. // NOTE: This regex pattern only looks for non-ASCII multibyte sequences in
  4471. // the UTF-8 range and stops once it finds at least one multibytes string.
  4472. if (preg_match('%(?:
  4473. [\xC2-\xDF][\x80-\xBF] # non-overlong 2-byte
  4474. | \xE0[\xA0-\xBF][\x80-\xBF] # excluding overlongs
  4475. | [\xE1-\xEC\xEE\xEF][\x80-\xBF]{2} # straight 3-byte
  4476. | \xED[\x80-\x9F][\x80-\xBF] # excluding surrogates
  4477. | \xF0[\x90-\xBF][\x80-\xBF]{2} # planes 1-3
  4478. | [\xF1-\xF3][\x80-\xBF]{3} # planes 4-15
  4479. | \xF4[\x80-\x8F][\x80-\xBF]{2} # plane 16
  4480. )+%xs', $sourceString))
  4481. $charSet = "UTF-8"; // found at least one multibyte UTF-8 character
  4482. else
  4483. $charSet = "ISO-8859-1";
  4484. return $charSet;
  4485. }
  4486. // --------------------------------------------------------------------
  4487. // Convert to character encoding:
  4488. // This function converts text that's represented in '$sourceCharset' into the character encoding
  4489. // given in '$targetCharset'. If '$sourceCharset' isn't given, we default to the refbase database
  4490. // encoding (which is indicated in '$contentTypeCharset'). '$transliteration' must be either
  4491. // "TRANSLIT" or "IGNORE" causing characters which are unrecognized by the target charset to get
  4492. // either transliterated or ignored, respectively.
  4493. function convertToCharacterEncoding($targetCharset, $transliteration, $sourceString, $sourceCharset = "")
  4494. {
  4495. global $contentTypeCharset; // defined in 'ini.inc.php'
  4496. global $transtab_latin1_ascii; // defined in 'transtab_latin1_ascii.inc.php'
  4497. global $transtab_unicode_ascii; // defined in 'transtab_unicode_ascii.inc.php'
  4498. global $transtab_unicode_latin1; // defined in 'transtab_unicode_latin1.inc.php'
  4499. global $transtab_unicode_refbase; // defined in 'transtab_unicode_refbase.inc.php'
  4500. if (empty($sourceCharset))
  4501. $sourceCharset = $contentTypeCharset;
  4502. // In case of ISO-8859-1/UTF-8 to ASCII conversion we attempt to transliterate non-ASCII chars,
  4503. // comparable to the fallback notations that people use commonly in email and on typewriters to
  4504. // represent unavailable characters:
  4505. if (($targetCharset == "ASCII") AND ($transliteration == "TRANSLIT"))
  4506. {
  4507. if ($sourceCharset == "UTF-8")
  4508. $convertedString = searchReplaceText($transtab_unicode_ascii, $sourceString, false);
  4509. else // we assume "ISO-8859-1" by default
  4510. $convertedString = searchReplaceText($transtab_latin1_ascii, $sourceString, false);
  4511. // Strip any additional non-ASCII characters which we weren't able to transliterate:
  4512. $convertedString = iconv($sourceCharset, "ASCII//IGNORE", $convertedString);
  4513. // Notes from <http://www.php.net/manual/en/function.iconv.php> regarding "TRANSLIT" and "IGNORE":
  4514. // - If you append the string //TRANSLIT to out_charset transliteration is activated.
  4515. // This means that when a character can't be represented in the target charset, it can
  4516. // be approximated through one or several similarly looking characters. If you append
  4517. // the string //IGNORE, characters that cannot be represented in the target charset
  4518. // are silently discarded. Otherwise, str is cut from the first illegal character.
  4519. }
  4520. // Similar to the ISO-8859-1/UTF-8 to ASCII conversion we attempt to transliterate non-latin1 chars when
  4521. // converting from UTF-8 to ISO-8859-1.
  4522. // NOTE: we don't use 'iconv("UTF-8", "ISO-8859-1//TRANSLIT", $sourceString)' here, since this seems to
  4523. // abort the conversion with an error ("Detected an illegal character in input string") if e.g. a
  4524. // greek delta character is encountered.
  4525. elseif (($targetCharset == "ISO-8859-1") AND ($transliteration == "TRANSLIT") AND ($sourceCharset == "UTF-8"))
  4526. {
  4527. // Convert Unicode entities to refbase markup (if possible):
  4528. $convertedString = searchReplaceText($transtab_unicode_refbase, $sourceString, true);
  4529. // Attempt to transliterate any remaining non-latin1 characters:
  4530. $convertedString = searchReplaceText($transtab_unicode_latin1, $convertedString, false);
  4531. // Strip any additional non-latin1 characters which we weren't able to transliterate:
  4532. $convertedString = iconv($sourceCharset, "ISO-8859-1//IGNORE", $convertedString);
  4533. }
  4534. else
  4535. $convertedString = iconv($sourceCharset, "$targetCharset//$transliteration", $sourceString);
  4536. return $convertedString;
  4537. }
  4538. // --------------------------------------------------------------------
  4539. // Encode HTML entities:
  4540. // (this custom function is provided so that it'll be easier to change the way how entities are HTML encoded later on)
  4541. function encodeHTML($sourceString)
  4542. {
  4543. global $contentTypeCharset; // defined in 'ini.inc.php'
  4544. // Note: Using PHP 5.0.4, I couldn't get 'htmlentities()' to work properly with UTF-8. Apparently, versions before
  4545. // PHP 4.3.11 and PHP 5.0.4 had a partially incorrect utf8 to htmlentities mapping (see <http://bugs.php.net/28067>),
  4546. // however, PHP 5.0.4 still seems buggy to me. Therefore, in case of UTF-8, we'll use 'mb_convert_encoding()' instead.
  4547. // IMPORTANT: this requires multi-byte support enabled on your PHP server!
  4548. // (i.e., PHP must be compiled with the '--enable-mbstring' configure option)
  4549. if ($contentTypeCharset == "UTF-8")
  4550. {
  4551. // encode HTML special chars
  4552. $sourceString = encodeHTMLspecialchars($sourceString);
  4553. // converts from 'UTF-8' to 'HTML-ENTITIES' (see: <http://php.net/manual/en/function.mb-convert-encoding.php>)
  4554. $encodedString = mb_convert_encoding($sourceString, 'HTML-ENTITIES', "$contentTypeCharset");
  4555. }
  4556. else
  4557. $encodedString = htmlentities($sourceString, ENT_COMPAT, "$contentTypeCharset");
  4558. // Notes from <http://www.php.net/htmlentities>:
  4559. //
  4560. // - The optional second parameter lets you define what will be done with 'single' and "double" quotes.
  4561. // It takes on one of three constants with the default being ENT_COMPAT:
  4562. // ENT_COMPAT: Will convert double-quotes and leave single-quotes alone.
  4563. // ENT_QUOTES: Will convert both double and single quotes.
  4564. // ENT_NOQUOTES: Will leave both double and single quotes unconverted.
  4565. //
  4566. // - The optional third argument defines the character set used in conversion. Support for this argument
  4567. // was added in PHP 4.1.0. Presently, the ISO-8859-1 character set is used as the default.
  4568. return $encodedString;
  4569. }
  4570. // --------------------------------------------------------------------
  4571. // Encode HTML special chars:
  4572. // As opposed to the 'encodeHTML()' function this function will only convert the characters supported by the
  4573. // 'htmlspecialchars()' function:
  4574. // - '&' (ampersand) becomes '&amp;'
  4575. // - '"' (double quote) becomes '&quot;' when ENT_NOQUOTES is not set
  4576. // - ''' (single quote) becomes '&#039;' only when ENT_QUOTES is set
  4577. // - '<' (less than) becomes '&lt;'
  4578. // - '>' (greater than) becomes '&gt;'
  4579. // Note that these (and only these!) entities are also supported by XML (which is why we use this function within the XML
  4580. // generating functions 'generateRSS()', 'modsRecord()' & 'atomEntry()' and leave all other higher ASCII chars unencoded)
  4581. function encodeHTMLspecialchars($sourceString)
  4582. {
  4583. global $contentTypeCharset; // defined in 'ini.inc.php'
  4584. $encodedString = htmlspecialchars($sourceString, ENT_COMPAT, "$contentTypeCharset");
  4585. // Notes from <http://www.php.net/htmlspecialchars>:
  4586. //
  4587. // - The optional second parameter lets you define what will be done with 'single' and "double" quotes.
  4588. // It takes on one of three constants with the default being ENT_COMPAT:
  4589. // ENT_COMPAT: Will convert double-quotes and leave single-quotes alone.
  4590. // ENT_QUOTES: Will convert both double and single quotes.
  4591. // ENT_NOQUOTES: Will leave both double and single quotes unconverted.
  4592. //
  4593. // - The optional third argument defines the character set used in conversion. Support for this argument
  4594. // was added in PHP 4.1.0. Presently, the ISO-8859-1 character set is used as the default.
  4595. return $encodedString;
  4596. }
  4597. // --------------------------------------------------------------------
  4598. // Decode HTML entities:
  4599. // This function converts HTML entities in '$sourceString' to the character encoding given in '$targetCharset'.
  4600. // It is intended to work similar to function 'html_entity_decode()' but should also support conversion of numeric
  4601. // entities as well as UTF-8 on PHP 4. In case of refbase, '$targetCharset' should be either "UTF-8" or "ISO-8859-1".
  4602. function decodeHTML($targetCharset, $sourceString)
  4603. {
  4604. global $contentTypeCharset; // defined in 'ini.inc.php'
  4605. static $transtab_HTML;
  4606. // Method A:
  4607. // Function 'html_entity_decode()' is available since PHP 4.3.0, but UTF-8 support was only added with PHP 5?
  4608. // (see <http://www.php.net/html-entity-decode>)
  4609. // NOTE: This function doesn't convert numeric entities, so, if used, it should be combined with the code block
  4610. // underneath "Replace numeric entities" below.
  4611. // $convertedString = html_entity_decode($sourceString, ENT_QUOTES, "$targetCharset");
  4612. // W.r.t. the second parameter, see notes underneath the call to 'htmlentities()' in function 'encodeHTML()'
  4613. // Method B:
  4614. // Function 'mb_convert_encoding()' requires PHP with multi-byte support (i.e., PHP must be compiled with the
  4615. // '--enable-mbstring' configure option). Converts from 'HTML-ENTITIES' to '$targetCharset'.
  4616. // (see: <http://php.net/manual/en/function.mb-convert-encoding.php>)
  4617. // NOTE: Compared to methods A + C, this seems to yield different results! ?:-/
  4618. // $convertedString = mb_convert_encoding($sourceString, "$targetCharset", 'HTML-ENTITIES');
  4619. // Method C:
  4620. // Assembled from user contributions at <http://www.php.net/html-entity-decode>
  4621. // - Replace numeric entities:
  4622. $convertedString = preg_replace('/&#x0*([0-9a-f]+);/ei', "charNumToCharString('$targetCharset', hexdec('\\1'))", $sourceString); // hex notation
  4623. $convertedString = preg_replace('/&#0*([0-9]+);/e', "charNumToCharString('$targetCharset', '\\1')", $convertedString); // decimal notation
  4624. // - Replace literal entities:
  4625. if (!isset($transtab_HTML))
  4626. {
  4627. // Get the translation table that's used by function 'htmlspecialchars()':
  4628. $transtab_HTML = get_html_translation_table(HTML_ENTITIES, ENT_QUOTES);
  4629. $transtab_HTML = array_flip($transtab_HTML);
  4630. // Change the translation table from latin1 to UTF-8 (if necessary):
  4631. if ($targetCharset == "UTF-8")
  4632. foreach ($transtab_HTML as $key => $value)
  4633. $transtab_HTML[$key] = utf8_encode($value); // encode ISO-8859-1 char as UTF-8
  4634. }
  4635. $convertedString = strtr($convertedString, $transtab_HTML);
  4636. return $convertedString;
  4637. }
  4638. // --------------------------------------------------------------------
  4639. // Decode HTML special chars:
  4640. // As opposed to the 'decodeHTML()' function this function will only decode the characters supported by the
  4641. // 'htmlspecialchars()' function:
  4642. // - '&amp;' (ampersand) becomes '&'
  4643. // - '&quot;' (double quote) becomes '"' when ENT_NOQUOTES is not set
  4644. // - '&#039;' (single quote) becomes ''' only when ENT_QUOTES is set
  4645. // - '&lt;' (less than) becomes '<'
  4646. // - '&gt;' (greater than) becomes '>'
  4647. function decodeHTMLspecialchars($sourceString)
  4648. {
  4649. static $transtab_HTMLspecialchars;
  4650. // Method A:
  4651. // Function 'htmlspecialchars_decode()' seems to be available since PHP 5.1.0.
  4652. // (see <http://www.php.net/htmlspecialchars-decode>)
  4653. // $decodedString = htmlspecialchars_decode($sourceString, ENT_QUOTES);
  4654. // W.r.t. the second parameter, see notes underneath the call to 'htmlspecialchars()' in function 'encodeHTMLspecialchars()'
  4655. // Method B:
  4656. // Assembled from user contributions at <http://www.php.net/htmlspecialchars-decode>
  4657. if (!isset($transtab_HTMLspecialchars))
  4658. {
  4659. // Get the translation table that's used by function 'htmlspecialchars()':
  4660. $transtab_HTMLspecialchars = get_html_translation_table(HTML_SPECIALCHARS, ENT_QUOTES);
  4661. $transtab_HTMLspecialchars = array_flip($transtab_HTMLspecialchars);
  4662. if (!isset($transtab_HTMLspecialchars['&#039;'])) // we need to add '&#039;' since the above call to 'get_html_translation_table()' returns just '&#39;'
  4663. $transtab_HTMLspecialchars['&#039;'] = "'";
  4664. }
  4665. $decodedString = strtr($sourceString, $transtab_HTMLspecialchars);
  4666. return $decodedString;
  4667. }
  4668. // --------------------------------------------------------------------
  4669. // Returns the character string that corresponds to the given character code value:
  4670. // (modified after user contributions by <akniep at rayo dot info>, <aurynas dot butkus at gmail dot com>
  4671. // and <romans at void dot lv> at <http://www.php.net/html-entity-decode>)
  4672. // NOTE: - In case of refbase, '$targetCharset' should be either "UTF-8" or "ISO-8859-1"
  4673. // - For a latin1-based database, we'll convert any Unicode-only entities into the
  4674. // corresponding refbase markup (if possible), and any remaining UTF-8 characters
  4675. // will be converted to their ASCII equivalents.
  4676. function charNumToCharString($targetCharset, $num)
  4677. {
  4678. global $transtab_unicode_ascii; // defined in 'transtab_unicode_ascii.inc.php'
  4679. global $transtab_unicode_refbase; // defined in 'transtab_unicode_refbase.inc.php'
  4680. // Generates a UTF-8 string that corresponds to the given Unicode value:
  4681. if ($num < 0)
  4682. $utfChar = '';
  4683. elseif ($num < 128)
  4684. $utfChar = chr($num);
  4685. elseif ($num < 2048)
  4686. $utfChar = chr(($num >> 6) + 192) . chr(($num & 63) + 128);
  4687. elseif ($num < 65536)
  4688. $utfChar = chr(($num >> 12) + 224) . chr((($num >> 6) & 63) + 128) . chr(($num & 63) + 128);
  4689. elseif ($num < 2097152)
  4690. $utfChar = chr(($num >> 18) + 240) . chr((($num >> 12) & 63) + 128) . chr((($num >> 6) & 63) + 128) . chr(($num & 63) + 128);
  4691. if (!empty($utfChar) AND $targetCharset == "ISO-8859-1")
  4692. {
  4693. // Convert Unicode entities to refbase markup (if possible):
  4694. $utfChar = searchReplaceText($transtab_unicode_refbase, $utfChar, true);
  4695. // Convert any remaining UTF-8 characters to their ASCII equivalents:
  4696. // TODO: Should we use iconv (function 'convertToCharacterEncoding()') instead?
  4697. $utfChar = searchReplaceText($transtab_unicode_ascii, $utfChar, false);
  4698. }
  4699. return $utfChar;
  4700. }
  4701. // --------------------------------------------------------------------
  4702. // Encode special chars, perform charset conversions (if necessary)
  4703. // and apply any field-specific search & replace actions:
  4704. // ('$targetFormat' must be either "HTML" or "XML")
  4705. function encodeField($fieldName, $fieldValue, $localSearchReplaceActionsArray = array(), $encodingExceptionsArray = array(), $encode = true, $targetFormat = "HTML")
  4706. {
  4707. global $contentTypeCharset; // these variables are defined in 'ini.inc.php'
  4708. global $convertExportDataToUTF8;
  4709. global $searchReplaceActionsArray;
  4710. if (($encode) AND (!in_array($fieldName, $encodingExceptionsArray)))
  4711. {
  4712. if ($targetFormat == "HTML")
  4713. {
  4714. // Encode non-ASCII chars as HTML entities:
  4715. $fieldValue = encodeHTML($fieldValue);
  4716. }
  4717. elseif ($targetFormat == "XML")
  4718. {
  4719. // Only convert those special chars to entities which are supported by XML:
  4720. $fieldValue = encodeHTMLspecialchars($fieldValue);
  4721. // Convert field data to UTF-8:
  4722. if (($convertExportDataToUTF8 == "yes") AND ($contentTypeCharset != "UTF-8"))
  4723. $fieldValue = convertToCharacterEncoding("UTF-8", "IGNORE", $fieldValue);
  4724. }
  4725. }
  4726. // Apply *locally* defined search & replace 'actions' to all fields that are listed
  4727. // in the 'fields' element of the arrays contained in '$localSearchReplaceActionsArray':
  4728. foreach ($localSearchReplaceActionsArray as $fieldActionsArray)
  4729. if (in_array($fieldName, $fieldActionsArray['fields']))
  4730. $fieldValue = searchReplaceText($fieldActionsArray['actions'], $fieldValue, true);
  4731. if ($targetFormat == "HTML")
  4732. {
  4733. // Apply *globally* defined search & replace 'actions' to all fields that are listed
  4734. // in the 'fields' element of the arrays contained in '$searchReplaceActionsArray':
  4735. foreach ($searchReplaceActionsArray as $fieldActionsArray)
  4736. if (in_array($fieldName, $fieldActionsArray['fields']))
  4737. $fieldValue = searchReplaceText($fieldActionsArray['actions'], $fieldValue, true);
  4738. }
  4739. return $fieldValue;
  4740. }
  4741. // --------------------------------------------------------------------
  4742. // Strip HTML and PHP tags from input string:
  4743. // See <http://www.php.net/strip_tags>
  4744. function stripTags($sourceString, $allowedTags = "")
  4745. {
  4746. $cleanedString = strip_tags($sourceString, $allowedTags);
  4747. return $cleanedString;
  4748. }
  4749. // --------------------------------------------------------------------
  4750. // Verify the SQL query specified by the user and modify it if security concerns are encountered:
  4751. // (this function does add/remove user-specific query code as required and will fix problems with escape sequences within the SQL query)
  4752. function verifySQLQuery($sqlQuery, $referer, $displayType, $showLinks)
  4753. {
  4754. global $loginEmail;
  4755. global $loginUserID;
  4756. global $fileVisibility; // these variables are specified in 'ini.inc.php'
  4757. global $librarySearchPattern;
  4758. global $showAdditionalFieldsDetailsViewDefault;
  4759. global $showUserSpecificFieldsDetailsViewDefault;
  4760. global $tableRefs, $tableUserData; // defined in 'db.inc.php'
  4761. global $loc; // '$loc' is made globally available in 'core.php'
  4762. // note that, if several errors occur, only the last error message will be displayed
  4763. // disallow display/querying of the 'file' field if NONE of the following conditions are met:
  4764. // - the variable '$fileVisibility' (defined in 'ini.inc.php') is set to 'everyone'
  4765. // - the variable '$fileVisibility' is set to 'login' AND the user is logged in
  4766. // - the variable '$fileVisibility' is set to 'user-specific' AND the 'user_permissions' session variable contains 'allow_download'
  4767. 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'])))))
  4768. {
  4769. // remove 'file' field from SQL query:
  4770. $sqlQuery = stripFieldFromSQLQuery($sqlQuery, "file", true);
  4771. }
  4772. // disallow display/querying of the 'location' field if the user is NOT logged in:
  4773. // (this is mostly done to shield user email addresses from exposure to search engines and/or email harvesting robots)
  4774. if (!isset($_SESSION['loginEmail']))
  4775. {
  4776. // remove 'location' field from SQL query:
  4777. $sqlQuery = stripFieldFromSQLQuery($sqlQuery, "location", true);
  4778. }
  4779. // supply generic 'WHERE' clause if it didn't exist in the SELECT query:
  4780. if (preg_match("/^SELECT/i", $sqlQuery) AND !preg_match("/ FROM " . $tableRefs . ".* WHERE /i", $sqlQuery))
  4781. $sqlQuery = preg_replace("/(?= ORDER BY| LIMIT| GROUP BY| HAVING| PROCEDURE| FOR UPDATE| LOCK IN|$)/i", " WHERE serial RLIKE \".+\"", $sqlQuery, 1);
  4782. // supply generic 'ORDER BY' clause if it didn't exist in the SELECT query:
  4783. // TODO: - add a suitable 'ORDER BY' clause for Browse view and if '$citeOrder != "author"'
  4784. if (preg_match("/^SELECT/i", $sqlQuery) AND !preg_match("/ FROM " . $tableRefs . ".* ORDER BY /i", $sqlQuery) AND ($displayType != "Browse"))
  4785. $sqlQuery = preg_replace("/(?= LIMIT| GROUP BY| HAVING| PROCEDURE| FOR UPDATE| LOCK IN|$)/i", " ORDER BY author, year DESC, publication", $sqlQuery, 1);
  4786. // handle the display & querying of user-specific fields:
  4787. if (!isset($_SESSION['loginEmail'])) // if NO user is logged in...
  4788. {
  4789. // ... and any user-specific fields are part of the SELECT or ORDER BY statement...
  4790. 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
  4791. {
  4792. // if the 'SELECT' clause contains any user-specific fields:
  4793. if (preg_match("/SELECT(.(?!FROM))+?(marked|copy|selected|user_keys|user_notes|user_file|user_groups|cite_key|related)/i",$sqlQuery))
  4794. {
  4795. // return an appropriate error message:
  4796. // 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)
  4797. $HeaderString = returnMsg($loc["Warning_DisplayUserSpecificFieldsOmitted"] . "!", "warning", "strong", "HeaderString");
  4798. }
  4799. $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
  4800. $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
  4801. $sqlQuery = preg_replace("/(SELECT|ORDER BY) *, */i", "\\1 ", $sqlQuery); // ...remove any field delimiters that directly follow the 'SELECT' or 'ORDER BY' terms
  4802. $sqlQuery = preg_replace("/SELECT *(?=FROM)/i", buildSELECTclause("", "", "", false, false) . " ", $sqlQuery); // ...supply generic 'SELECT' clause if it did ONLY contain user-specific fields
  4803. $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
  4804. }
  4805. // ... and the 'LEFT JOIN...' statement is part of the 'FROM' clause...
  4806. 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...
  4807. $sqlQuery = preg_replace("/FROM $tableRefs LEFT JOIN.+WHERE/i","FROM $tableRefs WHERE",$sqlQuery); // ...delete 'LEFT JOIN...' part from 'FROM' clause
  4808. // ... and any user-specific fields are part of the WHERE clause...
  4809. 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')...
  4810. // 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')
  4811. // 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).
  4812. // 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=...')
  4813. // 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=...'
  4814. {
  4815. // Note: in the patterns below we'll attempt to account for parentheses but this won't catch all cases!
  4816. $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
  4817. $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
  4818. $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
  4819. $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
  4820. // return an appropriate error message:
  4821. $HeaderString = returnMsg($loc["Warning_QueryUserSpecificFieldsOmitted"] . "!", "warning", "strong", "HeaderString");
  4822. }
  4823. }
  4824. else // if a user is logged in...
  4825. {
  4826. if (preg_match("/LEFT JOIN $tableUserData/i",$sqlQuery)) // if the 'LEFT JOIN...' statement is part of the 'FROM' clause...
  4827. {
  4828. // ...and any user-specific fields other(!) than the 'selected' or 'cite_key' field are part of the 'SELECT' or 'WHERE' clause...
  4829. // 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.
  4830. // 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')
  4831. // 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=...')
  4832. 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))
  4833. {
  4834. $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
  4835. 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! :-/)
  4836. $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
  4837. }
  4838. }
  4839. // 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);
  4840. // in 'Display Details' view, the 'call_number' and 'serial' fields are the last generic fields before any user-specific fields:
  4841. 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...
  4842. $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
  4843. // in 'Display Details' view, the 'location' field should occur within the SELECT statement before the 'call_number' and 'serial' fields:
  4844. // 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...
  4845. // $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
  4846. // 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
  4847. // 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
  4848. 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...
  4849. $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
  4850. }
  4851. // restrict adding of columns to SELECT queries (so that 'DELETE FROM refs ...' statements won't get modified as well);
  4852. // we'll also exclude the Browse view since these links aren't needed (and would cause problems) in this view
  4853. if (preg_match("/^SELECT/i",$sqlQuery) AND ($displayType != "Browse"))
  4854. {
  4855. $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)
  4856. $sqlQuery = preg_replace("/ FROM $tableRefs/i",", serial FROM $tableRefs",$sqlQuery); // add 'serial' column (which is required in order to obtain unique checkbox names)
  4857. if ($showLinks == "1")
  4858. $sqlQuery = preg_replace("/ FROM $tableRefs/i",", file, url, doi, isbn, type FROM $tableRefs",$sqlQuery); // add 'file', 'url', 'doi', 'isbn' & 'type' columns
  4859. }
  4860. // fix escape sequences within the SQL query:
  4861. $query = stripSlashesIfMagicQuotes($sqlQuery);
  4862. return $query;
  4863. }
  4864. // --------------------------------------------------------------------
  4865. // Removes the field given in '$field' from the SQL query and
  4866. // issues a warning if '$issueWarning == true':
  4867. // TODO: I18n
  4868. function stripFieldFromSQLQuery($sqlQuery, $field, $issueWarning = true)
  4869. {
  4870. // note that, upon multiple warnings, only the last warning message will be displayed
  4871. // if the given '$field' is part of the SELECT or ORDER BY statement...
  4872. if (preg_match("/(SELECT |ORDER BY |, *)" . $field . "/i", $sqlQuery))
  4873. {
  4874. // if the 'SELECT' clause contains '$field':
  4875. if ($issueWarning AND (preg_match("/SELECT(.(?!FROM))+?" . $field . "/i", $sqlQuery)))
  4876. {
  4877. // return an appropriate error message:
  4878. // 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)
  4879. $HeaderString = returnMsg("Display of '" . $field . "' field was omitted!", "warning", "strong", "HeaderString");
  4880. }
  4881. $sqlQuery = preg_replace("/(SELECT|ORDER BY) " . $field . "( DESC)?/i", "\\1 ", $sqlQuery); // ...delete '$field' from beginning of 'SELECT' or 'ORDER BY' clause
  4882. $sqlQuery = preg_replace("/, *" . $field . "( DESC)?/i", "", $sqlQuery); // ...delete any other occurrences of '$field' from 'SELECT' or 'ORDER BY' clause
  4883. $sqlQuery = preg_replace("/(SELECT|ORDER BY) *, */i", "\\1 ", $sqlQuery); // ...remove any field delimiters that directly follow the 'SELECT' or 'ORDER BY' terms
  4884. $sqlQuery = preg_replace("/SELECT *(?=FROM)/i", buildSELECTclause("", "", "", false, false) . " ", $sqlQuery); // ...supply generic 'SELECT' clause if it did ONLY contain the given '$field'
  4885. $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'
  4886. }
  4887. // if the given '$field' is part of the WHERE clause...
  4888. 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
  4889. {
  4890. // Note: in the patterns below we'll attempt to account for parentheses but this won't catch all cases!
  4891. $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
  4892. $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
  4893. $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
  4894. $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'
  4895. if ($issueWarning)
  4896. {
  4897. // return an appropriate error message:
  4898. $HeaderString = returnMsg("Querying of '" . $field . "' field was omitted!", "warning", "strong", "HeaderString");
  4899. }
  4900. }
  4901. return $sqlQuery;
  4902. }
  4903. // --------------------------------------------------------------------
  4904. // this function uses 'mysqli_real_escape_string()' to:
  4905. // - prepend backslashes to \, ', "
  4906. // - replace the characters \x00, \n, \r, and \x1a with a MySQL acceptable representation
  4907. // for queries (e.g., the newline character is replaced with the litteral string '\n')
  4908. function escapeSQL($sourceString)
  4909. {
  4910. global $connection;
  4911. $sourceString = mysqli_real_escape_string($connection, $sourceString);
  4912. return $sourceString;
  4913. }
  4914. // --------------------------------------------------------------------
  4915. // generate a UNIX date/time stamp (integer) from a MySQL-formatted date (YYYY-MM-DD)
  4916. // and time (HH:MM:SS) (or the current date/time if no specific date/time was given):
  4917. function generateUNIXTimeStamp($date = "", $time = "")
  4918. {
  4919. if (!empty($date))
  4920. $dateArray = preg_split("/-/", $date); // split MySQL-formatted date string (e.g. "2004-09-27") into its pieces (year, month, day)
  4921. else
  4922. $dateArray = array(date('Y'), date('m'), date('d')); // use current year, month & day
  4923. if (!empty($time))
  4924. $timeArray = preg_split("/:/", $time); // split MySQL-formatted time string (e.g. "23:58:23") into its pieces (hours, minutes, seconds)
  4925. else
  4926. $timeArray = array(date('H'), date('i'), date('s')); // use current hour, minute & second
  4927. // return the Unix timestamp corresponding to the arguments given; the timestamp is a long integer
  4928. // containing the number of seconds between the Unix Epoch (January 1 1970 00:00:00 GMT) and the time specified:
  4929. $timeStamp = mktime($timeArray[0], $timeArray[1], $timeArray[2], $dateArray[1], $dateArray[2], $dateArray[0]);
  4930. return $timeStamp;
  4931. }
  4932. // --------------------------------------------------------------------
  4933. // generate an ISO date/time stamp (string) according to ISO-8601,
  4934. // the international standard for date and time representations:
  4935. // (ISO-8601 date/time example: "2008-01-11T18:30:21+0100";
  4936. // more info: <http://en.wikipedia.org/wiki/ISO_8601>
  4937. // <http://www.cl.cam.ac.uk/~mgk25/iso-time.html>)
  4938. function generateISO8601TimeStamp($date = "", $time = "")
  4939. {
  4940. $timeStamp = generateUNIXTimeStamp($date, $time);
  4941. $iso8601date = date('Y-m-d\TH:i:s', $timeStamp); // PHP 4+5
  4942. // for PHP4 support, we manually insert a colon in the TZ designation:
  4943. $timezone = date("O", $timeStamp); // get timezone
  4944. $iso8601date .= substr($timezone, 0, -2) . ":" . substr($timezone, -2, 2); // append timezone
  4945. // $iso8601date = date('c', $timeStamp); // PHP 5
  4946. return $iso8601date;
  4947. }
  4948. // --------------------------------------------------------------------
  4949. // generate a RFC-2822 formatted date/time stamp (string):
  4950. // (RFC-2822 date/time example: "Fri, 11 Jan 2008 18:30:21 +0100")
  4951. function generateRFC2822TimeStamp($date = "", $time = "")
  4952. {
  4953. $timeStamp = generateUNIXTimeStamp($date, $time);
  4954. $rfc2822date = date('r', $timeStamp);
  4955. return $rfc2822date;
  4956. }
  4957. // --------------------------------------------------------------------
  4958. // generate an email address from MySQL 'created_by' fields that conforms
  4959. // to the RFC-2822 specifications (<http://www.faqs.org/rfcs/rfc2822.html>):
  4960. function generateRFC2822EmailAddress($createdBy)
  4961. {
  4962. // Note that the following patterns don't attempt to do fancy parsing of email addresses but simply assume the string format
  4963. // of the 'created_by' field (table 'refs'). If you change the string format, you must modify these patterns as well!
  4964. $authorName = preg_replace("/(.+?)\([^)]+\)/", "\\1", $createdBy);
  4965. $authorEmail = preg_replace("/.+?\(([^)]+)\)/", "\\1", $createdBy);
  4966. $rfc2822address = encodeHTMLspecialchars($authorName . "<" . $authorEmail . ">");
  4967. return $rfc2822address;
  4968. }
  4969. // --------------------------------------------------------------------
  4970. // Takes a SQL query and tries to describe it in natural language:
  4971. // (Note that, currently, this function doesn't attempt to cover all kinds of SQL queries [which would be a task by its own!]
  4972. // but rather sticks to what is needed in the context of refbase: I.e., right now, only the 'WHERE' clause will be translated)
  4973. function explainSQLQuery($sourceSQLQuery)
  4974. {
  4975. // fix escape sequences within the SQL query:
  4976. $translatedSQL = stripSlashesIfMagicQuotes($sourceSQLQuery);
  4977. // $translatedSQL = str_replace('\"','"',$sourceSQLQuery); // replace any \" with "
  4978. // $translatedSQL = preg_replace('/(\\\\)+/','\\\\',$translatedSQL);
  4979. // define an array of search & replace actions:
  4980. // (Note that the order of array elements IS important since it defines when a search/replace action gets executed)
  4981. $sqlSearchReplacePatterns = array(" != " => " is not equal to ",
  4982. " = " => " is equal to ",
  4983. " > " => " is greater than ",
  4984. " >= " => " is equal to or greater than ",
  4985. " < " => " is less than ",
  4986. " <= " => " is equal to or less than ",
  4987. "NOT RLIKE \"\\^([^\"]+?)\\$\"" => "is not equal to '\\1'",
  4988. "NOT RLIKE \"\\^" => "does not start with '",
  4989. "NOT RLIKE \"([^\"]+?)\\$\"" => "does not end with '\\1'",
  4990. "NOT RLIKE" => "does not contain",
  4991. "RLIKE \"\\^([^\"]+?)\\$\"" => "is equal to '\\1'",
  4992. "RLIKE \"\\^" => "starts with '",
  4993. "RLIKE \"([^\"]+?)\\$\"" => "ends with '\\1'",
  4994. "RLIKE" => "contains",
  4995. "AND" => "and");
  4996. // Perform search & replace actions on the SQL query:
  4997. $translatedSQL = searchReplaceText($sqlSearchReplacePatterns, $translatedSQL, false);
  4998. $translatedSQL = str_replace('"',"'",$translatedSQL); // replace any remaining " with '
  4999. return $translatedSQL;
  5000. }
  5001. // --------------------------------------------------------------------
  5002. // Extract the 'SELECT' clause from an SQL query:
  5003. function extractSELECTclause($query)
  5004. {
  5005. $querySELECTclause = preg_replace("/^.*?SELECT (.+?) FROM .*?$/i", "\\1", $query);
  5006. return $querySELECTclause;
  5007. }
  5008. // --------------------------------------------------------------------
  5009. // Extract the 'WHERE' clause from an SQL query:
  5010. function extractWHEREclause($query)
  5011. {
  5012. // 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
  5013. $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);
  5014. return $queryWHEREclause;
  5015. }
  5016. // --------------------------------------------------------------------
  5017. // Extract the 'ORDER BY' clause from an SQL query:
  5018. function extractORDERBYclause($query)
  5019. {
  5020. // 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
  5021. $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);
  5022. return $queryORDERBYclause;
  5023. }
  5024. // --------------------------------------------------------------------
  5025. // This function walks a '$searchArray' and appends its items to the WHERE clause:
  5026. // (the array hierarchy will be maintained, i.e. if the '_query' item is itself
  5027. // an array of query items these sub-items will get properly nested in parentheses)
  5028. // Example '$searchArray':
  5029. // Array
  5030. // (
  5031. // [0] => Array
  5032. // (
  5033. // [_boolean] =>
  5034. // [_query] => location RLIKE "user@refbase.net"
  5035. // )
  5036. // [1] => Array
  5037. // (
  5038. // [_boolean] => AND
  5039. // [_query] => Array
  5040. // (
  5041. // [0] => Array
  5042. // (
  5043. // [_boolean] => OR
  5044. // [_query] => author RLIKE "steffens"
  5045. // )
  5046. // [1] => Array
  5047. // (
  5048. // [_boolean] => OR
  5049. // [_query] => title RLIKE "refbase"
  5050. // )
  5051. // [2] => Array
  5052. // (
  5053. // [_boolean] => OR
  5054. // [_query] => keywords RLIKE "refbase"
  5055. // )
  5056. // )
  5057. // )
  5058. // )
  5059. function appendToWhereClause($searchArray)
  5060. {
  5061. global $query;
  5062. foreach ($searchArray as $searchArrayItem)
  5063. {
  5064. 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
  5065. {
  5066. $query .= " ";
  5067. if (!empty($searchArrayItem["_boolean"]))
  5068. $query .= $searchArrayItem["_boolean"] . " ";
  5069. }
  5070. if (is_array($searchArrayItem["_query"])) // recursively parse any sub-arrays of query items and nest them in parentheses
  5071. {
  5072. $query .= "("; // NOTE: the parentheses must be on their own code lines to allow for correct recursion
  5073. $query .= appendToWhereClause($searchArrayItem["_query"]);
  5074. $query .= ")";
  5075. }
  5076. else
  5077. {
  5078. $query .= $searchArrayItem["_query"];
  5079. }
  5080. }
  5081. }
  5082. // -------------------------------------------------------------------------------------------------------------------
  5083. // Generate an URL pointing to a RSS feed or any of the supported export/citation formats for the given query:
  5084. // '$urlType' must be one of these: - RSS XML
  5085. // - export formats: ADS, BibTeX, Endnote, ISI, RIS, Atom XML, MODS XML, OAI_DC XML, ODF XML, SRW_DC XML, SRW_MODS XML, Word XML
  5086. // - citation formats: RTF, PDF, LaTeX, Markdown, ASCII, LaTeX .bbl
  5087. // - default format: html (session variable 'userDefaultView' specifies the default display type)
  5088. function generateURL($baseURL, $urlType, $queryParametersArray, $encodeAmpersands = false, $showRows = 0, $rowOffset = 0, $citeStyle = "", $citeOrder = "")
  5089. {
  5090. global $defaultCiteStyle; // defined in 'ini.inc.php'
  5091. // NOTE: This code block is a hack that fixes an inconsistency in the refbase API, where "RSS XML" is generated by 'rss.php'
  5092. // while all other formats are available via 'show.php'. Eventually, "RSS XML" should be also made available as proper
  5093. // export format so that it can be generated via 'show.php' URLs.
  5094. if (($baseURL == "show.php") AND ($urlType == "RSS XML"))
  5095. $baseURL = "rss.php";
  5096. if (empty($urlType))
  5097. $urlType = "html";
  5098. // NOTE: The record offset ('$rowOffset') as well as the number of records to be returned ('$showRows') will only work for "html"
  5099. // output, any of the citation formats or the export formats "Atom XML", "SRW_DC XML" and "SRW_MODS XML" - the other export formats will
  5100. // currently always export the entire result set. Also, 'rss.php' supports '$showRows', but not '$rowOffset', '$citeStyle' or '$citeOrder'.
  5101. if (!empty($rowOffset))
  5102. {
  5103. if (preg_match("#^((opensearch|sru|show|rss)\.php)$#i", $baseURL))
  5104. $queryParametersArray["startRecord"] = ($rowOffset + 1);
  5105. else
  5106. $queryParametersArray["rowOffset"] = $rowOffset;
  5107. }
  5108. if (!empty($showRows) AND ($showRows != $_SESSION['userRecordsPerPage']))
  5109. {
  5110. if (preg_match("#^((opensearch|sru)\.php)$#i", $baseURL))
  5111. $queryParametersArray["maximumRecords"] = $showRows;
  5112. else
  5113. $queryParametersArray["showRows"] = $showRows;
  5114. }
  5115. // Add parameters required by 'search.php' or the 'show.php' API:
  5116. if (preg_match("#^((search|show)\.php)$#i", $baseURL))
  5117. {
  5118. // - all formats:
  5119. if (!empty($citeOrder) AND ($citeOrder != "author")) // 'citeOrder=author' is the default sort order
  5120. $queryParametersArray["citeOrder"] = $citeOrder;
  5121. // - all formats that (may) contain formatted citations:
  5122. if (preg_match("/^(html|Atom XML|OAI_DC XML|SRW_DC XML|RTF|PDF|LaTeX|Markdown|ASCII|LaTeX \.bbl)$/i", $urlType))
  5123. {
  5124. if (!empty($citeStyle) AND ($citeStyle != $defaultCiteStyle))
  5125. $queryParametersArray["citeStyle"] = $citeStyle;
  5126. }
  5127. // - export formats:
  5128. 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))
  5129. {
  5130. if (!isset($queryParametersArray["exportType"]))
  5131. {
  5132. if (preg_match("/XML/i", $urlType))
  5133. $queryParametersArray["exportType"] = "xml";
  5134. else
  5135. $queryParametersArray["exportType"] = "file";
  5136. }
  5137. $queryParametersArray["submit"] = "Export";
  5138. $queryParametersArray["exportFormat"] = $urlType;
  5139. }
  5140. // - citation formats:
  5141. elseif (preg_match("/^(RTF|PDF|LaTeX|Markdown|ASCII|LaTeX \.bbl)$/i", $urlType))
  5142. {
  5143. $queryParametersArray["submit"] = "Cite";
  5144. $queryParametersArray["citeType"] = $urlType;
  5145. }
  5146. }
  5147. // Add parameters required by 'opensearch.php':
  5148. elseif ($baseURL == "opensearch.php")
  5149. {
  5150. $queryParametersArray["recordSchema"] = $urlType;
  5151. }
  5152. // Build query URL:
  5153. $queryURL = "";
  5154. if ($encodeAmpersands)
  5155. $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
  5156. else
  5157. $ampersandChar = "&";
  5158. foreach ($queryParametersArray as $varname => $value)
  5159. $queryURL .= $ampersandChar . $varname . "=" . rawurlencode($value);
  5160. $queryURL = trimTextPattern($queryURL, $ampersandChar, true, false); // remove again ampersand character from beginning of query URL
  5161. return $baseURL . "?" . $queryURL;
  5162. }
  5163. // --------------------------------------------------------------------
  5164. // Generate RSS XML data from a particular result set (upto the limit given in '$showRows'):
  5165. //
  5166. // TODO: include OpenSearch elements in RSS output
  5167. // (see examples at <http://www.opensearch.org/Specifications/OpenSearch/1.1#OpenSearch_response_elements>)
  5168. function generateRSS($result, $showRows, $rssChannelDescription)
  5169. {
  5170. global $officialDatabaseName; // these variables are defined in 'ini.inc.php'
  5171. global $databaseBaseURL;
  5172. global $feedbackEmail;
  5173. global $defaultCiteStyle;
  5174. global $contentTypeCharset;
  5175. global $logoImageURL;
  5176. global $transtab_refbase_html; // defined in 'transtab_refbase_html.inc.php'
  5177. // Note that we only convert those entities that are supported by XML (by use of the 'encodeHTMLspecialchars()' function).
  5178. // 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'.
  5179. // (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)
  5180. // Define inline text markup to be used by the 'citeRecord()' function:
  5181. $markupPatternsArray = array("bold-prefix" => "<b>",
  5182. "bold-suffix" => "</b>",
  5183. "italic-prefix" => "<i>",
  5184. "italic-suffix" => "</i>",
  5185. "underline-prefix" => "<u>",
  5186. "underline-suffix" => "</u>",
  5187. "endash" => "&#8211;",
  5188. "emdash" => "&#8212;",
  5189. "ampersand" => "&", // ampersands in author contents get encoded in function 'reArrangeAuthorContents()' (since the last param in the 'citeRecord()' function call below is set to 'true')
  5190. "double-quote" => "&quot;",
  5191. "single-quote" => "'",
  5192. "less-than" => "&lt;",
  5193. "greater-than" => "&gt;",
  5194. "newline" => "\n<br>\n"
  5195. );
  5196. $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')")
  5197. // write RSS header:
  5198. $rssData = "<?xml version=\"1.0\" encoding=\"" . $contentTypeCharset . "\"?>"
  5199. . "\n<rss version=\"2.0\" xmlns:dc=\"http://purl.org/dc/elements/1.1/\">";
  5200. // write channel info:
  5201. $rssData .= "\n\t<channel>"
  5202. . "\n\t\t<title>" . encodeHTMLspecialchars($officialDatabaseName) . "</title>"
  5203. . "\n\t\t<link>" . $databaseBaseURL . "</link>"
  5204. . "\n\t\t<description>" . encodeHTMLspecialchars($rssChannelDescription) . "</description>"
  5205. . "\n\t\t<language>en</language>"
  5206. . "\n\t\t<pubDate>" . $currentDateTimeStamp . "</pubDate>"
  5207. . "\n\t\t<lastBuildDate>" . $currentDateTimeStamp . "</lastBuildDate>"
  5208. . "\n\t\t<webMaster>" . $feedbackEmail . "</webMaster>";
  5209. // write image data:
  5210. $rssData .= "\n\n\t\t<image>"
  5211. . "\n\t\t\t<url>" . $databaseBaseURL . $logoImageURL . "</url>"
  5212. . "\n\t\t\t<title>" . encodeHTMLspecialchars($officialDatabaseName) . "</title>"
  5213. . "\n\t\t\t<link>" . $databaseBaseURL . "</link>"
  5214. . "\n\t\t</image>";
  5215. // fetch results: upto the limit specified in '$showRows', fetch a row into the '$row' array and write out a RSS item:
  5216. for ($rowCounter=0; (($rowCounter < $showRows) && ($row = @ mysqli_fetch_array($result))); $rowCounter++)
  5217. {
  5218. $origTitle = $row['title']; // save the original title contents before applying any search & replace actions
  5219. // Perform search & replace actions on the text of the 'title' field:
  5220. // (the array '$transtab_refbase_html' in 'transtab_refbase_html.inc.php' defines which search & replace actions will be employed)
  5221. $row['title'] = searchReplaceText($transtab_refbase_html, $row['title'], true);
  5222. // 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);
  5223. // item titles are still served in raw format, though, since the use of HTML in item titles breaks many news readers
  5224. $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')
  5225. // include the found citation style file *once*:
  5226. include_once "cite/" . $citeStyleFile; // instead of 'include_once' we could also use: 'if ($rowCounter == 0) { include "cite/" . $citeStyleFile; }'
  5227. // Generate a proper citation for this record, ordering attributes according to the chosen output style & record type:
  5228. $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)
  5229. // To avoid advertising email adresses in public RSS output, we remove the email address from contents of the 'modified_by' field which
  5230. // get displayed in item descriptions. However, note that email adresses are NOT stripped from contents of the 'created_by' field
  5231. // since a valid RSS feed must include an email address in the '<author>' element.
  5232. // The following pattern does not attempt to do fancy parsing of email addresses but simply assumes the string format
  5233. // of the 'modified_by' field (table 'refs'). If you change the string format, you must modify this pattern as well!
  5234. $editorName = preg_replace("/(.+?) \([^)]+\)/", "\\1", $row['modified_by']);
  5235. // append a RSS item for the current record:
  5236. $rssData .= "\n\n\t\t<item>"
  5237. . "\n\t\t\t<title>" . encodeHTMLspecialchars($origTitle) . "</title>" // we avoid embedding HTML in the item title and use the raw title instead
  5238. . "\n\t\t\t<link>" . $databaseBaseURL . "show.php?record=" . $row['serial'] . "</link>"
  5239. . "\n\t\t\t<description><![CDATA[" . $record
  5240. . "\n\t\t\t<br><br>Edited by " . encodeHTMLspecialchars($editorName) . " on " . generateRFC2822TimeStamp($row['modified_date'], $row['modified_time']) . ".]]></description>"
  5241. . "\n\t\t\t<guid isPermaLink=\"true\">" . $databaseBaseURL . "show.php?record=" . $row['serial'] . "</guid>"
  5242. . "\n\t\t\t<pubDate>" . generateRFC2822TimeStamp($row['created_date'], $row['created_time']) . "</pubDate>"
  5243. . "\n\t\t\t<author>" . generateRFC2822EmailAddress($row['created_by']) . "</author>"
  5244. . "\n\t\t</item>";
  5245. }
  5246. // finish RSS data:
  5247. $rssData .= "\n\n\t</channel>"
  5248. . "\n</rss>\n";
  5249. return $rssData;
  5250. }
  5251. // --------------------------------------------------------------------
  5252. // Create new table with parsed table data
  5253. // This function will create a new table with separate rows for all sub-items
  5254. // (which are delimited by '$delim') from the given '$field' (from table 'refs'
  5255. // or 'user_data'). This is done to support the Browse view feature for fields
  5256. // that contain a string of multiple values separated by a delimiter.
  5257. // (for each of the multi-item fields this function is executed only once by
  5258. // 'update.php', thereafter 'modify.php' will keep these 'ref_...' tables in
  5259. // sync with the main tables)
  5260. // Note: The split pattern must be specified as perl-style regular expression
  5261. // (including the leading & trailing slashes) and may include mode
  5262. // modifiers (such as '/.../i' to perform a case insensitive match)
  5263. function createNewTableWithParsedTableData($fieldName, $delim)
  5264. {
  5265. global $loginUserID; // saved as session variable on login
  5266. global $tableRefs, $tableUserData; // defined in 'db.inc.php'
  5267. 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)
  5268. {
  5269. $query = "SELECT $fieldName, record_id, user_id FROM $tableUserData"; // WHERE user_id = " . $loginUserID
  5270. $userIDTableSpec = "ref_user_id MEDIUMINT UNSIGNED NOT NULL, ";
  5271. }
  5272. else
  5273. {
  5274. $query = "SELECT $fieldName, serial FROM $tableRefs";
  5275. $userIDTableSpec = "";
  5276. }
  5277. $result = queryMySQLDatabase($query);
  5278. $fieldValuesArray = array(); // initialize array variable which will hold the splitted sub-items
  5279. // split field values on the given delimiter:
  5280. for ($i=0; $row = @ mysqli_fetch_array($result); $i++)
  5281. {
  5282. $fieldSubValuesArray = preg_split($delim, $row[$fieldName]); // split field contents on '$delim' (which is interpreted as perl-style regular expression!)
  5283. foreach ($fieldSubValuesArray as $fieldSubValue)
  5284. {
  5285. // // NOTE: we include empty values so that any Browse view query will also display the number of records where the given field is empty
  5286. // if (!empty($fieldSubValue))
  5287. // {
  5288. $fieldSubValue = trim($fieldSubValue);
  5289. if ($fieldName == "author")
  5290. $fieldSubValue = trimTextPattern($fieldSubValue, " *\(eds?\)", false, true); // remove any existing editor info from the 'author' string, i.e., kill any trailing " (ed)" or " (eds)"
  5291. // copy the individual item (as string, ready for database insertion) to the array:
  5292. if (preg_match("/^(user_keys|user_notes|user_file|user_groups)$/", $fieldName))
  5293. $fieldValuesArray[] = "(NULL, \"". addslashes($fieldSubValue) . "\", $row[record_id], $row[user_id])";
  5294. else
  5295. $fieldValuesArray[] = "(NULL, \"". addslashes($fieldSubValue) . "\", $row[serial])";
  5296. // }
  5297. }
  5298. }
  5299. // build correct 'ref_...' table and field names:
  5300. list($tableName, $fieldName) = buildRefTableAndFieldNames($fieldName);
  5301. // NOTE: the below query will only work if the current MySQL user is allowed to CREATE tables ('Create_priv = Y')
  5302. // therefore, the CREATE statements should be moved to 'update.sql'!
  5303. $queryArray[] = "CREATE TABLE " . $tableName . " ("
  5304. . $fieldName . "_id MEDIUMINT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY, "
  5305. . $fieldName . " VARCHAR(255), "
  5306. . "ref_id MEDIUMINT UNSIGNED NOT NULL, "
  5307. . $userIDTableSpec
  5308. . "INDEX (" . $fieldName . "_id, " . $fieldName . ", ref_id))";
  5309. // TODO: Sanitize with quote_smart
  5310. foreach ($fieldValuesArray as $fieldValue)
  5311. $queryArray[] = "INSERT INTO " . $tableName . " VALUES " . $fieldValue;
  5312. // inserting all values at once may cause 'URL too long' server errors:
  5313. // $fieldValuesString = implode(", ", $fieldValuesArray); // merge array
  5314. // $queryArray[] = "INSERT INTO " . $tableName . " VALUES " . $fieldValuesString;
  5315. // RUN the queries on the database through the connection:
  5316. foreach($queryArray as $query)
  5317. $result = queryMySQLDatabase($query);
  5318. return $tableName;
  5319. }
  5320. // --------------------------------------------------------------------
  5321. // Build correct 'ref_...' table and field names:
  5322. function buildRefTableAndFieldNames($fieldName)
  5323. {
  5324. if ($fieldName == "address")
  5325. {
  5326. $tableName = "ref_addresses";
  5327. $fieldName = "ref_address";
  5328. }
  5329. elseif (!preg_match("/s$/i", $fieldName)) // field name does NOT end with an 's' (such as in 'author')
  5330. {
  5331. $tableName = "ref_" . $fieldName . "s"; // e.g. 'ref_authors'
  5332. $fieldName = "ref_" . $fieldName; // e.g. 'ref_author'
  5333. }
  5334. else // field name ends with an 's' (such as in 'keywords')
  5335. {
  5336. $tableName = "ref_" . $fieldName; // e.g. 'ref_keywords'
  5337. $fieldName = "ref_" . preg_replace("/^(\w+)s$/i", "\\1", $fieldName); // strip 's' from end of field name -> produces e.g. 'ref_keyword'
  5338. }
  5339. return array($tableName, $fieldName);
  5340. }
  5341. // --------------------------------------------------------------------
  5342. ?>