summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--admin.php2
-rw-r--r--admin_media.php1328
-rw-r--r--admin_media_upload.php (renamed from uploadmedia.php)4
-rw-r--r--admin_module_blocks.php176
-rw-r--r--admin_module_menus.php199
-rw-r--r--admin_module_reports.php176
-rw-r--r--admin_module_sidebar.php199
-rw-r--r--admin_module_tabs.php199
-rw-r--r--admin_modules.php245
-rw-r--r--admin_site_config.php114
-rw-r--r--admin_site_info.php81
-rw-r--r--admin_site_ipaddress.php205
-rw-r--r--admin_site_logs.php261
-rw-r--r--admin_site_readme.php61
-rw-r--r--admin_trees_config.php1673
-rw-r--r--admin_trees_manage.php345
-rw-r--r--admin_users_add.php408
-rw-r--r--admin_users_list.php358
-rw-r--r--administration.php236
-rw-r--r--editconfig_gedcom.php3
-rw-r--r--includes/functions/functions.php2
-rw-r--r--includes/functions/functions_db.php28
-rw-r--r--includes/functions/functions_mediadb.php2
-rw-r--r--includes/session.php5
-rw-r--r--js/jquery/jquery.dataTables.js6691
-rw-r--r--load.php120
-rw-r--r--module_admin.php682
-rw-r--r--modules/descendancy/module.php12
-rw-r--r--modules/random_media/module.php2
-rw-r--r--themes/_administration/footer.php33
-rw-r--r--themes/_administration/header.php149
-rw-r--r--themes/_administration/images/close.pngbin0 -> 715 bytes
-rw-r--r--themes/_administration/images/delete.pngbin0 -> 928 bytes
-rw-r--r--themes/_administration/images/edit.pngbin0 -> 467 bytes
-rw-r--r--themes/_administration/images/email.pngbin0 -> 9282 bytes
-rw-r--r--themes/_administration/images/find_facts.pngbin0 -> 1253 bytes
-rw-r--r--themes/_administration/images/header.pngbin0 -> 17809 bytes
-rw-r--r--themes/_administration/images/help.jpgbin0 -> 731 bytes
-rw-r--r--themes/_administration/images/indi.gifbin0 -> 901 bytes
-rw-r--r--themes/_administration/images/media/audio.pngbin0 -> 5294 bytes
-rw-r--r--themes/_administration/images/media/doc.pngbin0 -> 6448 bytes
-rw-r--r--themes/_administration/images/media/flash.pngbin0 -> 4386 bytes
-rw-r--r--themes/_administration/images/media/ged.pngbin0 -> 5463 bytes
-rw-r--r--themes/_administration/images/media/globe.pngbin0 -> 5827 bytes
-rw-r--r--themes/_administration/images/media/html.pngbin0 -> 5360 bytes
-rw-r--r--themes/_administration/images/media/media.pngbin0 -> 6395 bytes
-rw-r--r--themes/_administration/images/media/pdf.pngbin0 -> 5820 bytes
-rw-r--r--themes/_administration/images/media/picasa.pngbin0 -> 6416 bytes
-rw-r--r--themes/_administration/images/media/tex.pngbin0 -> 5744 bytes
-rw-r--r--themes/_administration/images/media/wmv.pngbin0 -> 5505 bytes
-rw-r--r--themes/_administration/images/open.pngbin0 -> 733 bytes
-rw-r--r--themes/_administration/images/webtrees_s.pngbin0 -> 3857 bytes
-rw-r--r--themes/_administration/images/yes.pngbin0 -> 3315 bytes
-rw-r--r--themes/_administration/jquery/images/ui-bg_diagonal-maze_20_6e4f1c_10x10.pngbin0 -> 154 bytes
-rw-r--r--themes/_administration/jquery/images/ui-bg_diagonal-maze_40_000000_10x10.pngbin0 -> 132 bytes
-rw-r--r--themes/_administration/jquery/images/ui-bg_fine-grain_10_eceadf_60x60.pngbin0 -> 4429 bytes
-rw-r--r--themes/_administration/jquery/images/ui-bg_fine-grain_10_f8f7f6_60x60.pngbin0 -> 3251 bytes
-rw-r--r--themes/_administration/jquery/images/ui-bg_fine-grain_15_eceadf_60x60.pngbin0 -> 4391 bytes
-rw-r--r--themes/_administration/jquery/images/ui-bg_fine-grain_15_f7f3de_60x60.pngbin0 -> 4645 bytes
-rw-r--r--themes/_administration/jquery/images/ui-bg_fine-grain_15_ffffff_60x60.pngbin0 -> 3716 bytes
-rw-r--r--themes/_administration/jquery/images/ui-bg_fine-grain_65_654b24_60x60.pngbin0 -> 7369 bytes
-rw-r--r--themes/_administration/jquery/images/ui-bg_fine-grain_68_b83400_60x60.pngbin0 -> 8347 bytes
-rw-r--r--themes/_administration/jquery/images/ui-icons_222222_256x240.pngbin0 -> 4369 bytes
-rw-r--r--themes/_administration/jquery/images/ui-icons_3572ac_256x240.pngbin0 -> 4369 bytes
-rw-r--r--themes/_administration/jquery/images/ui-icons_8c291d_256x240.pngbin0 -> 4369 bytes
-rw-r--r--themes/_administration/jquery/images/ui-icons_b83400_256x240.pngbin0 -> 4369 bytes
-rw-r--r--themes/_administration/jquery/images/ui-icons_fbdb93_256x240.pngbin0 -> 4369 bytes
-rw-r--r--themes/_administration/jquery/images/ui-icons_ffffff_256x240.pngbin0 -> 4369 bytes
-rw-r--r--themes/_administration/jquery/jquery-ui_theme.css572
-rw-r--r--themes/_administration/style.css282
-rw-r--r--themes/_administration/theme.php70
-rw-r--r--themes/clouds/header.php2
-rw-r--r--themes/colors/header.php2
-rw-r--r--themes/fab/header.php2
-rw-r--r--themes/minimal/header.php2
-rw-r--r--themes/webtrees/header.php2
-rw-r--r--themes/xenea/header.php2
77 files changed, 14232 insertions, 703 deletions
diff --git a/admin.php b/admin.php
index faa651bbf1..9d672db341 100644
--- a/admin.php
+++ b/admin.php
@@ -120,7 +120,7 @@ if (WT_USER_IS_ADMIN) {
'<li><a href="#users" onclick="window.location=\'useradmin.php\';">',
i18n::translate('User administration'),
'</a></li>',
- '<li><a href="#multimedia" onclick="window.location=\'media.php\';">',
+ '<li><a href="#multimedia" onclick="window.location=\'admin_media.php\';">',
i18n::translate('Manage multimedia'),
'</a></li>';
}
diff --git a/admin_media.php b/admin_media.php
new file mode 100644
index 0000000000..8a5027fcdc
--- /dev/null
+++ b/admin_media.php
@@ -0,0 +1,1328 @@
+<?php
+/**
+ * Popup window that will allow a user to search for a media
+ *
+ * webtrees: Web based Family History software
+ * Copyright (C) 2010 webtrees development team.
+ *
+ * Derived from PhpGedView
+ * Copyright (C) 2002 to 2009 PGV Development Team. All rights reserved.
+ *
+ * Modifications Copyright (c) 2010 Greg Roach
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ * @package webtrees
+ * @subpackage Display
+ * @version $Id$
+ */
+
+ /* TODO:
+ * Add check for missing index.php files when creating a directory
+ * Add an option to generate thumbnails for all files on the page
+ * Add filter for correct media like php, gif etc.
+ * Check for URL instead of physical file
+ * Check array buld up use ID_GEDCOM for aray key
+ */
+
+ /* Standard variable convention admin_media.php
+ * $filename = Filename of the media item
+ * $thumbnail = Filename of the thumbnail of the media item
+ * $gedfile = Name of the GEDCOM file
+ * $medialist = Array with all media items
+ * $directory = Current directory, starting with $MEDIA_DIRECTORY. Has trailing "/".
+ * $dirs = list of subdirectories within current directory. Built with medialist.
+ */
+
+define('WT_SCRIPT_NAME', 'admin_media.php');
+require './includes/session.php';
+require_once WT_ROOT.'includes/functions/functions_print_lists.php';
+require_once WT_ROOT.'includes/functions/functions_print_facts.php';
+require_once WT_ROOT.'includes/functions/functions_edit.php';
+require_once WT_ROOT.'includes/functions/functions_import.php';
+require_once WT_ROOT.'includes/functions/functions_mediadb.php';
+
+/**
+ * This functions checks if an existing directory is physically writeable
+ * The standard PHP function only checks for the R/O attribute and doesn't
+ * detect authorisation by ACL.
+ */
+function dir_is_writable($dir) {
+ $err_write = false;
+ $handle = @fopen(filename_decode($dir."x.y"), "w+");
+ if ($handle) {
+ $i = fclose($handle);
+ $err_write = true;
+ @unlink(filename_decode($dir."x.y"));
+ }
+ return($err_write);
+}
+
+/**
+ * Moves a file from one location to another, creating destination directory if needed
+ * used by the routines that move files between the standard media directory and the protected media directory
+ */
+function move_file($src, $dest) {
+ global $MEDIA_FIREWALL_ROOTDIR, $MEDIA_DIRECTORY;
+
+ // sometimes thumbnail files are set to something like "images/media.gif", this ensures we do not move them
+ // check to make sure the src file is in the standard or protected media directories
+ if (preg_match("'^($MEDIA_FIREWALL_ROOTDIR)?$MEDIA_DIRECTORY'", $src)==0) {
+ return false;
+ }
+ // check to make sure the dest file is in the standard or protected media directories
+ if (preg_match("'^($MEDIA_FIREWALL_ROOTDIR)?$MEDIA_DIRECTORY'", $dest)==0) {
+ return false;
+ }
+
+ $destdir = dirname($dest);
+ if (!is_dir($destdir)) {
+ @mkdirs($destdir);
+ if (!is_dir($destdir)) {
+ echo "<div class=\"error\">".i18n::translate('Directory could not be created')." [".$destdir."]</div>";
+ return false;
+ }
+ }
+ if (!rename($src, $dest)) {
+ echo "<div class=\"error\">".i18n::translate('Media file could not be moved.')." [".$src."]</div>";
+ return false;
+ }
+ echo "<div>".i18n::translate('Media file moved.')." [".$src."]</div>";
+ return true;
+}
+
+/**
+* Recursively moves files from standard media directory to the protected media directory
+* and vice-versa. Operates directly on the filesystem, does not use the db.
+*/
+function move_files($path, $protect) {
+ global $MEDIA_FIREWALL_THUMBS, $starttime;
+ $timelimit=get_site_setting('MAX_EXECUTION_TIME');
+ if ($dir=@opendir($path)) {
+ while (($element=readdir($dir))!== false) {
+ $exectime = time() - $starttime;
+ if (($timelimit != 0) && ($timelimit - $exectime) < 3) {
+ // bail now to ensure nothing is lost
+ echo "<div class=\"error\">".i18n::translate('The execution time limit was reached. Try the command again to move the rest of the files.')."</div>";
+ return;
+ }
+ // do not move certain files...
+ if ($element!= "." && $element!= ".." && $element!=".svn" && $element!="watermark" && $element!="thumbs" && $element!=".htaccess" && $element!="index.php" && $element!="MediaInfo.txt" && $element!="ThumbsInfo.txt") {
+ $filename = $path."/".$element;
+ if (is_dir($filename)) {
+ // call this function recursively on this directory
+ move_files($filename, $protect);
+ } else {
+ if ($protect) {
+ // Move single file and optionally its corresponding thumbnail to protected dir
+ if (file_exists($filename)) {
+ move_file($filename, get_media_firewall_path($filename));
+ }
+ if ($MEDIA_FIREWALL_THUMBS) {
+ $thumbnail = thumbnail_file($filename, false);
+ if (file_exists($thumbnail)) {
+ move_file($thumbnail, get_media_firewall_path($thumbnail));
+ }
+ }
+ } else {
+ // Move single file and its corresponding thumbnail to standard dir
+ $filename = get_media_standard_path($filename);
+ if (file_exists(get_media_firewall_path($filename))) {
+ move_file(get_media_firewall_path($filename), $filename);
+ }
+ $thumbnail = thumbnail_file($filename, false);
+ if (file_exists(get_media_firewall_path($thumbnail))) {
+ move_file(get_media_firewall_path($thumbnail), $thumbnail);
+ }
+ }
+ }
+ }
+ }
+ echo "</td></tr></table>";
+ $action="filter";
+ closedir($dir);
+ }
+ return;
+}
+
+/**
+* Recursively sets the permissions on files
+* Operates directly on the filesystem, does not use the db.
+*/
+function set_perms($path) {
+ global $MEDIA_FIREWALL_ROOTDIR, $MEDIA_DIRECTORY, $starttime;
+ if (preg_match("'^($MEDIA_FIREWALL_ROOTDIR)?$MEDIA_DIRECTORY'", $path."/")==0) {
+ return false;
+ }
+ $timelimit=get_site_setting('MAX_EXECUTION_TIME');
+ if ($dir=@opendir($path)) {
+ while (($element=readdir($dir))!== false) {
+ $exectime = time() - $starttime;
+ if (($timelimit != 0) && ($timelimit - $exectime) < 3) {
+ // bail now to ensure nothing is lost
+ echo "<div class=\"error\">".i18n::translate('The execution time limit was reached. Try the command again on a smaller directory.')."</div>";
+ return;
+ }
+ // do not set perms on certain files...
+ if ($element!= "." && $element!= ".." && $element!=".svn") {
+ $fullpath = $path."/".$element;
+ if (is_dir($fullpath)) {
+ if (@chmod($fullpath, WT_PERM_EXE)) {
+ echo "<div>".i18n::translate('Permissions Set')." [".decoct(WT_PERM_EXE)."] [".$fullpath."]</div>";
+ } else {
+ echo "<div>".i18n::translate('Permissions Not Set')." [".decoct(WT_PERM_EXE)."] [".$fullpath."]</div>";
+ }
+ // call this function recursively on this directory
+ set_perms($fullpath);
+ } else {
+ if (@chmod($fullpath, WT_PERM_FILE)) {
+ echo "<div>".i18n::translate('Permissions Set')." [".decoct(WT_PERM_FILE)."] [".$fullpath."]</div>";
+ } else {
+ echo "<div>".i18n::translate('Permissions Not Set')." [".decoct(WT_PERM_FILE)."] [".$fullpath."]</div>";
+ }
+ }
+ }
+ }
+ closedir($dir);
+ }
+ return;
+}
+
+// global var used by recursive functions
+$starttime = time();
+
+// TODO Determine source and validation requirements for these variables
+$filename=safe_REQUEST($_REQUEST, 'filename');
+$directory=safe_REQUEST($_REQUEST, 'directory', WT_REGEX_NOSCRIPT, $MEDIA_DIRECTORY);
+$movetodir=safe_REQUEST($_REQUEST, 'movetodir');
+$movefile=safe_REQUEST($_REQUEST, 'movefile');
+$action=safe_REQUEST($_REQUEST, 'action', WT_REGEX_ALPHA, 'filter');
+$subclick=safe_REQUEST($_REQUEST, 'subclick', WT_REGEX_ALPHA, 'none');
+$media=safe_REQUEST($_REQUEST, 'media');
+$filter=safe_REQUEST($_REQUEST, 'filter', WT_REGEX_NOSCRIPT);
+$sortby=safe_REQUEST($_REQUEST, 'sortby', 'file', 'title');
+$level=safe_REQUEST($_REQUEST, 'level', WT_REGEX_INTEGER, 0);
+
+$showthumb=safe_REQUEST($_REQUEST, 'showthumb');
+
+$all=safe_REQUEST($_REQUEST, 'all', 'yes', 'no');
+
+if (isset($_REQUEST['xref'])) $xref = $_REQUEST['xref'];
+
+if (count($_POST) == 0) $showthumb = true;
+
+$thumbget = "";
+if ($showthumb) $thumbget = "&amp;showthumb=true";
+
+//-- prevent script from accessing an area outside of the media directory
+//-- and keep level consistency
+if (($level < 0) || ($level > $MEDIA_DIRECTORY_LEVELS)) {
+ $directory = $MEDIA_DIRECTORY;
+ $level = 0;
+} elseif (preg_match("'^$MEDIA_DIRECTORY'", $directory)==0) {
+ $directory = $MEDIA_DIRECTORY;
+ $level = 0;
+}
+
+$thumbdir = str_replace($MEDIA_DIRECTORY, $MEDIA_DIRECTORY."thumbs/", $directory);
+$directory_fw = get_media_firewall_path($directory);
+$thumbdir_fw = get_media_firewall_path($thumbdir);
+
+//-- only allow users with Admin privileges to access script.
+if (!WT_USER_IS_ADMIN || !$ALLOW_EDIT_GEDCOM) {
+ header('Location: '.WT_SERVER_NAME.WT_SCRIPT_PATH.'login.php?url='.WT_SCRIPT_NAME);
+ exit;
+}
+
+//-- TODO add check for -- admin can manipulate files
+$fileaccess = false;
+if (WT_USER_IS_ADMIN) {
+ $fileaccess = true;
+}
+
+// echo the header of the page
+print_header(i18n::translate('Manage multimedia'));
+?>
+<script language="JavaScript" type="text/javascript">
+<!--
+function pasteid(id) {
+ window.opener.paste_id(id);
+ window.close();
+}
+
+function ilinkitem(mediaid, type) {
+ window.open('inverselink.php?mediaid='+mediaid+'&linkto='+type+'&'+sessionname+'='+sessionid, '_blank', 'top=50, left=50, width=570, height=650, resizable=1, scrollbars=1');
+ return false;
+}
+
+function checknames(frm) {
+ if (document.managemedia.subclick) button = document.managemedia.subclick.value;
+ if (button == "all") {
+ frm.filter.value = "";
+ return true;
+ }
+ else if (frm.filter.value.length < 2) {
+ alert("<?php echo i18n::translate('Please enter more than one character'); ?>");
+ frm.filter.focus();
+ return false;
+ }
+ return true;
+}
+
+function checkpath(folder) {
+ value = folder.value;
+ if (value.substr(value.length-1, 1) == "/") value = value.substr(0, value.length-1);
+ if (value.substr(0, 1) == "/") value = value.substr(1, value.length-1);
+ result = value.split("/");
+ if (result.length > <?php echo $MEDIA_DIRECTORY_LEVELS; ?>) {
+ alert('<?php echo i18n::translate('You can enter no more than %s subdirectory names', $MEDIA_DIRECTORY_LEVELS); ?>');
+ folder.focus();
+ return false;
+ }
+}
+
+function showchanges() {
+ window.location = '<?php echo WT_SCRIPT_NAME."?show_changes=yes&directory=".$directory."&level=".$level."&filter=".$filter."&subclick=".$subclick; ?>';
+}
+
+//-->
+</script>
+<script src="js/webtrees.js" language="JavaScript" type="text/javascript"></script>
+<?php
+if (check_media_structure()) {
+ echo "<div id=\"uploadmedia\" style=\"display:none\">";
+ // Check if Media Directory is writeable or if Media features are enabled
+ // If one of these is not true then do not continue
+ if (!dir_is_writable($MEDIA_DIRECTORY) || !$MULTI_MEDIA) {
+ echo "<span class=\"error\"><b>";
+ echo i18n::translate('Uploading media files is not allowed because multi-media items have been disabled or because the media directory is not writable.');
+ echo "</b></span><br />";
+ } else {
+ show_mediaUpload_form(WT_SCRIPT_NAME, $showthumb); // We have the green light to upload media, echo the form
+ }
+ echo "</div><br />";
+
+ ob_start(); // Save output until action table has been printed
+
+ if ($action == "deletedir") {
+ echo "<table class=\"list_table width100\">";
+ echo "<tr><td class=\"messagebox\">";
+ // Check if media directory and thumbs directory are empty
+ $clean = false;
+ $files = array();
+ $thumbfiles = array();
+ $files_fw = array();
+ $thumbfiles_fw = array();
+ $resdir = false;
+ $resthumb = false;
+ // Media directory check
+ if (@is_dir(filename_decode($directory))) {
+ $handle = opendir(filename_decode($directory));
+ $files = array();
+ while (false !== ($file = readdir($handle))) {
+ if (!in_array($file, $BADMEDIA)) $files[] = $file;
+ }
+ } else {
+ echo "<div class=\"error\">".$directory." ".i18n::translate('Directory does not exist.')."</div>";
+ AddToLog('Directory does not exist.'.$directory, 'media');
+ }
+
+ // Thumbs directory check
+ if (@is_dir(filename_decode($thumbdir))) {
+ $handle = opendir(filename_decode($thumbdir));
+ $thumbfiles = array();
+ while (false !== ($file = readdir($handle))) {
+ if (!in_array($file, $BADMEDIA)) $thumbfiles[] = $file;
+ }
+ closedir($handle);
+ }
+
+ // Media Firewall Media directory check
+ if (@is_dir(filename_decode($directory_fw))) {
+ $handle = opendir(filename_decode($directory_fw));
+ $files_fw = array();
+ while (false !== ($file = readdir($handle))) {
+ if (!in_array($file, $BADMEDIA)) $files_fw[] = $file;
+ }
+ }
+
+ // Media Firewall Thumbs directory check
+ if (@is_dir(filename_decode($thumbdir_fw))) {
+ $handle = opendir(filename_decode($thumbdir_fw));
+ $thumbfiles_fw = array();
+ while (false !== ($file = readdir($handle))) {
+ if (!in_array($file, $BADMEDIA)) $thumbfiles_fw[] = $file;
+ }
+ closedir($handle);
+ }
+
+ if (!isset($error)) {
+ if (count($files) > 0 ) {
+ echo "<div class=\"error\">".$directory." -- ".i18n::translate('Directory not empty.')."</div>";
+ AddToLog($directory." -- ".i18n::translate('Directory not empty.'), 'media');
+ $clean = false;
+ }
+ if (count($thumbfiles) > 0) {
+ echo "<div class=\"error\">".$thumbdir." -- ".i18n::translate('Directory not empty.')."</div>";
+ AddToLog($thumbdir." -- ".i18n::translate('Directory not empty.'), 'media');
+ $clean = false;
+ }
+ if (count($files_fw) > 0 ) {
+ echo "<div class=\"error\">".$directory_fw." -- ".i18n::translate('Directory not empty.')."</div>";
+ AddToLog($directory_fw." -- ".i18n::translate('Directory not empty.'), 'media');
+ $clean = false;
+ }
+ if (count($thumbfiles_fw) > 0) {
+ echo "<div class=\"error\">".$thumbdir_fw." -- ".i18n::translate('Directory not empty.')."</div>";
+ AddToLog($thumbdir_fw." -- ".i18n::translate('Directory not empty.'), 'media');
+ $clean = false;
+ }
+ else $clean = true;
+ }
+
+ // Only start deleting if all directories are empty
+ if ($clean) {
+ $resdir = true;
+ $resthumb = true;
+ $resdir_fw = true;
+ $resthumb_fw = true;
+ if (file_exists(filename_decode($directory."index.php"))) @unlink(filename_decode($directory."index.php"));
+ if (@is_dir(filename_decode($directory))) $resdir = @rmdir(filename_decode(substr($directory, 0, -1)));
+ if (file_exists(filename_decode($thumbdir."index.php"))) @unlink(filename_decode($thumbdir."index.php"));
+ if (@is_dir(filename_decode($thumbdir))) $resthumb = @rmdir(filename_decode(substr($thumbdir, 0, -1)));
+ if (file_exists(filename_decode($directory_fw."index.php"))) @unlink(filename_decode($directory_fw."index.php"));
+ if (@is_dir(filename_decode($directory_fw))) $resdir_fw = @rmdir(filename_decode(substr($directory_fw, 0, -1)));
+ if (file_exists(filename_decode($thumbdir_fw."index.php"))) @unlink(filename_decode($thumbdir_fw."index.php"));
+ if (@is_dir(filename_decode($thumbdir_fw))) $resthumb_fw = @rmdir(filename_decode(substr($thumbdir_fw, 0, -1)));
+ if ($resdir && $resthumb && $resdir_fw && $resthumb_fw) {
+ echo i18n::translate('Media and thumbnail directories successfully removed.');
+ AddToLog($directory." -- ".i18n::translate('Media and thumbnail directories successfully removed.'), 'media');
+ } else {
+ if (!$resdir) {
+ echo "<div class=\"error\">".i18n::translate('Media directory not removed.')."</div>";
+ AddToLog($directory." -- ".i18n::translate('Media directory not removed.'), 'media');
+ } else if (!$resdir_fw) {
+ echo "<div class=\"error\">".i18n::translate('Media directory not removed.')."</div>";
+ AddToLog($directory_fw." -- ".i18n::translate('Media directory not removed.'), 'media');
+ } else {
+ echo i18n::translate('Media directory successfully removed.');
+ AddToLog($directory." -- ".i18n::translate('Media directory successfully removed.'), 'media');
+ }
+ if (!$resthumb) {
+ echo "<div class=\"error\">".i18n::translate('Thumbnail directory not removed.')."</div>";
+ AddToLog($thumbdir." -- ".i18n::translate('Thumbnail directory not removed.'), 'media');
+ } else if (!$resthumb_fw) {
+ echo "<div class=\"error\">".i18n::translate('Thumbnail directory not removed.')."</div>";
+ AddToLog($thumbdir_fw." -- ".i18n::translate('Thumbnail directory not removed.'), 'media');
+ } else {
+ echo i18n::translate('Thumbnail directory successfully removed.');
+ AddToLog($thumbdir." -- ".i18n::translate('Thumbnail directory successfully removed.'), 'media');
+ }
+
+ }
+ }
+
+ // Back up to this directory's parent
+ $i = strrpos(substr($directory, 0, -1), '/');
+ $directory = trim(substr($directory, 0, $i), '/').'/';
+ $action="filter";
+ echo "</td></tr></table>";
+ }
+/**
+ * This action generates a thumbnail for the file
+ *
+ * @name $action->thumbnail
+ */
+ if ($action == "thumbnail") {
+ echo "<table class=\"list_table $TEXT_DIRECTION width100\">";
+ echo "<tr><td class=\"messagebox wrap\">";
+ // TODO: add option to generate thumbnails for all images on page
+ // Cycle through $medialist and skip all exisiting thumbs
+
+ // Check if $all is true, if so generate thumbnails for all files that do
+ // not yet have any thumbnails created. Otherwise only the file specified.
+ if ($all == 'yes') {
+ $medialist = get_medialist(true, $directory);
+ foreach ($medialist as $key => $media) {
+ if (!($MEDIA_EXTERNAL && isFileExternal($filename))) {
+ // why doesn't this use thumbnail_file??
+ $thumbnail = str_replace("$MEDIA_DIRECTORY", $MEDIA_DIRECTORY."thumbs/", check_media_depth($media["FILE"], "NOTRUNC"));
+ if (!$media["THUMBEXISTS"]) {
+ if (generate_thumbnail($media["FILE"], $thumbnail)) {
+ echo i18n::translate('Thumbnail %s generated automatically.', $thumbnail);
+ AddToLog("Thumbnail {$thumbnail} generated automatically.", 'edit');
+ }
+ else {
+ echo "<span class=\"error\">";
+ echo i18n::translate('Thumbnail %s could not be generated automatically.', $thumbnail);
+ echo "</span>";
+ AddToLog("Thumbnail {$thumbnail} could not be generated automatically.", 'edit');
+ }
+ echo "<br />";
+ }
+ }
+ }
+ }
+ else if ($all != 'yes') {
+ if (!($MEDIA_EXTERNAL && isFileExternal($filename))) {
+ $thumbnail = str_replace("$MEDIA_DIRECTORY", $MEDIA_DIRECTORY."thumbs/", check_media_depth($filename, "NOTRUNC"));
+ if (generate_thumbnail($filename, $thumbnail)) {
+ echo i18n::translate('Thumbnail %s generated automatically.', $thumbnail);
+ AddToLog("Thumbnail {$thumbnail} generated automatically.", 'edit');
+ }
+ else {
+ echo "<span class=\"error\">";
+ echo i18n::translate('Thumbnail %s could not be generated automatically.', $thumbnail);
+ echo "</span>";
+ AddToLog("Thumbnail {$thumbnail} could not be generated automatically.", 'edit');
+ }
+ }
+ }
+ $action = "filter";
+ echo "</td></tr></table>";
+ }
+
+ // Move single file and optionally its corresponding thumbnail to protected dir
+ if ($action == "moveprotected") {
+ echo "<table class=\"list_table $TEXT_DIRECTION width100\">";
+ echo "<tr><td class=\"messagebox wrap\">";
+ if (strpos($filename, "../") !== false) {
+ // don't allow user to access directories outside of media dir
+ echo "<div class=\"error\">".i18n::translate('Blank name or illegal characters in name')."</div>";
+ } else {
+ if (file_exists($filename)) {
+ move_file($filename, get_media_firewall_path($filename));
+ }
+ if ($MEDIA_FIREWALL_THUMBS) {
+ $thumbnail = thumbnail_file($filename, false);
+ if (file_exists($thumbnail)) {
+ move_file($thumbnail, get_media_firewall_path($thumbnail));
+ }
+ }
+ }
+ echo "</td></tr></table>";
+ $action="filter";
+ }
+
+ // Move single file and its corresponding thumbnail to standard dir
+ if ($action == "movestandard") {
+ echo "<table class=\"list_table $TEXT_DIRECTION width100\">";
+ echo "<tr><td class=\"messagebox wrap\">";
+ if (strpos($filename, "../") !== false) {
+ // don't allow user to access directories outside of media dir
+ echo "<div class=\"error\">".i18n::translate('Blank name or illegal characters in name')."</div>";
+ } else {
+ if (file_exists(get_media_firewall_path($filename))) {
+ move_file(get_media_firewall_path($filename), $filename);
+ }
+ $thumbnail = thumbnail_file($filename, false);
+ if (file_exists(get_media_firewall_path($thumbnail))) {
+ move_file(get_media_firewall_path($thumbnail), $thumbnail);
+ }
+ }
+ echo "</td></tr></table>";
+ $action="filter";
+ }
+
+ // Move entire dir and all subdirs to protected dir
+ if ($action == "movedirprotected") {
+ echo "<table class=\"list_table $TEXT_DIRECTION width100\">";
+ echo "<tr><td class=\"messagebox wrap\">";
+ echo "<strong>".i18n::translate('Move to protected')."<br />";
+ move_files(substr($directory, 0, -1), true);
+ echo "</td></tr></table>";
+ $action="filter";
+ }
+
+ // Move entire dir and all subdirs to standard dir
+ if ($action == "movedirstandard") {
+ echo "<table class=\"list_table $TEXT_DIRECTION width100\">";
+ echo "<tr><td class=\"messagebox wrap\">";
+ echo "<strong>".i18n::translate('Move to standard')."<br />";
+ move_files(substr(get_media_firewall_path($directory), 0, -1), false);
+ echo "</td></tr></table>";
+ $action="filter";
+ }
+
+ if ($action == "setpermsfix") {
+ echo "<table class=\"list_table $TEXT_DIRECTION width100\">";
+ echo "<tr><td class=\"messagebox wrap\">";
+ echo "<strong>".i18n::translate('Correct read/write/execute permissions')."<br />";
+ set_perms(substr($directory, 0, -1));
+ set_perms(substr(get_media_firewall_path($directory), 0, -1));
+ echo "</td></tr></table>";
+ $action="filter";
+ }
+
+ // Upload media items
+ if ($action == "upload") {
+ process_uploadMedia_form();
+ $medialist = get_medialist();
+ $action = "filter";
+ }
+
+ $allowDelete = true;
+ $removeObject = true;
+ // Remove object: same as Delete file, except file isn't deleted
+ if ($action == "removeobject") {
+ $action = "deletefile";
+ $allowDelete = false;
+ $removeObject = true;
+ }
+
+ // Remove link: same as Delete file, except file isn't deleted
+ if ($action == "removelinks") {
+ $action = "deletefile";
+ $allowDelete = false;
+ $removeObject = false;
+ }
+
+ // Delete file
+ if ($action == "deletefile") {
+ echo "<table class=\"list_table $TEXT_DIRECTION width100\">";
+ echo "<tr><td class=\"messagebox wrap\">";
+ $xrefs = array($xref);
+ $onegedcom = true;
+ //-- get all of the XREFS associated with this record
+ //-- and check if the file is used in multiple gedcoms
+ $myFile = str_replace($MEDIA_DIRECTORY, "", $filename);
+ //-- figure out how many levels are in this file
+ $mlevels = preg_split("~[/\\\]~", $filename);
+
+ $statement=WT_DB::prepare("SELECT * FROM `##media` WHERE m_file LIKE ?")->execute(array("%{$myFile}"));
+ while ($row=$statement->fetch(PDO::FETCH_ASSOC)) {
+ $rlevels = preg_split("~[/\\\]~", $row["m_file"]);
+ //-- make sure we only delete a file at the same level of directories
+ //-- see 1825257
+ $match = true;
+ $k=0;
+ $i=count($rlevels)-1;
+ $j=count($mlevels)-1;
+ while ($i>=0 && $j>=0) {
+ if ($rlevels[$i] != $mlevels[$j]) {
+ $match = false;
+ break;
+ }
+ $j--;
+ $i--;
+ $k++;
+ if ($k>$MEDIA_DIRECTORY_LEVELS) break;
+ }
+ if ($match) {
+ if ($row["m_gedfile"]!=WT_GED_ID) $onegedcom = false;
+ else $xrefs[] = $row["m_media"];
+ }
+ }
+ $statement->closeCursor();
+ $xrefs = array_unique($xrefs);
+
+ $finalResult = true;
+ if ($allowDelete) {
+ if (!$onegedcom) {
+ echo "<span class=\"error\">".i18n::translate('This file is linked to another genealogical database on this server. It cannot be deleted, moved, or renamed until these links have been removed.')."<br /><br /><b>".i18n::translate('Media file could not be deleted.')."</b></span><br />";
+ $finalResult = false;
+ }
+ if (isFileExternal($filename)) {
+ echo "<span class=\"error\">".i18n::translate('This media object does not exist as a file on this server. It cannot be deleted, moved, or renamed.')."<br /><br /><b>".i18n::translate('Media file could not be deleted.')."</b></span><br />";
+ $finalResult = false;
+ }
+ if ($finalResult) {
+ // Check if file exists. If so, delete it
+ $server_filename = get_server_filename($filename);
+ if (file_exists($server_filename) && $allowDelete) {
+ if (@unlink($server_filename)) {
+ echo i18n::translate('Media file successfully deleted.')."<br />";
+ AddToLog($server_filename." -- ".i18n::translate('Media file successfully deleted.'), 'edit');
+ } else {
+ $finalResult = false;
+ echo "<span class=\"error\">".i18n::translate('Media file could not be deleted.')."</span><br />";
+ AddToLog($server_filename." -- ".i18n::translate('Media file could not be deleted.'), 'edit');
+ }
+ }
+
+ // Check if thumbnail exists. If so, delete it.
+ $thumbnail = str_replace("$MEDIA_DIRECTORY", $MEDIA_DIRECTORY."thumbs/", $filename);
+ $server_thumbnail = get_server_filename($thumbnail);
+ if (file_exists($server_thumbnail) && $allowDelete) {
+ if (@unlink($server_thumbnail)) {
+ echo i18n::translate('Thumbnail file successfully deleted.')."<br />";
+ AddToLog($server_thumbnail." -- ".i18n::translate('Thumbnail file successfully deleted.'), 'edit');
+ } else {
+ $finalResult = false;
+ echo "<span class=\"error\">".i18n::translate('Thumbnail file could not be deleted.')."</span><br />";
+ AddToLog($server_thumbnail." -- ".i18n::translate('Thumbnail file could not be deleted.'), 'edit');
+ }
+ }
+ }
+ }
+
+ //-- loop through all of the found xrefs and delete any references to them
+ foreach ($xrefs as $ind=>$xref) {
+ // Remove references to media file from gedcom and database
+ // Check for XREF
+ if ($xref != "") {
+ $links = get_media_relations($xref);
+ foreach ($links as $pid=>$type) {
+ $gedrec = find_gedcom_record($pid, WT_GED_ID, true);
+ $gedrec = remove_subrecord($gedrec, "OBJE", $xref, -1);
+ replace_gedrec($pid, WT_GED_ID, $gedrec);
+ echo i18n::translate('Record %s successfully updated.', $pid), '<br />';
+ }
+
+ // Remove media object from gedcom
+ if (find_gedcom_record($xref, WT_GED_ID)) {
+ delete_gedrec($xref, WT_GED_ID);
+ echo i18n::translate('Record %s successfully removed from GEDCOM.', $xref), '<br />';
+ } else {
+ echo "<span class=\"error\">".i18n::translate('This media object does not exist as a file on this server. It cannot be deleted, moved, or renamed.')."</span><br />";
+ $finalResult = false;
+ }
+
+/* I've commented this out, as I have no idea what it is supposed to do. We've just deleted a
+ * file, so why are we creating a new media object for it???
+
+ // Record changes to the Media object
+ accept_all_changes($xref, WT_GED_ID);
+ $objerec = find_gedcom_record($xref, WT_GED_ID);
+
+ // Add the same file as a new object
+ if ($finalResult && !$removeObject && $objerec!="") {
+ $xref = get_new_xref("OBJE");
+ $objerec = preg_replace("/0 @.*@ OBJE/", "0 @".$xref."@ OBJE", $objerec);
+ if (append_gedrec($objerec, WT_GED_ID)) {
+ echo i18n::translate('Record %s successfully added to GEDCOM.', $xref);
+ } else {
+ $finalResult = false;
+ echo "<span class=\"error\">";
+ echo i18n::translate('Record %s could not be added to GEDCOM.', $xref);
+ echo "</span>";
+ }
+ echo "<br />";
+ }
+*/
+ }
+ }
+ if ($finalResult) echo i18n::translate('Update successful');
+ $action = "filter";
+ echo "</td></tr></table>";
+ }
+
+/**
+ * Generate link flyout menu
+ *
+ * @param string $mediaid
+ */
+ function print_link_menu($mediaid) {
+ global $TEXT_DIRECTION;
+
+ $classSuffix = "";
+ if ($TEXT_DIRECTION=="rtl") $classSuffix = "_rtl";
+
+ // main link displayed on page
+ $menu = new Menu();
+
+ // GEDFact assistant Add Media Links =======================
+ if (file_exists('modules/GEDFact_assistant/_MEDIA/media_1_ctrl.php')) {
+ $menu->addLabel(i18n::translate('Manage links'));
+ $menu->addOnclick("return ilinkitem('$mediaid', 'manage')");
+ $menu->addClass("", "", "submenu");
+ $menu->addFlyout("left");
+ // Do not echo submunu
+
+ } else {
+ $menu->addLabel(i18n::translate('Set link'));
+ $menu->addOnclick("return ilinkitem('$mediaid', 'person')");
+ $submenu = new Menu(i18n::translate('To Person'));
+ $submenu->addClass("submenuitem".$classSuffix, "submenuitem_hover".$classSuffix);
+ $submenu->addOnclick("return ilinkitem('$mediaid', 'person')");
+ $menu->addSubMenu($submenu);
+
+ $submenu = new Menu(i18n::translate('To Family'));
+ $submenu->addClass("submenuitem".$classSuffix, "submenuitem_hover".$classSuffix);
+ $submenu->addOnclick("return ilinkitem('$mediaid', 'family')");
+ $menu->addSubMenu($submenu);
+
+ $submenu = new Menu(i18n::translate('To Source'));
+ $submenu->addClass("submenuitem".$classSuffix, "submenuitem_hover".$classSuffix);
+ $submenu->addOnclick("return ilinkitem('$mediaid', 'source')");
+ $menu->addSubMenu($submenu);
+ }
+ echo $menu->getMenu();
+ }
+
+ $savedOutput = ob_get_clean();
+
+?>
+
+ <form name="managemedia" method="post" onsubmit="return checknames(this);" action="<?php echo WT_SCRIPT_NAME; ?>">
+ <input type="hidden" name="thumbdir" value="<?php echo $thumbdir; ?>" />
+ <input type="hidden" name="level" value="<?php echo $level; ?>" />
+ <input type="hidden" name="all" value="true" />
+ <input type="hidden" name="subclick" />
+ <table class="facts_table center width75 <?php echo $TEXT_DIRECTION; ?>">
+ <?php
+ if ($TEXT_DIRECTION=='ltr') $legendAlign = 'align="right"';
+ else $legendAlign = 'align="left"';
+ ?>
+
+ <!-- // NOTE: Row 1 left: Sort sequence -->
+ <tr><td class="descriptionbox wrap width25" <?php echo $legendAlign; ?>><?php echo i18n::translate('Sequence'), help_link('sortby'); ?></td>
+ <td class="optionbox wrap"><select name="sortby">
+ <option value="title" <?php if ($sortby=='title') echo "selected=\"selected\""; ?>><?php echo translate_fact('TITL'); ?></option>
+ <option value="file" <?php if ($sortby=='file') echo "selected=\"selected\""; ?>><?php echo translate_fact('FILE'); ?></option>
+ </select></td>
+
+ <!-- // NOTE: Row 1 right, Upload media files -->
+ <td class="descriptionbox wrap width25" <?php echo $legendAlign; ?>><?php echo i18n::translate('Upload media files'), help_link('upload_media'); ?></td>
+ <td class="optionbox wrap"><?php echo "<a href=\"#\" onclick=\"expand_layer('uploadmedia');\">".i18n::translate('Upload media files')."</a>"; ?></td></tr>
+
+ <!-- // NOTE: Row 2 left: Filter options -->
+ <tr><td class="descriptionbox wrap width25" <?php echo $legendAlign; ?>><?php echo i18n::translate('Filter'), help_link('simple_filter'); ?></td>
+ <td class="optionbox wrap">
+ <?php
+ // Directory pick list
+ if (empty($directory)) {
+ if (!empty($_SESSION['upload_folder'])) $directory = $_SESSION['upload_folder'];
+ else $directory = $MEDIA_DIRECTORY;
+ }
+ if ($MEDIA_DIRECTORY_LEVELS > 0) {
+ $folders = get_media_folders();
+ echo "<span dir=\"ltr\"><select name=\"directory\">";
+ foreach ($folders as $f) {
+ echo "<option value=\"".$f."\"";
+ if ($directory==$f) echo " selected=\"selected\"";
+ echo ">{$f}</option>";
+ }
+ echo "</select></span><br />";
+ } else echo "<input name=\"directory\" type=\"hidden\" value=\"ALL\" />";
+ // Text field for filter
+ ?>
+ <input type="text" name="filter" value="<?php if ($filter) echo $filter; ?>" /><br /><input type="submit" name="search" value="<?php echo i18n::translate('Filter'); ?>" onclick="this.form.subclick.value=this.name" />&nbsp;&nbsp;&nbsp;<input type="submit" name="all" value="<?php echo i18n::translate('Display all'); ?>" onclick="this.form.subclick.value=this.name" /></td>
+
+ <!-- // NOTE: Row 2 right: Add media -->
+ <td class="descriptionbox wrap width25" <?php echo $legendAlign; ?>><?php echo i18n::translate('Add media'), help_link('add_media'); ?></td>
+ <td class="optionbox wrap"><a href="javascript: <?php echo i18n::translate('Add media'); ?>" onclick="window.open('addmedia.php?action=showmediaform&linktoid=new', '_blank', 'top=50, left=50, width=600, height=500, resizable=1, scrollbars=1'); return false;"> <?php echo i18n::translate('Add a new media item'); ?></a></td></tr>
+
+ <!-- // NOTE: Row 3 left: Show thumbnails -->
+ <tr>
+ <td class="descriptionbox wrap width25" <?php echo $legendAlign; ?>>
+ <?php echo i18n::translate('Show thumbnails'), help_link('show_thumb'); ?>
+ </td>
+ <td class="optionbox wrap width25">
+ <input type="checkbox" name="showthumb" value="true" <?php if ($showthumb) echo "checked=\"checked\""; ?> onclick="submit();" />
+ </td>
+
+ <!-- // NOTE: Row 3 right: Generate missing thumbnails -->
+ <?php
+ $tempURL = WT_SCRIPT_NAME.'?';
+ if (!empty($filter)) $tempURL .= 'filter='.rawurlencode($filter).'&amp;';
+ if (!empty($subclick)) $tempURL .= "subclick={$subclick}&amp;";
+ $tempURL .= "action=thumbnail&amp;sortby={$sortby}&amp;all=yes&amp;level={$level}&amp;directory=".rawurlencode($directory).$thumbget;
+ ?>
+ <td class="descriptionbox wrap width25" <?php echo $legendAlign; ?>><?php echo i18n::translate('Missing thumbnails'), help_link('gen_missing_thumbs'); ?></td>
+ <td class="optionbox wrap"><a href="<?php echo $tempURL; ?>"><?php echo i18n::translate('Create missing thumbnails'); ?></a></td></tr>
+ </table>
+</form>
+<script type="text/javascript">
+//<![CDATA[
+jQuery(document).ready(function() {
+// Table pageing
+ jQuery("#media_table")
+ .tablesorter({
+ sortList: [[<?php if ($showthumb) echo '2'; else echo '1'; ?>,0]], widgets: ['zebra'],
+ headers: { 0: { sorter: false }}
+ })
+ .tablesorterPager({
+ container: jQuery("#pager"),
+ positionFixed: false,
+ size: 15
+ });
+});
+//]]>
+</script>
+<?php
+
+ if (!empty($savedOutput)) echo $savedOutput; // echo everything we have saved up
+
+ if ($action == "filter" && $subclick != "none") {
+ if (empty($directory)) $directory = $MEDIA_DIRECTORY;
+ // only check for externalLinks when dealing with the root folder
+ $showExternal = ($directory == $MEDIA_DIRECTORY) ? true : false;
+ $medialist=get_medialist(true, $directory, false, false, $showExternal);
+
+
+ // Get the list of media items
+/**
+ * This is the default action for the page
+ *
+ * Displays a list of dirs and files. Displaying only
+ * thumbnails as the images may be large and we do not want large delays
+ * while administering the file structure
+ *
+ * @name $action->filter
+ */
+ // Show link to previous folder
+ $levels = explode('/', $directory);
+ $pdir = '';
+ for ($i=0; $i<count($levels)-2; $i++) $pdir.=$levels[$i].'/';
+ if ($pdir != '') {
+ $uplink = "<a href=\"".WT_SCRIPT_NAME."?directory={$pdir}&amp;amp;sortby={$sortby}&amp;amp;level=".($level-1).$thumbget."\">";
+ if ($TEXT_DIRECTION=="rtl") $uplink .= getLRM();
+ $uplink .= $pdir;
+ if ($TEXT_DIRECTION=="rtl") $uplink .= getLRM();
+ $uplink .= "</a>";
+
+ $uplink2 = "<a href=\"".WT_SCRIPT_NAME."?directory={$pdir}&amp;sortby={$sortby}&amp;level=".($level-1).$thumbget."\"><img class=\"icon\" src=\"";
+ $uplink2 .= $WT_IMAGES["larrow"];
+ $uplink2 .= "\" alt=\"\" /></a>";
+ }
+ // Start of media directory table
+ echo "<table class=\"list_table width50 $TEXT_DIRECTION\">";
+ // Tell the user where he is
+ echo "<tr>";
+ echo "<td class=\"topbottombar\" colspan=\"2\">";
+ echo i18n::translate('Current directory');
+ echo "<br />";
+ if ($USE_MEDIA_FIREWALL) {
+ echo $MEDIA_FIREWALL_ROOTDIR;
+ }
+ echo PrintReady(substr($directory, 0, -1));
+ echo "<br />";
+
+ // Calculation to determine whether files are protected or not -------------------------
+ // Check if media directory and thumbs directory are empty
+ $clean = false;
+ $files = array();
+ $thumbfiles = array();
+ $files_fw = array();
+ $thumbfiles_fw = array();
+ $resdir = false;
+ $resthumb = false;
+ // Media directory check
+ if (@is_dir(filename_decode($directory))) {
+ $handle = opendir(filename_decode($directory));
+ $files = array();
+ while (false !== ($file = readdir($handle))) {
+ if (!in_array($file, $BADMEDIA)) $files[] = $file;
+ }
+ } else {
+ echo "<div class=\"error\">".$directory." ".i18n::translate('Directory does not exist.')."</div>";
+ AddToLog('Directory does not exist.'.$directory, 'media');
+ }
+ // Thumbs directory check
+ if (@is_dir(filename_decode($thumbdir))) {
+ $handle = opendir(filename_decode($thumbdir));
+ $thumbfiles = array();
+ while (false !== ($file = readdir($handle))) {
+ if (!in_array($file, $BADMEDIA)) $thumbfiles[] = $file;
+ }
+ closedir($handle);
+ }
+ // Media Firewall Media directory check
+ if (@is_dir(filename_decode($directory_fw))) {
+ $handle = opendir(filename_decode($directory_fw));
+ $files_fw = array();
+ while (false !== ($file = readdir($handle))) {
+ if (!in_array($file, $BADMEDIA)) $files_fw[] = $file;
+ }
+ }
+ // Media Firewall Thumbs directory check
+ if (@is_dir(filename_decode($thumbdir_fw))) {
+ $handle = opendir(filename_decode($thumbdir_fw));
+ $thumbfiles_fw = array();
+ while (false !== ($file = readdir($handle))) {
+ if (!in_array($file, $BADMEDIA)) $thumbfiles_fw[] = $file;
+ }
+ closedir($handle);
+ }
+ $protected_files = count($files_fw);
+ $standard_files = count($files);
+
+ echo "<br />";
+ echo "<form name=\"blah3\" action=\"".WT_SCRIPT_NAME."\" method=\"post\">";
+ echo "<input type=\"hidden\" name=\"directory\" value=\"".$directory."\" />";
+ echo "<input type=\"hidden\" name=\"level\" value=\"".($level)."\" />";
+ echo "<input type=\"hidden\" name=\"dir\" value=\"".$directory."\" />";
+ echo "<input type=\"hidden\" name=\"action\" value=\"\" />";
+ echo "<input type=\"hidden\" name=\"showthumb\" value=\"{$showthumb}\" />";
+ echo "<input type=\"hidden\" name=\"sortby\" value=\"{$sortby}\" />";
+
+ if ($USE_MEDIA_FIREWALL) {
+ if ($protected_files < $standard_files) {
+ echo '<div class="error">';
+ echo i18n::translate('The media Firewall is ENABLED but your media may still be located in the Standard Media Directory').'<br />';
+ echo i18n::translate('Choose either').'<br />';
+ echo i18n::translate('(a) Click the "Move ALL to Protected" button to move your media to the protected directory').'<br />';
+ echo i18n::translate('or').'<br />';
+ echo i18n::translate('(b) Disable The Media Firewall Directory in the GEDCOM configuration section').'<br /><br />';
+ echo '</div>';
+ }
+ echo "<input type=\"submit\" value=\"".i18n::translate('Move ALL to standard')."\" onclick=\"this.form.action.value='movedirstandard'; \" />";
+ echo "<input type=\"submit\" value=\"".i18n::translate('Move ALL to protected')."\" onclick=\"this.form.action.value='movedirprotected';\" />";
+ echo help_link('move_mediadirs');
+ echo "<br />";
+ }
+
+ if (!$USE_MEDIA_FIREWALL && is_dir($MEDIA_FIREWALL_ROOTDIR.$MEDIA_DIRECTORY)) {
+ if ($protected_files > $standard_files) {
+ echo '<div class="error">';
+ echo i18n::translate('The media Firewall is DISABLED but your media may still be located in the Protected Media Directory').'<br />';
+ echo i18n::translate('Choose either').'<br />';
+ echo i18n::translate('(a) Click the "Move ALL to Standard" button to move your media to the standard directory').'<br />';
+ echo i18n::translate('or').'<br />';
+ echo i18n::translate('(b) Re-enable The Media Firewall Directory in the GEDCOM configuration section').'<br /><br />';
+ echo '</div>';
+ echo "<input type=\"submit\" value=\"".i18n::translate('Move ALL to standard')."\" onclick=\"this.form.action.value='movedirstandard'; \" />";
+ echo "<input type=\"submit\" value=\"".i18n::translate('Move ALL to protected')."\" onclick=\"this.form.action.value='movedirprotected';\" />";
+ echo help_link('move_mediadirs');
+ echo "<br />";
+ }
+ }
+
+ echo "<input type=\"submit\" value=\"".i18n::translate('Correct read/write/execute permissions')."\" onclick=\"this.form.action.value='setpermsfix';\" />";
+ echo help_link('setperms');
+ echo "</form>";
+ echo "</td>";
+ echo "</tr>";
+
+ // display the directory list
+ if (count($dirs) || $pdir != '') {
+ sort($dirs);
+ if ($pdir != '') {
+ echo "<tr>";
+ echo "<td class=\"optionbox center width10\">";
+ echo $uplink2;
+ echo "</td>";
+ echo "<td class=\"descriptionbox $TEXT_DIRECTION\">";
+ echo $uplink;
+ echo "</td>";
+ echo "</tr>";
+ }
+
+ foreach ($dirs as $indexval => $dir) {
+ if ($dir{0}!=".") {
+ echo "<tr>";
+ echo "<td class=\"optionbox center width10\">";
+ // directory options
+ echo "<form name=\"blah\" action=\"".WT_SCRIPT_NAME."\" method=\"post\">";
+ echo "<input type=\"hidden\" name=\"directory\" value=\"".$directory.$dir."/\" />";
+ echo "<input type=\"hidden\" name=\"parentdir\" value=\"".$directory."\" />";
+ echo "<input type=\"hidden\" name=\"level\" value=\"".($level)."\" />";
+ echo "<input type=\"hidden\" name=\"dir\" value=\"".$dir."\" />";
+ echo "<input type=\"hidden\" name=\"action\" value=\"\" />";
+ echo "<input type=\"hidden\" name=\"showthumb\" value=\"{$showthumb}\" />";
+ echo "<input type=\"hidden\" name=\"sortby\" value=\"{$sortby}\" />";
+ echo "<input type=\"image\" src=\"".$WT_IMAGES["remove"]."\" alt=\"".i18n::translate('Delete')."\" onclick=\"this.form.action.value='deletedir';return confirm('".i18n::translate('Are you sure you want to delete this folder?')."');\" />";
+ if ($USE_MEDIA_FIREWALL) {
+ echo "<br /><input type=\"submit\" value=\"".i18n::translate('Move to standard')."\" onclick=\"this.form.level.value=(this.form.level.value*1)+1;this.form.action.value='movedirstandard';\" />";
+ echo "<br /><input type=\"submit\" value=\"".i18n::translate('Move to protected')."\" onclick=\"this.form.level.value=(this.form.level.value*1)+1;this.form.action.value='movedirprotected';\" />";
+ }
+
+ echo "</form>";
+ echo "</td>";
+ echo "<td class=\"descriptionbox $TEXT_DIRECTION\">";
+ echo "<a href=\"".WT_SCRIPT_NAME."?directory=".rawurlencode($directory.$dir)."/&amp;sortby={$sortby}&amp;level=".($level+1).$thumbget."\">";
+ if ($TEXT_DIRECTION=="rtl") echo getRLM();
+ echo $dir;
+ if ($TEXT_DIRECTION=="rtl") echo getRLM();
+ echo "</a>";
+ echo "</td>";
+ echo "</tr>";
+ }
+ }
+ }
+ echo "</table>";
+ echo "<br />";
+
+ // display the images
+ if (count($medialist) && ($subclick=='search' || $subclick=='all')) {
+ if (WT_USE_LIGHTBOX) {
+ // Get Lightbox config variables
+ require WT_ROOT.'modules/lightbox/lb_defaultconfig.php';
+ require WT_ROOT.'modules/lightbox/functions/lb_call_js.php';
+ }
+
+ // Sort the media list according to the user's wishes
+ $sortedMediaList = $medialist; // Default sort (by title) has already been done
+ if ($sortby=='file') uasort($sortedMediaList, 'filesort');
+
+ // Set up for two passes, the first showing URLs, the second normal files
+ ?>
+<div align="center">
+<form class="tablesorter" method="post" action="<?php echo WT_SCRIPT_NAME; ?>">
+ <table id="media_table" class="tablesorter" border="0" cellpadding="0" cellspacing="1">
+ <thead>
+ <tr>
+ <th><?php echo i18n::translate('Edit options'); ?></th>
+ <?php if ($showthumb) { ?>
+ <th><?php echo i18n::translate('Media'); ?></th>
+ <?php } ?>
+ <th><?php echo i18n::translate('Description'); ?></th>
+ </tr>
+ </thead>
+ <tbody>
+<?php
+ if ($directory==$MEDIA_DIRECTORY) {
+ $httpFilter = "http";
+ $passStart = 1;
+ } else {
+ $httpFilter = "";
+ $passStart = 2;
+ }
+ for ($passCount=$passStart; $passCount<3; $passCount++) {
+ $printDone = false;
+ foreach ($sortedMediaList as $indexval => $media) {
+ while (true) {
+ if (!filterMedia($media, $filter, $httpFilter)) break;
+ $isExternal = isFileExternal($media["FILE"]);
+ if ($passCount==1 && !$isExternal) break;
+ if ($passCount==2 && $isExternal) break;
+ $imgsize = findImageSize($media["FILE"]);
+ $imgwidth = $imgsize[0]+40;
+ $imgheight = $imgsize[1]+150;
+
+ $changeClass = "";
+ if ($media["CHANGE"]=="delete") $changeClass = "change_old";
+ if ($media["CHANGE"]=="replace") $changeClass = "change_new";
+ if ($media["CHANGE"]=="append") $changeClass = "change_new";
+
+ // Show column with file operations options
+ $printDone = true;
+ echo "<tr><td class=\"optionbox $changeClass $TEXT_DIRECTION width20\">";
+
+ if ($media["CHANGE"]!="delete") {
+ // Edit File
+ $tempURL = "addmedia.php?action=";
+ if ($media["XREF"] != "") {
+ $tempURL .= "editmedia&amp;pid={$media['XREF']}&amp;linktoid=";
+ if (!$media["LINKED"]) {
+ $tempURL .= "new";
+ } else {
+ foreach ($media["LINKS"] as $linkToID => $temp) break;
+ $tempURL .= $linkToID;
+ }
+ } else {
+ $tempURL .= 'showmediaform&amp;filename='.rawurlencode($media['FILE']).'&amp;linktoid=new';
+ }
+ echo "<a href=\"javascript:", i18n::translate('Edit'), "\" onclick=\"window.open('", $tempURL, "', '_blank', 'top=50, left=50, width=600, height=500, resizable=1, scrollbars=1'); return false;\">", i18n::translate('Edit'), "</a><br />";
+
+ // Edit Raw
+ if ($media["XREF"] != "") {
+ echo "<a href=\"javascript:".i18n::translate('Edit raw GEDCOM record')."\" onclick=\"return edit_raw('".$media['XREF']."');\">".i18n::translate('Edit raw GEDCOM record')."</a><br />";
+ }
+
+ // Delete File
+ // don't delete external files
+ // don't delete files linked to more than 1 object
+ $objectCount = 0;
+ if (!$isExternal) {
+ foreach ($medialist as $tempMedia) {
+ if ($media["EXISTS"] && $media["FILE"]==$tempMedia["FILE"]) $objectCount++;
+ }
+ unset($tempMedia);
+ }
+ if (!$isExternal && $objectCount<2) {
+ $tempURL = WT_SCRIPT_NAME.'?';
+ if (!empty($filter)) $tempURL.= "filter=".rawurlencode($filter)."&amp;";
+ $tempURL .= "action=deletefile&amp;showthumb={$showthumb}&amp;sortby={$sortby}&amp;filter={$filter}&amp;subclick={$subclick}&amp;filename=".rawurlencode($media['FILE'])."&amp;directory={$directory}&amp;level={$level}&amp;xref={$media['XREF']}&amp;gedfile={$media['GEDFILE']}";
+ echo "<a href=\"".$tempURL."\" onclick=\"return confirm('".i18n::translate('Are you sure you want to delete this file?')."');\">".i18n::translate('Delete file')."</a><br />";
+ }
+
+ // Remove Object
+ if (!empty($media["XREF"])) {
+ $tempURL = WT_SCRIPT_NAME.'?';
+ if (!empty($filter)) $tempURL .= "filter={$filter}&amp;";
+ $tempURL .= "action=removeobjectamp;&showthumb={$showthumb}amp;&sortby={$sortby}amp;&filter={$filter}amp;&subclick={$subclick}amp;&filename=".rawurlencode($media['FILE'])."amp;&directory={$directory}amp;&level={$level}amp;&xref={$media['XREF']}amp;&gedfile={$media['GEDFILE']}";
+ echo "<a href=\"".$tempURL."\" onclick=\"return confirm('".i18n::translate('Are you sure you want to remove this object from the database?')."');\">".i18n::translate('Remove object')."</a><br />";
+ }
+
+ // Remove links
+ if ($media["LINKED"]) {
+ $tempURL = WT_SCRIPT_NAME.'?';
+ if (!empty($filter)) $tempURL .= "filter={$filter}&";
+ $tempURL .= "action=removelinks&showthumb={$showthumb}&sortby={$sortby}&filter={$filter}&subclick={$subclick}&filename=".urlencode($media['FILE'])."&directory={$directory}&level={$level}&xref={$media['XREF']}&gedfile={$media['GEDFILE']}";
+ }
+
+ // Add or Remove Links
+ // Only add or remove links to media that is in the DB
+ if ($media["XREF"] != "") {
+ print_link_menu($media["XREF"]);
+ }
+
+
+ // Move image between standard and protected directories
+ if ($USE_MEDIA_FIREWALL && ($media["EXISTS"] > 1)) {
+ $tempURL = WT_SCRIPT_NAME.'?';
+ if ($media["EXISTS"] == 2) {
+ $tempURL .= "action=moveprotected";
+ $message=i18n::translate('Move to protected directory');
+ }
+ if ($media["EXISTS"] == 3) {
+ $tempURL .= "action=movestandard";
+ $message=i18n::translate('Move to standard directory');
+ }
+ $tempURL .= "&amp;showthumb={$showthumb}&amp;sortby={$sortby}&amp;filename=".rawurlencode($media['FILE'])."&amp;directory=".rawurlencode($directory)."&amp;level={$level}&amp;xref={$media['XREF']}&amp;gedfile=".rawurlencode($media["GEDFILE"]);
+ echo "<a href=\"".$tempURL."\">".$message."</a><br />";
+ }
+
+ // Generate thumbnail
+ if (!$isExternal && (empty($media["THUMB"]) || !$media["THUMBEXISTS"])) {
+ $ct = preg_match("/\.([^\.]+)$/", $media["FILE"], $match);
+ if ($ct>0) $ext = strtolower(trim($match[1]));
+ if ($ext=="jpg" || $ext=="jpeg" || $ext=="gif" || $ext=="png") {
+ $tempURL = WT_SCRIPT_NAME.'?';
+ if (!empty($filter)) $tempURL .= "filter={$filter}&amp;";
+ $tempURL .= "action=thumbnail&amp;all=no&amp;sortby={$sortby}&amp;level={$level}&amp;directory=".rawurlencode($directory)."&amp;filename=".rawurlencode($media["FILE"]).$thumbget;
+ echo "<a href=\"".$tempURL."\">".i18n::translate('Create thumbnail')."</a>";
+ }
+ }
+
+ }
+ // NOTE: Close column for file operations
+ echo "</td>";
+
+
+ $name = trim($media["TITL"]);
+ // Get media item Notes
+ $haystack = $media["GEDCOM"];
+ $needle = "1 NOTE";
+ $before = substr($haystack, 0, strpos($haystack, $needle));
+ $after = substr(strstr($haystack, $needle), strlen($needle));
+ $worked = str_replace("1 NOTE", "1 NOTE<br />", $after);
+ $final = $before.$needle.$worked;
+ $notes = PrintReady(htmlspecialchars(addslashes(print_fact_notes($final, 1, true, true))));
+
+ // Get info on how to handle this media file
+ $mediaInfo = mediaFileInfo($media["FILE"], $media["THUMB"], $media["XREF"], $name, $notes);
+
+ //-- Thumbnail field
+ if ($showthumb) {
+ echo "<td class=\"optionbox $changeClass $TEXT_DIRECTION width10\">";
+ // if Streetview object
+ if (strpos($media["FILE"], 'http://maps.google.')===0) {
+ echo '<iframe style="float:left; padding:5px;" width="264" height="176" frameborder="0" scrolling="no" marginheight="0" marginwidth="0" src="', $media["FILE"], '&amp;output=svembed"></iframe>';
+ } else {
+ echo '<center><a href="', $mediaInfo['url'], '">';
+ echo '<img src="', $mediaInfo['thumb'], '" align="middle" class="thumbnail" border="none"', $mediaInfo['width'];
+ echo ' alt="', $name, '" /></a></center>';
+ }
+ echo '</td>';
+ }
+
+ //-- name and size field
+ echo "<td class=\"optionbox $changeClass $TEXT_DIRECTION wrap\">";
+ if ($media["TITL"]!="" && begRTLText($media["TITL"]) && $TEXT_DIRECTION=="ltr") {
+ if (!empty($media["XREF"])) {
+ echo "(".$media["XREF"].")";
+ echo "&nbsp;&nbsp;&nbsp;";
+ }
+ if ($media["TITL"]!="") echo "<b>".PrintReady($media["TITL"])."</b><br />";
+ } else {
+ if ($media["TITL"]!="") echo "<b>".PrintReady($media["TITL"])."</b>&nbsp;&nbsp;&nbsp;";
+ if (!empty($media["XREF"])) {
+ if ($TEXT_DIRECTION=="rtl") echo getRLM();
+ echo "(".$media["XREF"].")";
+ if ($TEXT_DIRECTION=="rtl") echo getRLM();
+ echo "<br />";
+ }
+ }
+ if (!$isExternal && !$media["EXISTS"]) echo "<span dir=\"ltr\">".PrintReady($media["FILE"])."</span><br /><span class=\"error\">".i18n::translate('The filename entered does not exist.')."</span><br />";
+ else {
+ if (substr($mediaInfo['type'], 0, 4) == 'url_') $tempText = 'URL';
+ else $tempText = PrintReady($media["FILE"]);
+ if (!empty($media["XREF"])) {
+ echo '<a href="', 'mediaviewer.php?mid=', $media["XREF"], '"><span dir="ltr">', $tempText, '</span></a><br />';
+ } else {
+ echo '<span dir="ltr">', $tempText, '</span><br />';
+ }
+ }
+ if (substr($mediaInfo['type'], 0, 4) != 'url_' && !empty($imgsize[0])) {
+ echo "<sub>&nbsp;&nbsp;".i18n::translate('Image Dimensions')." -- ".$imgsize[0]."x".$imgsize[1]."</sub><br />";
+ }
+ print_fact_notes($media["GEDCOM"], 1);
+ print_fact_sources($media["GEDCOM"], 1);
+ if ($media["LINKED"]) {
+ PrintMediaLinks($media["LINKS"], "normal");
+ } else {
+ echo "<br />".i18n::translate('This media object is not linked to any GEDCOM record.');
+ }
+
+
+ if ($USE_MEDIA_FIREWALL) {
+ echo "<br /><br />";
+ if ($media["EXISTS"]) {
+ switch ($media["EXISTS"]) {
+ case 1:
+ echo i18n::translate('This media object is located on an external server');
+ break;
+ case 2:
+ echo i18n::translate('This media object is in the standard media directory');
+ break;
+ case 3:
+ echo i18n::translate('This media object is in the protected media directory');
+ break;
+ }
+ echo '<br />';
+ }
+ if ($media["THUMBEXISTS"]) {
+ switch ($media["EXISTS"]) {
+ case 1:
+ echo i18n::translate('This thumbnail is located on an external server');
+ break;
+ case 2:
+ echo i18n::translate('This thumbnail is in the standard media directory');
+ break;
+ case 3:
+ echo i18n::translate('This thumbnail is in the protected media directory');
+ break;
+ }
+ echo '<br />';
+ }
+ }
+
+ echo "</td></tr>";
+ break;
+ }
+ }
+ if ($passCount==1 && $printDone) echo "<tr><td class=\"optionbox\" colspan=\"3\">&nbsp;</td></tr>";
+ }
+ ?>
+ </tbody>
+ </table>
+ </form><br />
+ <div id="pager" class="pager">
+ <form>
+ <img src="<?php echo WT_THEME_DIR; ?>images/jquery/first.png" class="first"/>
+ <img src="<?php echo WT_THEME_DIR; ?>images/jquery/prev.png" class="prev"/>
+ <input type="text" class="pagedisplay"/>
+ <img src="<?php echo WT_THEME_DIR; ?>images/jquery/next.png" class="next"/>
+ <img src="<?php echo WT_THEME_DIR; ?>images/jquery/last.png" class="last"/>
+ <select class="pagesize">
+ <option value="10">10</option>
+ <option selected="selected" value="15">15</option>
+ <option value="30">30</option>
+ <option value="40">40</option>
+ <option value="50">50</option>
+ <option value="100">100</option>
+ </select>
+ </form>
+ </div> <?php
+ }
+ }
+ ?> </div> <?php
+} else {
+ echo i18n::translate('The media folder is corrupted.');
+}
+print_footer();
diff --git a/uploadmedia.php b/admin_media_upload.php
index 2760d0d67f..8b3fab72c7 100644
--- a/uploadmedia.php
+++ b/admin_media_upload.php
@@ -27,7 +27,7 @@
* @version $Id$
*/
-define('WT_SCRIPT_NAME', 'uploadmedia.php');
+define('WT_SCRIPT_NAME', 'admin_media_upload.php');
require './includes/session.php';
require_once WT_ROOT.'includes/functions/functions_mediadb.php';
@@ -84,6 +84,6 @@ if (!dir_is_writable($MEDIA_DIRECTORY) || !$MULTI_MEDIA) {
echo i18n::translate('Uploading media files is not allowed because multi-media items have been disabled or because the media directory is not writable.');
echo "</b></span><br />";
} else {
- show_mediaUpload_form('uploadmedia.php', false); // We have the green light to upload media, print the form
+ show_mediaUpload_form(WT_SCRIPT_NAME, false); // We have the green light to upload media, print the form
}
print_footer();
diff --git a/admin_module_blocks.php b/admin_module_blocks.php
new file mode 100644
index 0000000000..0ed6f94ea2
--- /dev/null
+++ b/admin_module_blocks.php
@@ -0,0 +1,176 @@
+<?php
+// Module Administration User Interface.
+//
+// webtrees: Web based Family History software
+// Copyright (C) 2010 webtrees development team.
+//
+// This program is free software; you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation; either version 2 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program; if not, write to the Free Software
+// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+//
+// @version $Id$
+
+define('WT_SCRIPT_NAME', 'admin_module_blocks.php');
+
+require 'includes/session.php';
+require WT_ROOT.'includes/functions/functions_edit.php';
+
+if (!WT_USER_GEDCOM_ADMIN) {
+ header('Location: '.WT_SERVER_NAME.WT_SCRIPT_PATH.'login.php?url='.WT_SCRIPT_NAME);
+ exit;
+}
+
+// Modules may have been added or updated to no longer provide a particular component
+$installed_modules=WT_Module::getInstalledModules();
+foreach ($installed_modules as $module_name=>$module) {
+ // New module
+ WT_DB::prepare("INSERT IGNORE INTO `##module` (module_name) VALUES (?)")->execute(array($module_name));
+
+ // Removed component
+ if (!$module instanceof WT_Module_Block) {
+ WT_DB::prepare(
+ "DELETE FROM `##module_privacy` WHERE module_name=? AND component='block'"
+ )->execute(array($module_name));
+ WT_DB::prepare(
+ "DELETE `##block_setting` FROM `##block_setting` JOIN `##block` USING (block_id) WHERE module_name=?"
+ )->execute(array($module_name));
+ WT_DB::prepare(
+ "DELETE FROM `##block` WHERE module_name=?"
+ )->execute(array($module_name));
+ }
+ if (!$module instanceof WT_Module_Chart) {
+ WT_DB::prepare(
+ "DELETE FROM `##module_privacy` WHERE module_name=? AND component='chart'"
+ )->execute(array($module_name));
+ }
+ if (!$module instanceof WT_Module_Menu) {
+ WT_DB::prepare(
+ "DELETE FROM `##module_privacy` WHERE module_name=? AND component='menu'"
+ )->execute(array($module_name));
+ WT_DB::prepare(
+ "UPDATE `##module` SET menu_order=NULL WHERE module_name=?"
+ )->execute(array($module_name));
+ }
+ if (!$module instanceof WT_Module_Report) {
+ WT_DB::prepare(
+ "DELETE FROM `##module_privacy` WHERE module_name=? AND component='report'"
+ )->execute(array($module_name));
+ }
+ if (!$module instanceof WT_Module_Sidebar) {
+ WT_DB::prepare(
+ "DELETE FROM `##module_privacy` WHERE module_name=? AND component='sidebar'"
+ )->execute(array($module_name));
+ WT_DB::prepare(
+ "UPDATE `##module` SET sidebar_order=NULL WHERE module_name=?"
+ )->execute(array($module_name));
+ }
+ if (!$module instanceof WT_Module_Tab) {
+ WT_DB::prepare(
+ "DELETE FROM `##module_privacy` WHERE module_name=? AND component='tab'"
+ )->execute(array($module_name));
+ WT_DB::prepare(
+ "UPDATE `##module` SET tab_order=NULL WHERE module_name=?"
+ )->execute(array($module_name));
+ }
+ if (!$module instanceof WT_Module_Theme) {
+ WT_DB::prepare(
+ "DELETE FROM `##module_privacy` WHERE module_name=? AND component='theme'"
+ )->execute(array($module_name));
+ }
+}
+
+// Delete config for modules that no longer exist
+$module_names=WT_DB::prepare("SELECT module_name FROM `##module`")->fetchOneColumn();
+foreach ($module_names as $module_name) {
+ if (!array_key_exists($module_name, $installed_modules)) {
+ WT_DB::prepare(
+ "DELETE FROM `##module_privacy` WHERE module_name=?"
+ )->execute(array($module_name));
+ WT_DB::prepare(
+ "DELETE `##block_setting` FROM `##block_setting` JOIN `##block` USING (block_id) WHERE module_name=?"
+ )->execute(array($module_name));
+ WT_DB::prepare(
+ "DELETE FROM `##block` WHERE module_name=?"
+ )->execute(array($module_name));
+ WT_DB::prepare(
+ "DELETE FROM `##module` WHERE module_name=?"
+ )->execute(array($module_name));
+ }
+}
+
+$action = safe_POST('action');
+
+if ($action=='update_mods') {
+ foreach (WT_Module::getInstalledModules() as $module) {
+ $module_name=$module->getName();
+ foreach (get_all_gedcoms() as $ged_id=>$ged_name) {
+ if ($module instanceof WT_Module_Block) {
+ $value = safe_POST("blockaccess-{$module_name}-{$ged_id}", WT_REGEX_INTEGER, $module->defaultAccessLevel());
+ WT_DB::prepare(
+ "REPLACE INTO `##module_privacy` (module_name, gedcom_id, component, access_level) VALUES (?, ?, 'block', ?)"
+ )->execute(array($module_name, $ged_id, $value));
+ }
+ }
+ }
+}
+
+print_header(i18n::translate('Module administration'));
+?>
+
+<div align="center">
+ <div id="tabs">
+ <form method="post" action="<?php echo WT_SCRIPT_NAME; ?>">
+ <input type="hidden" name="action" value="update_mods" />
+ <table id="blocks_table" class="list_table">
+ <thead>
+ <tr>
+ <th class="list_label"><?php echo i18n::translate('Block'); ?></th>
+ <th class="list_label"><?php echo i18n::translate('Access level'); ?></th>
+ </tr>
+ </thead>
+ <tbody>
+ <?php
+ $order = 1;
+ foreach (WT_Module::getInstalledBlocks() as $module) { ?>
+ <tr>
+ <td class="list_value"><?php echo $module->getTitle(); ?></td>
+ <td class="list_value_wrap">
+ <table>
+ <?php
+ foreach (get_all_gedcoms() as $ged_id=>$ged_name) {
+ $varname = 'blockaccess-'.$module->getName().'-'.$ged_id;
+ $access_level=WT_DB::prepare(
+ "SELECT access_level FROM `##module_privacy` WHERE gedcom_id=? AND module_name=? AND component='block'"
+ )->execute(array($ged_id, $module->getName()))->fetchOne();
+ if ($access_level===null) {
+ $access_level=$module->defaultAccessLevel();
+ }
+ echo '<tr><td>', htmlspecialchars($ged_name), '</td><td>';
+ echo edit_field_access_level($varname, $access_level);
+ }
+ ?>
+ </table>
+ </td>
+ </tr>
+ <?php
+ $order++;
+ }
+ ?>
+ </tbody>
+ </table>
+ <input type="submit" value="<?php echo i18n::translate('Save'); ?>" />
+ </form>
+ </div>
+</div>
+<?php
+print_footer();
diff --git a/admin_module_menus.php b/admin_module_menus.php
new file mode 100644
index 0000000000..d1a14beb8b
--- /dev/null
+++ b/admin_module_menus.php
@@ -0,0 +1,199 @@
+<?php
+// Module Administration User Interface.
+//
+// webtrees: Web based Family History software
+// Copyright (C) 2010 webtrees development team.
+//
+// This program is free software; you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation; either version 2 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program; if not, write to the Free Software
+// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+//
+// @version $Id$
+
+define('WT_SCRIPT_NAME', 'admin_module_menus.php');
+
+require 'includes/session.php';
+require WT_ROOT.'includes/functions/functions_edit.php';
+
+if (!WT_USER_GEDCOM_ADMIN) {
+ header('Location: '.WT_SERVER_NAME.WT_SCRIPT_PATH.'login.php?url='.WT_SCRIPT_NAME);
+ exit;
+}
+
+// Modules may have been added or updated to no longer provide a particular component
+$installed_modules=WT_Module::getInstalledModules();
+foreach ($installed_modules as $module_name=>$module) {
+ // New module
+ WT_DB::prepare("INSERT IGNORE INTO `##module` (module_name) VALUES (?)")->execute(array($module_name));
+
+ // Removed component
+ if (!$module instanceof WT_Module_Block) {
+ WT_DB::prepare(
+ "DELETE FROM `##module_privacy` WHERE module_name=? AND component='block'"
+ )->execute(array($module_name));
+ WT_DB::prepare(
+ "DELETE `##block_setting` FROM `##block_setting` JOIN `##block` USING (block_id) WHERE module_name=?"
+ )->execute(array($module_name));
+ WT_DB::prepare(
+ "DELETE FROM `##block` WHERE module_name=?"
+ )->execute(array($module_name));
+ }
+ if (!$module instanceof WT_Module_Chart) {
+ WT_DB::prepare(
+ "DELETE FROM `##module_privacy` WHERE module_name=? AND component='chart'"
+ )->execute(array($module_name));
+ }
+ if (!$module instanceof WT_Module_Menu) {
+ WT_DB::prepare(
+ "DELETE FROM `##module_privacy` WHERE module_name=? AND component='menu'"
+ )->execute(array($module_name));
+ WT_DB::prepare(
+ "UPDATE `##module` SET menu_order=NULL WHERE module_name=?"
+ )->execute(array($module_name));
+ }
+ if (!$module instanceof WT_Module_Report) {
+ WT_DB::prepare(
+ "DELETE FROM `##module_privacy` WHERE module_name=? AND component='report'"
+ )->execute(array($module_name));
+ }
+ if (!$module instanceof WT_Module_Sidebar) {
+ WT_DB::prepare(
+ "DELETE FROM `##module_privacy` WHERE module_name=? AND component='sidebar'"
+ )->execute(array($module_name));
+ WT_DB::prepare(
+ "UPDATE `##module` SET sidebar_order=NULL WHERE module_name=?"
+ )->execute(array($module_name));
+ }
+ if (!$module instanceof WT_Module_Tab) {
+ WT_DB::prepare(
+ "DELETE FROM `##module_privacy` WHERE module_name=? AND component='tab'"
+ )->execute(array($module_name));
+ WT_DB::prepare(
+ "UPDATE `##module` SET tab_order=NULL WHERE module_name=?"
+ )->execute(array($module_name));
+ }
+ if (!$module instanceof WT_Module_Theme) {
+ WT_DB::prepare(
+ "DELETE FROM `##module_privacy` WHERE module_name=? AND component='theme'"
+ )->execute(array($module_name));
+ }
+}
+
+// Delete config for modules that no longer exist
+$module_names=WT_DB::prepare("SELECT module_name FROM `##module`")->fetchOneColumn();
+foreach ($module_names as $module_name) {
+ if (!array_key_exists($module_name, $installed_modules)) {
+ WT_DB::prepare(
+ "DELETE FROM `##module_privacy` WHERE module_name=?"
+ )->execute(array($module_name));
+ WT_DB::prepare(
+ "DELETE `##block_setting` FROM `##block_setting` JOIN `##block` USING (block_id) WHERE module_name=?"
+ )->execute(array($module_name));
+ WT_DB::prepare(
+ "DELETE FROM `##block` WHERE module_name=?"
+ )->execute(array($module_name));
+ WT_DB::prepare(
+ "DELETE FROM `##module` WHERE module_name=?"
+ )->execute(array($module_name));
+ }
+}
+
+$action = safe_POST('action');
+
+if ($action=='update_mods') {
+ foreach (WT_Module::getInstalledModules() as $module) {
+ $module_name=$module->getName();
+ foreach (get_all_gedcoms() as $ged_id=>$ged_name) {
+ if ($module instanceof WT_Module_Menu) {
+ $value = safe_POST("menuaccess-{$module_name}-{$ged_id}", WT_REGEX_INTEGER, $module->defaultAccessLevel());
+ WT_DB::prepare(
+ "REPLACE INTO `##module_privacy` (module_name, gedcom_id, component, access_level) VALUES (?, ?, 'menu', ?)"
+ )->execute(array($module_name, $ged_id, $value));
+ $value = safe_POST('menuorder-'.$module_name);
+ WT_DB::prepare(
+ "UPDATE `##module` SET menu_order=? WHERE module_name=?"
+ )->execute(array($value, $module_name));
+ }
+ }
+ }
+}
+
+print_header(i18n::translate('Module administration'));
+
+echo WT_JS_START; ?>
+
+ jQuery(document).ready(function() {
+ jQuery("#menus_table").sortable({items: '.sortme', forceHelperSize: true, forcePlaceholderSize: true, opacity: 0.7, cursor: 'move', axis: 'y'});
+
+ //-- update the order numbers after drag-n-drop sorting is complete
+ jQuery('#menus_table').bind('sortupdate', function(event, ui) {
+ jQuery('#'+jQuery(this).attr('id')+' input').each(
+ function (index, value) {
+ value.value = index+1;
+ }
+ );
+ });
+ });
+
+<?php echo WT_JS_END; ?>
+
+<div align="center">
+ <div id="tabs">
+ <form method="post" action="<?php echo WT_SCRIPT_NAME; ?>">
+ <input type="hidden" name="action" value="update_mods" />
+ <table id="menus_table" class="list_table">
+ <thead>
+ <tr>
+ <th class="list_label"><?php echo i18n::translate('Menu'); ?></th>
+ <th class="list_label"><?php echo i18n::translate('Order'); ?></th>
+ <th class="list_label"><?php echo i18n::translate('Access level'); ?></th>
+ </tr>
+ </thead>
+ <tbody>
+ <?php
+ $order = 1;
+ foreach (WT_Module::getInstalledMenus() as $module) { ?>
+ <tr class="sortme">
+ <td class="list_value"><?php echo $module->getTitle(); ?></td>
+ <td class="list_value"><input type="text" size="5" value="<?php echo $order; ?>" name="menuorder-<?php echo $module->getName(); ?>" />
+ </td>
+ <td class="list_value_wrap">
+ <table>
+ <?php
+ foreach (get_all_gedcoms() as $ged_id=>$ged_name) {
+ $varname = 'menuaccess-'.$module->getName().'-'.$ged_id;
+ $access_level=WT_DB::prepare(
+ "SELECT access_level FROM `##module_privacy` WHERE gedcom_id=? AND module_name=? AND component='menu'"
+ )->execute(array($ged_id, $module->getName()))->fetchOne();
+ if ($access_level===null) {
+ $access_level=$module->defaultAccessLevel();
+ }
+ echo '<tr><td>', htmlspecialchars($ged_name), '</td><td>';
+ echo edit_field_access_level($varname, $access_level);
+ }
+ ?>
+ </table>
+ </td>
+ </tr>
+ <?php
+ $order++;
+ }
+ ?>
+ </tbody>
+ </table>
+ <input type="submit" value="<?php echo i18n::translate('Save'); ?>" />
+ </form>
+ </div>
+</div>
+<?php
+print_footer();
diff --git a/admin_module_reports.php b/admin_module_reports.php
new file mode 100644
index 0000000000..0ba9b751bf
--- /dev/null
+++ b/admin_module_reports.php
@@ -0,0 +1,176 @@
+<?php
+// Module Administration User Interface.
+//
+// webtrees: Web based Family History software
+// Copyright (C) 2010 webtrees development team.
+//
+// This program is free software; you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation; either version 2 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program; if not, write to the Free Software
+// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+//
+// @version $Id$
+
+define('WT_SCRIPT_NAME', 'admin_module_reports.php');
+
+require 'includes/session.php';
+require WT_ROOT.'includes/functions/functions_edit.php';
+
+if (!WT_USER_GEDCOM_ADMIN) {
+ header('Location: '.WT_SERVER_NAME.WT_SCRIPT_PATH.'login.php?url='.WT_SCRIPT_NAME);
+ exit;
+}
+
+// Modules may have been added or updated to no longer provide a particular component
+$installed_modules=WT_Module::getInstalledModules();
+foreach ($installed_modules as $module_name=>$module) {
+ // New module
+ WT_DB::prepare("INSERT IGNORE INTO `##module` (module_name) VALUES (?)")->execute(array($module_name));
+
+ // Removed component
+ if (!$module instanceof WT_Module_Block) {
+ WT_DB::prepare(
+ "DELETE FROM `##module_privacy` WHERE module_name=? AND component='block'"
+ )->execute(array($module_name));
+ WT_DB::prepare(
+ "DELETE `##block_setting` FROM `##block_setting` JOIN `##block` USING (block_id) WHERE module_name=?"
+ )->execute(array($module_name));
+ WT_DB::prepare(
+ "DELETE FROM `##block` WHERE module_name=?"
+ )->execute(array($module_name));
+ }
+ if (!$module instanceof WT_Module_Chart) {
+ WT_DB::prepare(
+ "DELETE FROM `##module_privacy` WHERE module_name=? AND component='chart'"
+ )->execute(array($module_name));
+ }
+ if (!$module instanceof WT_Module_Menu) {
+ WT_DB::prepare(
+ "DELETE FROM `##module_privacy` WHERE module_name=? AND component='menu'"
+ )->execute(array($module_name));
+ WT_DB::prepare(
+ "UPDATE `##module` SET menu_order=NULL WHERE module_name=?"
+ )->execute(array($module_name));
+ }
+ if (!$module instanceof WT_Module_Report) {
+ WT_DB::prepare(
+ "DELETE FROM `##module_privacy` WHERE module_name=? AND component='report'"
+ )->execute(array($module_name));
+ }
+ if (!$module instanceof WT_Module_Sidebar) {
+ WT_DB::prepare(
+ "DELETE FROM `##module_privacy` WHERE module_name=? AND component='sidebar'"
+ )->execute(array($module_name));
+ WT_DB::prepare(
+ "UPDATE `##module` SET sidebar_order=NULL WHERE module_name=?"
+ )->execute(array($module_name));
+ }
+ if (!$module instanceof WT_Module_Tab) {
+ WT_DB::prepare(
+ "DELETE FROM `##module_privacy` WHERE module_name=? AND component='tab'"
+ )->execute(array($module_name));
+ WT_DB::prepare(
+ "UPDATE `##module` SET tab_order=NULL WHERE module_name=?"
+ )->execute(array($module_name));
+ }
+ if (!$module instanceof WT_Module_Theme) {
+ WT_DB::prepare(
+ "DELETE FROM `##module_privacy` WHERE module_name=? AND component='theme'"
+ )->execute(array($module_name));
+ }
+}
+
+// Delete config for modules that no longer exist
+$module_names=WT_DB::prepare("SELECT module_name FROM `##module`")->fetchOneColumn();
+foreach ($module_names as $module_name) {
+ if (!array_key_exists($module_name, $installed_modules)) {
+ WT_DB::prepare(
+ "DELETE FROM `##module_privacy` WHERE module_name=?"
+ )->execute(array($module_name));
+ WT_DB::prepare(
+ "DELETE `##block_setting` FROM `##block_setting` JOIN `##block` USING (block_id) WHERE module_name=?"
+ )->execute(array($module_name));
+ WT_DB::prepare(
+ "DELETE FROM `##block` WHERE module_name=?"
+ )->execute(array($module_name));
+ WT_DB::prepare(
+ "DELETE FROM `##module` WHERE module_name=?"
+ )->execute(array($module_name));
+ }
+}
+
+$action = safe_POST('action');
+
+if ($action=='update_mods') {
+ foreach (WT_Module::getInstalledModules() as $module) {
+ $module_name=$module->getName();
+ foreach (get_all_gedcoms() as $ged_id=>$ged_name) {
+ if ($module instanceof WT_Module_Report) {
+ $value = safe_POST("reportaccess-{$module_name}-{$ged_id}", WT_REGEX_INTEGER, $module->defaultAccessLevel());
+ WT_DB::prepare(
+ "REPLACE INTO `##module_privacy` (module_name, gedcom_id, component, access_level) VALUES (?, ?, 'report', ?)"
+ )->execute(array($module_name, $ged_id, $value));
+ }
+ }
+ }
+}
+
+print_header(i18n::translate('Module administration'));
+?>
+
+<div align="center">
+ <div id="tabs">
+ <form method="post" action="<?php echo WT_SCRIPT_NAME; ?>">
+ <input type="hidden" name="action" value="update_mods" />
+ <table id="reports_table" class="list_table">
+ <thead>
+ <tr>
+ <th class="list_label"><?php echo i18n::translate('Report'); ?></th>
+ <th class="list_label"><?php echo i18n::translate('Access level'); ?></th>
+ </tr>
+ </thead>
+ <tbody>
+ <?php
+ $order = 1;
+ foreach (WT_Module::getInstalledReports() as $module) { ?>
+ <tr>
+ <td class="list_value"><?php echo $module->getTitle(); ?></td>
+ <td class="list_value_wrap">
+ <table>
+ <?php
+ foreach (get_all_gedcoms() as $ged_id=>$ged_name) {
+ $varname = 'reportaccess-'.$module->getName().'-'.$ged_id;
+ $access_level=WT_DB::prepare(
+ "SELECT access_level FROM `##module_privacy` WHERE gedcom_id=? AND module_name=? AND component='report'"
+ )->execute(array($ged_id, $module->getName()))->fetchOne();
+ if ($access_level===null) {
+ $access_level=$module->defaultAccessLevel();
+ }
+ echo '<tr><td>', htmlspecialchars($ged_name), '</td><td>';
+ echo edit_field_access_level($varname, $access_level);
+ }
+ ?>
+ </table>
+ </td>
+ </tr>
+ <?php
+ $order++;
+ }
+ ?>
+ </tbody>
+ </table>
+ <input type="submit" value="<?php echo i18n::translate('Save'); ?>" />
+ </form>
+ </div>
+</div>
+<?php
+print_footer();
diff --git a/admin_module_sidebar.php b/admin_module_sidebar.php
new file mode 100644
index 0000000000..9280e6a800
--- /dev/null
+++ b/admin_module_sidebar.php
@@ -0,0 +1,199 @@
+<?php
+// Module Administration User Interface.
+//
+// webtrees: Web based Family History software
+// Copyright (C) 2010 webtrees development team.
+//
+// This program is free software; you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation; either version 2 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program; if not, write to the Free Software
+// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+//
+// @version $Id$
+
+define('WT_SCRIPT_NAME', 'admin_module_sidebar.php');
+
+require 'includes/session.php';
+require WT_ROOT.'includes/functions/functions_edit.php';
+
+if (!WT_USER_GEDCOM_ADMIN) {
+ header('Location: '.WT_SERVER_NAME.WT_SCRIPT_PATH.'login.php?url='.WT_SCRIPT_NAME);
+ exit;
+}
+
+// Modules may have been added or updated to no longer provide a particular component
+$installed_modules=WT_Module::getInstalledModules();
+foreach ($installed_modules as $module_name=>$module) {
+ // New module
+ WT_DB::prepare("INSERT IGNORE INTO `##module` (module_name) VALUES (?)")->execute(array($module_name));
+
+ // Removed component
+ if (!$module instanceof WT_Module_Block) {
+ WT_DB::prepare(
+ "DELETE FROM `##module_privacy` WHERE module_name=? AND component='block'"
+ )->execute(array($module_name));
+ WT_DB::prepare(
+ "DELETE `##block_setting` FROM `##block_setting` JOIN `##block` USING (block_id) WHERE module_name=?"
+ )->execute(array($module_name));
+ WT_DB::prepare(
+ "DELETE FROM `##block` WHERE module_name=?"
+ )->execute(array($module_name));
+ }
+ if (!$module instanceof WT_Module_Chart) {
+ WT_DB::prepare(
+ "DELETE FROM `##module_privacy` WHERE module_name=? AND component='chart'"
+ )->execute(array($module_name));
+ }
+ if (!$module instanceof WT_Module_Menu) {
+ WT_DB::prepare(
+ "DELETE FROM `##module_privacy` WHERE module_name=? AND component='menu'"
+ )->execute(array($module_name));
+ WT_DB::prepare(
+ "UPDATE `##module` SET menu_order=NULL WHERE module_name=?"
+ )->execute(array($module_name));
+ }
+ if (!$module instanceof WT_Module_Report) {
+ WT_DB::prepare(
+ "DELETE FROM `##module_privacy` WHERE module_name=? AND component='report'"
+ )->execute(array($module_name));
+ }
+ if (!$module instanceof WT_Module_Sidebar) {
+ WT_DB::prepare(
+ "DELETE FROM `##module_privacy` WHERE module_name=? AND component='sidebar'"
+ )->execute(array($module_name));
+ WT_DB::prepare(
+ "UPDATE `##module` SET sidebar_order=NULL WHERE module_name=?"
+ )->execute(array($module_name));
+ }
+ if (!$module instanceof WT_Module_Tab) {
+ WT_DB::prepare(
+ "DELETE FROM `##module_privacy` WHERE module_name=? AND component='tab'"
+ )->execute(array($module_name));
+ WT_DB::prepare(
+ "UPDATE `##module` SET tab_order=NULL WHERE module_name=?"
+ )->execute(array($module_name));
+ }
+ if (!$module instanceof WT_Module_Theme) {
+ WT_DB::prepare(
+ "DELETE FROM `##module_privacy` WHERE module_name=? AND component='theme'"
+ )->execute(array($module_name));
+ }
+}
+
+// Delete config for modules that no longer exist
+$module_names=WT_DB::prepare("SELECT module_name FROM `##module`")->fetchOneColumn();
+foreach ($module_names as $module_name) {
+ if (!array_key_exists($module_name, $installed_modules)) {
+ WT_DB::prepare(
+ "DELETE FROM `##module_privacy` WHERE module_name=?"
+ )->execute(array($module_name));
+ WT_DB::prepare(
+ "DELETE `##block_setting` FROM `##block_setting` JOIN `##block` USING (block_id) WHERE module_name=?"
+ )->execute(array($module_name));
+ WT_DB::prepare(
+ "DELETE FROM `##block` WHERE module_name=?"
+ )->execute(array($module_name));
+ WT_DB::prepare(
+ "DELETE FROM `##module` WHERE module_name=?"
+ )->execute(array($module_name));
+ }
+}
+
+$action = safe_POST('action');
+
+if ($action=='update_mods') {
+ foreach (WT_Module::getInstalledModules() as $module) {
+ $module_name=$module->getName();
+ foreach (get_all_gedcoms() as $ged_id=>$ged_name) {
+ if ($module instanceof WT_Module_Sidebar) {
+ $value = safe_POST("sidebaraccess-{$module_name}-{$ged_id}", WT_REGEX_INTEGER, $module->defaultAccessLevel());
+ WT_DB::prepare(
+ "REPLACE INTO `##module_privacy` (module_name, gedcom_id, component, access_level) VALUES (?, ?, 'sidebar', ?)"
+ )->execute(array($module_name, $ged_id, $value));
+ $value = safe_POST('sidebarorder-'.$module_name);
+ WT_DB::prepare(
+ "UPDATE `##module` SET sidebar_order=? WHERE module_name=?"
+ )->execute(array($value, $module_name));
+ }
+ }
+ }
+}
+
+print_header(i18n::translate('Module administration'));
+
+echo WT_JS_START; ?>
+
+ jQuery(document).ready(function() {
+ jQuery("#sidebars_table").sortable({items: '.sortme', forceHelperSize: true, forcePlaceholderSize: true, opacity: 0.7, cursor: 'move', axis: 'y'});
+
+ //-- update the order numbers after drag-n-drop sorting is complete
+ jQuery('#sidebars_table').bind('sortupdate', function(event, ui) {
+ jQuery('#'+jQuery(this).attr('id')+' input').each(
+ function (index, value) {
+ value.value = index+1;
+ }
+ );
+ });
+ });
+
+<?php echo WT_JS_END; ?>
+
+<div align="center">
+ <div id="tabs">
+ <form method="post" action="<?php echo WT_SCRIPT_NAME; ?>">
+ <input type="hidden" name="action" value="update_mods" />
+ <table id="sidebars_table" class="list_table">
+ <thead>
+ <tr>
+ <th class="list_label"><?php echo i18n::translate('Sidebar'); ?></th>
+ <th class="list_label"><?php echo i18n::translate('Order'); ?></th>
+ <th class="list_label"><?php echo i18n::translate('Access level'); ?></th>
+ </tr>
+ </thead>
+ <tbody>
+ <?php
+ $order = 1;
+ foreach (WT_Module::getInstalledSidebars() as $module) { ?>
+ <tr class="sortme">
+ <td class="list_value"><?php echo $module->getTitle(); ?></td>
+ <td class="list_value"><input type="text" size="5" value="<?php echo $order; ?>" name="sidebarorder-<?php echo $module->getName(); ?>" />
+ </td>
+ <td class="list_value_wrap">
+ <table>
+ <?php
+ foreach (get_all_gedcoms() as $ged_id=>$ged_name) {
+ $varname = 'sidebaraccess-'.$module->getName().'-'.$ged_id;
+ $access_level=WT_DB::prepare(
+ "SELECT access_level FROM `##module_privacy` WHERE gedcom_id=? AND module_name=? AND component='sidebar'"
+ )->execute(array($ged_id, $module->getName()))->fetchOne();
+ if ($access_level===null) {
+ $access_level=$module->defaultAccessLevel();
+ }
+ echo '<tr><td>', htmlspecialchars($ged_name), '</td><td>';
+ echo edit_field_access_level($varname, $access_level);
+ }
+ ?>
+ </table>
+ </td>
+ </tr>
+ <?php
+ $order++;
+ }
+ ?>
+ </tbody>
+ </table>
+ <input type="submit" value="<?php echo i18n::translate('Save'); ?>" />
+ </form>
+ </div>
+</div>
+<?php
+print_footer();
diff --git a/admin_module_tabs.php b/admin_module_tabs.php
new file mode 100644
index 0000000000..ac50764e05
--- /dev/null
+++ b/admin_module_tabs.php
@@ -0,0 +1,199 @@
+<?php
+// Module Administration User Interface.
+//
+// webtrees: Web based Family History software
+// Copyright (C) 2010 webtrees development team.
+//
+// This program is free software; you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation; either version 2 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program; if not, write to the Free Software
+// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+//
+// @version $Id$
+
+define('WT_SCRIPT_NAME', 'admin_module_tabs.php');
+
+require 'includes/session.php';
+require WT_ROOT.'includes/functions/functions_edit.php';
+
+if (!WT_USER_GEDCOM_ADMIN) {
+ header('Location: '.WT_SERVER_NAME.WT_SCRIPT_PATH.'login.php?url='.WT_SCRIPT_NAME);
+ exit;
+}
+
+// Modules may have been added or updated to no longer provide a particular component
+$installed_modules=WT_Module::getInstalledModules();
+foreach ($installed_modules as $module_name=>$module) {
+ // New module
+ WT_DB::prepare("INSERT IGNORE INTO `##module` (module_name) VALUES (?)")->execute(array($module_name));
+
+ // Removed component
+ if (!$module instanceof WT_Module_Block) {
+ WT_DB::prepare(
+ "DELETE FROM `##module_privacy` WHERE module_name=? AND component='block'"
+ )->execute(array($module_name));
+ WT_DB::prepare(
+ "DELETE `##block_setting` FROM `##block_setting` JOIN `##block` USING (block_id) WHERE module_name=?"
+ )->execute(array($module_name));
+ WT_DB::prepare(
+ "DELETE FROM `##block` WHERE module_name=?"
+ )->execute(array($module_name));
+ }
+ if (!$module instanceof WT_Module_Chart) {
+ WT_DB::prepare(
+ "DELETE FROM `##module_privacy` WHERE module_name=? AND component='chart'"
+ )->execute(array($module_name));
+ }
+ if (!$module instanceof WT_Module_Menu) {
+ WT_DB::prepare(
+ "DELETE FROM `##module_privacy` WHERE module_name=? AND component='menu'"
+ )->execute(array($module_name));
+ WT_DB::prepare(
+ "UPDATE `##module` SET menu_order=NULL WHERE module_name=?"
+ )->execute(array($module_name));
+ }
+ if (!$module instanceof WT_Module_Report) {
+ WT_DB::prepare(
+ "DELETE FROM `##module_privacy` WHERE module_name=? AND component='report'"
+ )->execute(array($module_name));
+ }
+ if (!$module instanceof WT_Module_Sidebar) {
+ WT_DB::prepare(
+ "DELETE FROM `##module_privacy` WHERE module_name=? AND component='sidebar'"
+ )->execute(array($module_name));
+ WT_DB::prepare(
+ "UPDATE `##module` SET sidebar_order=NULL WHERE module_name=?"
+ )->execute(array($module_name));
+ }
+ if (!$module instanceof WT_Module_Tab) {
+ WT_DB::prepare(
+ "DELETE FROM `##module_privacy` WHERE module_name=? AND component='tab'"
+ )->execute(array($module_name));
+ WT_DB::prepare(
+ "UPDATE `##module` SET tab_order=NULL WHERE module_name=?"
+ )->execute(array($module_name));
+ }
+ if (!$module instanceof WT_Module_Theme) {
+ WT_DB::prepare(
+ "DELETE FROM `##module_privacy` WHERE module_name=? AND component='theme'"
+ )->execute(array($module_name));
+ }
+}
+
+// Delete config for modules that no longer exist
+$module_names=WT_DB::prepare("SELECT module_name FROM `##module`")->fetchOneColumn();
+foreach ($module_names as $module_name) {
+ if (!array_key_exists($module_name, $installed_modules)) {
+ WT_DB::prepare(
+ "DELETE FROM `##module_privacy` WHERE module_name=?"
+ )->execute(array($module_name));
+ WT_DB::prepare(
+ "DELETE `##block_setting` FROM `##block_setting` JOIN `##block` USING (block_id) WHERE module_name=?"
+ )->execute(array($module_name));
+ WT_DB::prepare(
+ "DELETE FROM `##block` WHERE module_name=?"
+ )->execute(array($module_name));
+ WT_DB::prepare(
+ "DELETE FROM `##module` WHERE module_name=?"
+ )->execute(array($module_name));
+ }
+}
+
+$action = safe_POST('action');
+
+if ($action=='update_mods') {
+ foreach (WT_Module::getInstalledModules() as $module) {
+ $module_name=$module->getName();
+ foreach (get_all_gedcoms() as $ged_id=>$ged_name) {
+ if ($module instanceof WT_Module_Tab) {
+ $value = safe_POST("tabaccess-{$module_name}-{$ged_id}", WT_REGEX_INTEGER, $module->defaultAccessLevel());
+ WT_DB::prepare(
+ "REPLACE INTO `##module_privacy` (module_name, gedcom_id, component, access_level) VALUES (?, ?, 'tab', ?)"
+ )->execute(array($module_name, $ged_id, $value));
+ $value = safe_POST('taborder-'.$module_name);
+ WT_DB::prepare(
+ "UPDATE `##module` SET tab_order=? WHERE module_name=?"
+ )->execute(array($value, $module_name));
+ }
+ }
+ }
+}
+
+print_header(i18n::translate('Module administration'));
+
+echo WT_JS_START; ?>
+
+ jQuery(document).ready(function() {
+ jQuery("#tabs_table").sortable({items: '.sortme', forceHelperSize: true, forcePlaceholderSize: true, opacity: 0.7, cursor: 'move', axis: 'y'});
+
+ //-- update the order numbers after drag-n-drop sorting is complete
+ jQuery('#tabs_table').bind('sortupdate', function(event, ui) {
+ jQuery('#'+jQuery(this).attr('id')+' input').each(
+ function (index, value) {
+ value.value = index+1;
+ }
+ );
+ });
+ });
+
+<?php echo WT_JS_END; ?>
+
+<div align="center">
+ <div id="tabs">
+ <form method="post" action="<?php echo WT_SCRIPT_NAME; ?>">
+ <input type="hidden" name="action" value="update_mods" />
+ <table id="tabs_table" class="list_table">
+ <thead>
+ <tr>
+ <th class="list_label"><?php echo i18n::translate('Tab'); ?></th>
+ <th class="list_label"><?php echo i18n::translate('Order'); ?></th>
+ <th class="list_label"><?php echo i18n::translate('Access level'); ?></th>
+ </tr>
+ </thead>
+ <tbody>
+ <?php
+ $order = 1;
+ foreach (WT_Module::getInstalledTabs() as $module) { ?>
+ <tr class="sortme">
+ <td class="list_value"><?php echo $module->getTitle(); ?></td>
+ <td class="list_value"><input type="text" size="5" value="<?php echo $order; ?>" name="taborder-<?php echo $module->getName(); ?>" />
+ </td>
+ <td class="list_value_wrap">
+ <table>
+ <?php
+ foreach (get_all_gedcoms() as $ged_id=>$ged_name) {
+ $varname = 'tabaccess-'.$module->getName().'-'.$ged_id;
+ $access_level=WT_DB::prepare(
+ "SELECT access_level FROM `##module_privacy` WHERE gedcom_id=? AND module_name=? AND component='tab'"
+ )->execute(array($ged_id, $module->getName()))->fetchOne();
+ if ($access_level===null) {
+ $access_level=$module->defaultAccessLevel();
+ }
+ echo '<tr><td>', htmlspecialchars($ged_name), '</td><td>';
+ echo edit_field_access_level($varname, $access_level);
+ }
+ ?>
+ </table>
+ </td>
+ </tr>
+ <?php
+ $order++;
+ }
+ ?>
+ </tbody>
+ </table>
+ <input type="submit" value="<?php echo i18n::translate('Save'); ?>" />
+ </form>
+ </div>
+</div>
+<?php
+print_footer();
diff --git a/admin_modules.php b/admin_modules.php
new file mode 100644
index 0000000000..e2c7f8191e
--- /dev/null
+++ b/admin_modules.php
@@ -0,0 +1,245 @@
+<?php
+// Module Administration User Interface.
+//
+// webtrees: Web based Family History software
+// Copyright (C) 2010 webtrees development team.
+//
+// This program is free software; you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation; either version 2 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program; if not, write to the Free Software
+// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+//
+// @version $Id$
+
+define('WT_SCRIPT_NAME', 'admin_modules.php');
+
+require 'includes/session.php';
+require WT_ROOT.'includes/functions/functions_edit.php';
+
+if (!WT_USER_GEDCOM_ADMIN) {
+ header('Location: '.WT_SERVER_NAME.WT_SCRIPT_PATH.'login.php?url='.WT_SCRIPT_NAME);
+ exit;
+}
+
+// Modules may have been added or updated to no longer provide a particular component
+$installed_modules=WT_Module::getInstalledModules();
+foreach ($installed_modules as $module_name=>$module) {
+ // New module
+ WT_DB::prepare("INSERT IGNORE INTO `##module` (module_name) VALUES (?)")->execute(array($module_name));
+
+ // Removed component
+ if (!$module instanceof WT_Module_Block) {
+ WT_DB::prepare(
+ "DELETE FROM `##module_privacy` WHERE module_name=? AND component='block'"
+ )->execute(array($module_name));
+ WT_DB::prepare(
+ "DELETE `##block_setting` FROM `##block_setting` JOIN `##block` USING (block_id) WHERE module_name=?"
+ )->execute(array($module_name));
+ WT_DB::prepare(
+ "DELETE FROM `##block` WHERE module_name=?"
+ )->execute(array($module_name));
+ }
+ if (!$module instanceof WT_Module_Chart) {
+ WT_DB::prepare(
+ "DELETE FROM `##module_privacy` WHERE module_name=? AND component='chart'"
+ )->execute(array($module_name));
+ }
+ if (!$module instanceof WT_Module_Menu) {
+ WT_DB::prepare(
+ "DELETE FROM `##module_privacy` WHERE module_name=? AND component='menu'"
+ )->execute(array($module_name));
+ WT_DB::prepare(
+ "UPDATE `##module` SET menu_order=NULL WHERE module_name=?"
+ )->execute(array($module_name));
+ }
+ if (!$module instanceof WT_Module_Report) {
+ WT_DB::prepare(
+ "DELETE FROM `##module_privacy` WHERE module_name=? AND component='report'"
+ )->execute(array($module_name));
+ }
+ if (!$module instanceof WT_Module_Sidebar) {
+ WT_DB::prepare(
+ "DELETE FROM `##module_privacy` WHERE module_name=? AND component='sidebar'"
+ )->execute(array($module_name));
+ WT_DB::prepare(
+ "UPDATE `##module` SET sidebar_order=NULL WHERE module_name=?"
+ )->execute(array($module_name));
+ }
+ if (!$module instanceof WT_Module_Tab) {
+ WT_DB::prepare(
+ "DELETE FROM `##module_privacy` WHERE module_name=? AND component='tab'"
+ )->execute(array($module_name));
+ WT_DB::prepare(
+ "UPDATE `##module` SET tab_order=NULL WHERE module_name=?"
+ )->execute(array($module_name));
+ }
+ if (!$module instanceof WT_Module_Theme) {
+ WT_DB::prepare(
+ "DELETE FROM `##module_privacy` WHERE module_name=? AND component='theme'"
+ )->execute(array($module_name));
+ }
+}
+
+// Delete config for modules that no longer exist
+$module_names=WT_DB::prepare("SELECT module_name FROM `##module`")->fetchOneColumn();
+foreach ($module_names as $module_name) {
+ if (!array_key_exists($module_name, $installed_modules)) {
+ WT_DB::prepare(
+ "DELETE FROM `##module_privacy` WHERE module_name=?"
+ )->execute(array($module_name));
+ WT_DB::prepare(
+ "DELETE `##block_setting` FROM `##block_setting` JOIN `##block` USING (block_id) WHERE module_name=?"
+ )->execute(array($module_name));
+ WT_DB::prepare(
+ "DELETE FROM `##block` WHERE module_name=?"
+ )->execute(array($module_name));
+ WT_DB::prepare(
+ "DELETE FROM `##module` WHERE module_name=?"
+ )->execute(array($module_name));
+ }
+}
+
+$action = safe_POST('action');
+
+if ($action=='update_mods') {
+ foreach (WT_Module::getInstalledModules() as $module) {
+ $module_name=$module->getName();
+ $status=safe_POST("status-{$module_name}");
+ if ($status!==null) {
+ WT_DB::prepare("UPDATE `##module` SET status=? WHERE module_name=?")->execute(array($status ? 'enabled' : 'disabled', $module_name));
+ }
+ }
+}
+
+print_header(i18n::translate('Module administration'));
+?>
+<style type="text/css">
+<!--
+.sortme {
+ cursor: move;
+}
+.sortme img {
+ cursor: pointer;
+}
+//-->
+</style>
+<script type="text/javascript">
+//<![CDATA[
+
+ function reindexMods(id) {
+ jQuery('#'+id+' input').each(
+ function (index, value) {
+ value.value = index+1;
+ });
+ }
+
+ jQuery(document).ready(function() {
+ //-- sortable menus and tabs tables
+ jQuery("#menus_table, #tabs_table, #sidebars_table").sortable({items: '.sortme', forceHelperSize: true, forcePlaceholderSize: true, opacity: 0.7, cursor: 'move', axis: 'y'});
+
+ //-- update the order numbers after drag-n-drop sorting is complete
+ jQuery('#menus_table').bind('sortupdate', function(event, ui) {
+ var id = jQuery(this).attr('id');
+ reindexMods(id);
+ });
+
+ jQuery('#tabs_table').bind('sortupdate', function(event, ui) {
+ var id = jQuery(this).attr('id');
+ reindexMods(id);
+ });
+
+ jQuery('#sidebars_table').bind('sortupdate', function(event, ui) {
+ var id = jQuery(this).attr('id');
+ reindexMods(id);
+ });
+
+ // Table sorting and pageing
+ jQuery("#installed_table")
+ .tablesorter({
+ sortList: [[2,0], [3,0]], widgets: ['zebra'],
+ headers: { 0: { sorter: false }}
+ })
+ .tablesorterPager({
+ container: jQuery("#pager"),
+ positionFixed: false,
+ size: 15
+ });
+
+});
+//]]>
+</script>
+
+<div align="center">
+ <div id="tabs">
+ <form method="post" action="<?php echo WT_SCRIPT_NAME; ?>">
+ <input type="hidden" name="action" value="update_mods" />
+ <table id="installed_table" class="tablesorter" border="0" cellpadding="0" cellspacing="1">
+ <thead>
+ <tr>
+ <th><?php echo i18n::translate('Enabled'); ?></th>
+ <th><?php echo i18n::translate('Module Name'); ?></th>
+ <th><?php echo i18n::translate('Description'); ?></th>
+ <th><?php echo i18n::translate('Menu'); ?></th>
+ <th><?php echo i18n::translate('Tab'); ?></th>
+ <th><?php echo i18n::translate('Sidebar'); ?></th>
+ <th><?php echo i18n::translate('Block'); ?></th>
+ <th><?php echo i18n::translate('Chart'); ?></th>
+ <th><?php echo i18n::translate('Report'); ?></th>
+ <th><?php echo i18n::translate('Theme'); ?></th>
+ </tr>
+ </thead>
+ <tbody>
+ <?php
+ foreach (WT_Module::getInstalledModules() as $module) {
+ $status=WT_DB::prepare(
+ "SELECT status FROM `##module` WHERE module_name=?"
+ )->execute(array($module->getName()))->fetchOne();
+ echo '<tr><td>', two_state_checkbox('status-'.$module->getName(), $status=='enabled'), '</td>';
+ ?>
+ <td><?php echo $module->getTitle(); ?></td>
+ <td><?php echo $module->getDescription(); ?></td>
+ <td><?php if ($module instanceof WT_Module_Menu) echo i18n::translate('Yes'); else echo i18n::translate('No'); ?></td>
+ <td><?php if ($module instanceof WT_Module_Tab) echo i18n::translate('Yes'); else echo i18n::translate('No'); ?></td>
+ <td><?php if ($module instanceof WT_Module_Sidebar) echo i18n::translate('Yes'); else echo i18n::translate('No'); ?></td>
+ <td><?php if ($module instanceof WT_Module_Block) echo i18n::translate('Yes'); else echo i18n::translate('No'); ?></td>
+ <td><?php if ($module instanceof WT_Module_Chart) echo i18n::translate('Yes'); else echo i18n::translate('No'); ?></td>
+ <td><?php if ($module instanceof WT_Module_Report) echo i18n::translate('Yes'); else echo i18n::translate('No'); ?></td>
+ <td><?php if ($module instanceof WT_Module_Theme) echo i18n::translate('Yes'); else echo i18n::translate('No'); ?></td>
+ </tr>
+ <?php
+ }
+ ?>
+ </tbody>
+ </table>
+ <div id="pager" class="pager">
+ <!--<form>-->
+ <img src="<?php echo WT_THEME_DIR; ?>images/jquery/first.png" class="first"/>
+ <img src="<?php echo WT_THEME_DIR; ?>images/jquery/prev.png" class="prev"/>
+ <input type="text" class="pagedisplay"/>
+ <img src="<?php echo WT_THEME_DIR; ?>images/jquery/next.png" class="next"/>
+ <img src="<?php echo WT_THEME_DIR; ?>images/jquery/last.png" class="last"/>
+ <select class="pagesize">
+ <option value="10">10</option>
+ <option selected="selected" value="15">15</option>
+ <option value="30">30</option>
+ <option value="40">40</option>
+ <option value="50">50</option>
+ <option value="100">100</option>
+ </select>
+ <!--</form>-->
+ </div>
+ <input type="submit" value="<?php echo i18n::translate('Save'); ?>" />
+ </form>
+ </div>
+</div>
+<?php
+print_footer();
diff --git a/admin_site_config.php b/admin_site_config.php
new file mode 100644
index 0000000000..a8d9225cd7
--- /dev/null
+++ b/admin_site_config.php
@@ -0,0 +1,114 @@
+<?php
+/**
+ * A form to edit site configuration.
+ *
+ * webtrees: Web based Family History software
+ * Copyright (C) 2010 webtrees development team.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ * @package webtrees
+ * @subpackage Admin
+ * @version $Id$
+ */
+
+define('WT_SCRIPT_NAME', 'admin_site_config.php');
+
+require './includes/session.php';
+require WT_ROOT.'includes/functions/functions_edit.php';
+
+// Only admin users can access this page
+if (!WT_USER_IS_ADMIN) {
+ header('Location: '.WT_SERVER_NAME.WT_SCRIPT_PATH.'login.php?url='.WT_SCRIPT_NAME);
+ exit;
+}
+
+print_header(i18n::translate('Server configuration'));
+
+echo
+ // Display the config items inline, rather than using a form.
+ '<table class="site_config"><tr>',
+ '<th>', i18n::translate('Server configuration'), '</th><th>&nbsp;</th>',
+ '</tr><tr>',
+ '<td>', i18n::translate('Data file directory'), help_link('INDEX_DIRECTORY'), '</td>',
+ '<td>', edit_field_inline('site_setting-INDEX_DIRECTORY', get_site_setting('INDEX_DIRECTORY')), '</td>',
+ '</tr><tr>',
+ '<td>', i18n::translate('Memory limit'), help_link('MEMORY_LIMIT'), '</td>',
+ '<td>', edit_field_inline('site_setting-MEMORY_LIMIT', get_site_setting('MEMORY_LIMIT')), '</td>',
+ '</tr><tr>',
+ '<td>', i18n::translate('PHP time limit'), help_link('MAX_EXECUTION_TIME'), '</td>',
+ '<td>', edit_field_inline('site_setting-MAX_EXECUTION_TIME', get_site_setting('MAX_EXECUTION_TIME')), '</td>',
+ '</tr><tr>',
+ '<td>', i18n::translate('Allow messages to be stored online'), help_link('STORE_MESSAGES'), '</td>',
+ '<td>', edit_field_yes_no_inline('site_setting-STORE_MESSAGES', get_site_setting('STORE_MESSAGES')), '</td>',
+ '</tr><tr>',
+ '<td>', i18n::translate('Allow visitors to request account registration'), help_link('USE_REGISTRATION_MODULE'), '</td>',
+ '<td>', edit_field_yes_no_inline('site_setting-USE_REGISTRATION_MODULE', get_site_setting('USE_REGISTRATION_MODULE')), '</td>',
+ '</tr><tr>',
+ '<td>', i18n::translate('Require an administrator to approve new user registrations'), help_link('REQUIRE_ADMIN_AUTH_REGISTRATION'), '</td>',
+ '<td>', edit_field_yes_no_inline('site_setting-REQUIRE_ADMIN_AUTH_REGISTRATION', get_site_setting('REQUIRE_ADMIN_AUTH_REGISTRATION')), '</td>',
+ '</tr><tr>',
+ '<td>', i18n::translate('Allow users to select their own theme'), help_link('ALLOW_USER_THEMES'), '</td>',
+ '<td>', edit_field_yes_no_inline('site_setting-ALLOW_USER_THEMES', get_site_setting('ALLOW_USER_THEMES')), '</td>',
+ '</tr><tr>',
+ '<td>', i18n::translate('Default Theme'), help_link('THEME'), '</td>',
+ '<td>', select_edit_control_inline('site_setting-THEME', array_flip(get_theme_names()), null, get_site_setting('THEME')),
+ '</tr><tr>',
+ '<td>', i18n::translate('Allow GEDCOM switching'), help_link('ALLOW_CHANGE_GEDCOM'), '</td>',
+ '<td>', edit_field_yes_no_inline('site_setting-ALLOW_CHANGE_GEDCOM', get_site_setting('ALLOW_CHANGE_GEDCOM')), '</td>',
+ '</tr><tr>',
+ '<td>', i18n::translate('Session timeout'), help_link('SESSION_TIME'), '</td>',
+ '<td>', edit_field_inline('site_setting-SESSION_TIME', get_site_setting('SESSION_TIME')), '</td>',
+ '</tr><tr>',
+ '<td>', i18n::translate('Website URL'), help_link('SERVER_URL'), '</td>',
+ '<td>', edit_field_inline('site_setting-SERVER_URL', get_site_setting('SERVER_URL')), '</td>',
+ '</tr><tr>',
+ '<td>', i18n::translate('Login URL'), help_link('LOGIN_URL'), '</td>',
+ '<td>', edit_field_inline('site_setting-LOGIN_URL', get_site_setting('LOGIN_URL')), '</td>',
+ '</tr><tr>',
+ '<th>', i18n::translate('SMTP mail configuration'), '</th><th>&nbsp;</th>',
+ '</tr><tr>',
+ '<td>', i18n::translate('Messages'), help_link('SMTP_ACTIVE'), '</td>',
+ '<td>', select_edit_control_inline('site_setting-SMTP_ACTIVE', array('internal'=>i18n::translate('Use PHP mail to send messages'), 'external'=>i18n::translate('Use SMTP to send messages'), 'disabled'=>i18n::translate('Do not send messages')), null, get_site_setting('SMTP_ACTIVE')), '</td>',
+ '</tr><tr>',
+ '<td>', i18n::translate('Server'), help_link('SMTP_HOST'), '</td>',
+ '<td>', edit_field_inline('site_setting-SMTP_HOST', get_site_setting('SMTP_HOST')), '</td>',
+ '</tr><tr>',
+ '<td>', i18n::translate('Port'), help_link('SMTP_PORT'), '</td>',
+ '<td>', edit_field_inline('site_setting-SMTP_PORT', get_site_setting('SMTP_PORT')), '</td>',
+ '</tr><tr>',
+ '<td>', i18n::translate('Use simple mail headers'), help_link('SMTP_SIMPLE_MAIL'), '</td>',
+ '<td>', edit_field_yes_no_inline('site_setting-SMTP_SIMPLE_MAIL', get_site_setting('SMTP_SIMPLE_MAIL')), '</td>',
+ '</tr><tr>',
+ '<td>', i18n::translate('Use password'), help_link('SMTP_AUTH'), '</td>',
+ '<td>', edit_field_yes_no_inline('site_setting-SMTP_AUTH', get_site_setting('SMTP_AUTH')), '</td>',
+ '</tr><tr>',
+ '<td>', i18n::translate('Username'), help_link('SMTP_AUTH_USER'), '</td>',
+ '<td>', edit_field_inline('site_setting-SMTP_AUTH_USER', get_site_setting('SMTP_AUTH_USER')), '</td>',
+ '</tr><tr>',
+ '<td>', i18n::translate('Password'), help_link('SMTP_AUTH_PASS'), '</td>',
+ '<td>', edit_field_inline('site_setting-SMTP_AUTH_PASS', get_site_setting('SMTP_AUTH_PASS')), '</td>',
+ '</tr><tr>',
+ '<td>', i18n::translate('Security'), help_link('SMTP_SSL'), '</td>',
+ '<td>', select_edit_control_inline('site_setting-SMTP_SSL', array('none'=>i18n::translate('none'), 'ssl'=>i18n::translate('ssl'), 'tls'=>i18n::translate('tls')), null, get_site_setting('SMTP_SSL')), '</td>',
+ '</tr><tr>',
+ '<td>', i18n::translate('From email address'), help_link('SMTP_FROM_NAME'), '</td>',
+ '<td>', edit_field_inline('site_setting-SMTP_FROM_NAME', get_site_setting('SMTP_FROM_NAME')), '</td>',
+ '</tr><tr>',
+ '<td>', i18n::translate('Sender email address'), help_link('SMTP_HELO'), '</td>',
+ '<td>', edit_field_inline('site_setting-SMTP_HELO', get_site_setting('SMTP_HELO')), '</td>',
+ '</tr></table>';
+
+print_footer();
diff --git a/admin_site_info.php b/admin_site_info.php
new file mode 100644
index 0000000000..eaf08812f0
--- /dev/null
+++ b/admin_site_info.php
@@ -0,0 +1,81 @@
+<?php
+/**
+ * Displays information on the PHP installation
+ *
+ * Provides links for administrators to get to other administrative areas of the site
+ *
+ * webtrees: Web based Family History software
+ * Copyright (C) 2010 webtrees development team.
+ *
+ * Derived from PhpGedView
+ * Copyright (C) 2002 to 2009 PGV Development Team. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ * @version $Id$
+ */
+
+define('WT_SCRIPT_NAME', 'admin_site_info.php');
+
+require './includes/session.php';
+
+if (!WT_USER_GEDCOM_ADMIN) {
+ header('Location: '.WT_SERVER_NAME.WT_SCRIPT_PATH.'login.php?url='.WT_SCRIPT_NAME);
+ exit;
+}
+
+if (isset($_REQUEST['action'])) $action = $_REQUEST['action'];
+
+if (!isset($action)) $action = "";
+
+if ($action == "phpinfo") {
+ $helpindex = "phpinfo_help";
+ print_header(i18n::translate('PHP information'));
+
+ echo '<div>';
+
+ ob_start();
+
+ phpinfo();
+ $php_info = ob_get_contents();
+
+ ob_end_clean();
+
+ $php_info = str_replace(" width=\"600\"", " width=\"\"", $php_info);
+ $php_info = str_replace("</body></html>", "", $php_info);
+ $php_info = str_replace("<table", "<table class=\"facts_table ltr\"", $php_info);
+ $php_info = str_replace("td class=\"e\"", "td class=\"facts_value\"", $php_info);
+ $php_info = str_replace("td class=\"v\"", "td class=\"facts_value\"", $php_info);
+ $php_info = str_replace("tr class=\"v\"", "tr", $php_info);
+ $php_info = str_replace("tr class=\"h\"", "tr", $php_info);
+
+ $php_info = str_replace(";", "; ", $php_info);
+ $php_info = str_replace(",", ", ", $php_info);
+
+ // Put logo in table header
+ $logo_offset = strpos($php_info, "<td>");
+ $php_info = substr_replace($php_info, "<td colspan=\"3\" class=\"center\">", $logo_offset, 4);
+ $logo_width_offset = strpos($php_info, "width=\"\"");
+ $php_info = substr_replace($php_info, "", $logo_width_offset, 8);
+ $php_info = str_replace(" width=\"\"", "", $php_info);
+
+ $offset = strpos($php_info, "<table");
+ $php_info = substr($php_info, $offset);
+
+ echo $php_info;
+ echo '</div>';
+}
+
+print_footer();
diff --git a/admin_site_ipaddress.php b/admin_site_ipaddress.php
new file mode 100644
index 0000000000..8e00a1a574
--- /dev/null
+++ b/admin_site_ipaddress.php
@@ -0,0 +1,205 @@
+<?php
+/**
+* Manage Servers Page
+*
+* Allow a user the ability to manage servers i.e. allowing, banning, deleting
+*
+* webtrees: Web based Family History software
+ * Copyright (C) 2010 webtrees development team.
+ *
+ * Derived from PhpGedView
+* Copyright (C) 2002 to 2009 PGV Development Team. All rights reserved.
+*
+* Modifications Copyright (c) 2010 Greg Roach
+*
+* This program is free software; you can redistribute it and/or modify
+* it under the terms of the GNU General Public License as published by
+* the Free Software Foundation; either version 2 of the License, or
+* (at your option) any later version.
+*
+* This program is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+* GNU General Public License for more details.
+*
+* You should have received a copy of the GNU General Public License
+* along with this program; if not, write to the Free Software
+* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+*
+* @package webtrees
+* @subpackage Admin
+* @version $Id$
+* @author rbennett
+*/
+
+define('WT_SCRIPT_NAME', 'admin_site_ipaddress.php');
+
+require './includes/session.php';
+require_once WT_ROOT.'includes/functions/functions.php';
+require_once WT_ROOT.'includes/functions/functions_edit.php';
+require_once WT_ROOT.'includes/functions/functions_import.php';
+
+print_header(i18n::translate('Manage sites'));
+//-- only allow managers here
+if (!WT_USER_GEDCOM_ADMIN) {
+ echo i18n::translate('<b>Access Denied</b><br />You do not have access to this resource.');
+ //-- display messages as to why the editing access was denied
+ if (!WT_USER_GEDCOM_ADMIN) echo "<br />".i18n::translate('This user name cannot edit this GEDCOM.');
+ echo "<br /><br /><div class=\"center\"><a href=\"javascript: ".i18n::translate('Close Window')."\" onclick=\"window.close();\">".i18n::translate('Close Window')."</a></div>";
+ print_footer();
+ exit;
+}
+
+$action = safe_GET('action');
+if (empty($action)) $action = safe_POST('action');
+$address = safe_GET('address');
+if (empty($address)) $address = safe_POST('address');
+$comment = safe_GET('comment');
+if (empty($comment)) $comment = safe_POST('comment');
+$comment = str_replace(array("\\", "\$", "\""), array("\\\\", "\\\$", "\\\""), $comment);
+
+$deleteBanned = safe_POST('deleteBanned');
+if (!empty($deleteBanned)) { // A "remove banned IP" button was pushed
+ $action = 'deleteBanned';
+ $address = $deleteBanned;
+}
+
+$deleteSearch = safe_POST('deleteSearch');
+if (!empty($deleteSearch)) { // A "remove search engine IP" button was pushed
+ $action = 'deleteSearch';
+ $address = $deleteSearch;
+}
+
+if (empty($action)) $action = 'showForm';
+
+/*
+* Validate input string to be an IP address
+*/
+function validIP($address) {
+ if (!preg_match('/^\d{1,3}\.(\d{1,3}|\*)\.(\d{1,3}|\*)\.(\d{1,3}|\*)$/', $address)) return false;
+ $pieces = explode('.', $address);
+ foreach ($pieces as $number) {
+ if ($number!="*" && $number>255) return false;
+ }
+ return true;
+}
+
+if ($action=='addBanned' || $action=='addSearch' || $action=='deleteBanned' || $action=='deleteSearch') {
+ if (validIP($address)) {
+ // Even if we are adding a new record, we must delete the existing one first.
+ WT_DB::prepare(
+ "DELETE FROM `##ip_address` WHERE ip_address=?"
+ )->execute(array($address));
+ if ($action=='addBanned') {
+ WT_DB::prepare(
+ "INSERT INTO `##ip_address` (ip_address, category, comment) VALUES (?, ?, ?)"
+ )->execute(array($address, 'banned', $comment));
+ }
+ if ($action=='addSearch') {
+ WT_DB::prepare(
+ "INSERT INTO `##ip_address` (ip_address, category, comment) VALUES (?, ?, ?)"
+ )->execute(array($address, 'search-engine', $comment));
+ }
+ } else {
+ if ($action=='addBanned') {
+ $errorBanned=i18n::translate('Invalid IP address.');
+ }
+ if ($action=='addSearch') {
+ $errorSearch=i18n::translate('Invalid IP address.');
+ }
+ }
+ $action='showForm';
+}
+
+?>
+
+<script language="JavaScript" type="text/javascript">
+<!--
+function showSite(siteID) {
+ buttonShow = document.getElementById("buttonShow_"+siteID);
+ siteDetails = document.getElementById("siteDetails_"+siteID);
+ if (siteDetails.style.display=='none') {
+ buttonShow.innerHTML='<?php echo i18n::translate('Hide Details'); ?>';
+ siteDetails.style.display='block';
+ } else {
+ buttonShow.innerHTML='<?php echo i18n::translate('Show Details'); ?>';
+ siteDetails.style.display='none';
+ }
+}
+//-->
+</script>
+
+<?php
+// Search Engine IP address table
+echo '<table>',
+ '<tr><td>',
+ '<form name="searchengineform" action="', WT_SCRIPT_NAME, '" method="post">',
+ '<table>',
+ '<tr>',
+ '<td><h3>', i18n::translate('Manually mark Search Engines by IP'). help_link('help_manual_search_engines'), '</h3></td>',
+ '</tr>',
+ '<tr>',
+ '<td>',
+ '<table>';
+
+ $sql="SELECT ip_address, comment FROM `##ip_address` WHERE category='search-engine' ORDER BY INET_ATON(ip_address)";
+ $index=0;
+ $search_engines=WT_DB::prepare($sql)->fetchAssoc();
+ foreach ($search_engines as $ip_address=>$ip_comment) {
+ echo '<tr><td><span dir="ltr"><input type="text" name="address', ++$index, '" size="16" value="', $ip_address, '" readonly /></span></td>';
+ echo '<td><input type="text" name="comment', ++$index, '" size="60" value="', $ip_comment, '" readonly /></td><td class="button">';
+ echo '<button name="deleteSearch" value="', $ip_address, '" type="submit">', i18n::translate('Remove'), '</button>';
+ echo '</td></tr>';
+ }
+ echo '<tr><td valign="top"><span dir="ltr"><input type="text" id="txtAddIp" name="address" size="16" value="', empty($errorSearch) ? '':$address, '" /></span></td>';
+ echo '<td><input type="text" id="txtAddComment" name="comment" size="60" value="" />';
+ echo '<br />', i18n::translate('You may enter a comment here.'), '</td><td class="button" valign="top"><input name="action" type="hidden" value="addSearch"/>';
+ echo '<input type="submit" value="', i18n::translate('Add'), '" />';
+ echo '</td></tr>';
+
+ if (!empty($errorSearch)) {
+ echo '<tr><td colspan="2"><span class="warning">';
+ echo $errorSearch;
+ echo '</span></td></tr>';
+ $errorSearch = '';
+ }
+ echo '</table></td></tr></table></form></td></tr></table>';
+?>
+
+<!-- Banned IP address table -->
+<table>
+<tr>
+ <td>
+ <form name="banIPform" action="<?php echo WT_SCRIPT_NAME; ?>" method="post">
+ <table>
+ <tr>
+ <td>
+ <h3><?php echo i18n::translate('Ban Sites by IP').help_link('help_banning'); ?></h3>
+ </td>
+ </tr>
+ <tr>
+ <td>
+ <table>
+<?php
+$sql="SELECT ip_address, comment FROM `##ip_address` WHERE category='banned' ORDER BY INET_ATON(ip_address)";
+$banned=WT_DB::prepare($sql)->fetchAssoc();
+foreach ($banned as $ip_address=>$ip_comment) {
+ echo '<tr><td><span dir="ltr"><input type="text" name="address', ++$index, '" size="16" value="', $ip_address, '" readonly /></span></td>';
+ echo '<td><input type="text" name="comment', ++$index, '" size="60" value="', $ip_comment, '" readonly /></td><td class="button">';
+ echo '<button name="deleteBanned" value="', $ip_address, '" type="submit">', i18n::translate('Remove'), '</button>';
+ echo '</td></tr>';
+}
+echo '<tr><td valign="top"><span dir="ltr"><input type="text" id="txtAddIp" name="address" size="16" value="', empty($errorBanned) ? '':$address, '" /></span></td>';
+echo '<td><input type="text" id="txtAddComment" name="comment" size="60" value="" />';
+echo '<br />', i18n::translate('You may enter a comment here.'), '</td><td class="button" valign="top"><input name="action" type="hidden" value="addBanned"/>';
+echo '<input type="submit" value="', i18n::translate('Add'), '" />';
+echo '</td></tr>';
+
+if (!empty($errorBanned)) {
+ echo '<tr><td colspan="2"><span class="warning">';
+ echo $errorBanned;
+ echo '</span></td></tr>';
+ $errorBanned = '';
+}
+echo '</table></td></tr></table></form></td></tr></table>';
+print_footer();
diff --git a/admin_site_logs.php b/admin_site_logs.php
new file mode 100644
index 0000000000..b14c43668c
--- /dev/null
+++ b/admin_site_logs.php
@@ -0,0 +1,261 @@
+<?php
+/**
+ * Log viewer.
+ *
+ * webtrees: Web based Family History software
+ * Copyright (C) 2010 webtrees development team.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ * @package webtrees
+ * @subpackage Admin
+ * @version $Id$
+ */
+
+define('WT_SCRIPT_NAME', 'admin_site_logs.php');
+
+require './includes/session.php';
+require WT_ROOT.'includes/functions/functions_edit.php';
+
+// Only admin users can access this page
+if (!WT_USER_GEDCOM_ADMIN) {
+ header('Location: '.WT_SERVER_NAME.WT_SCRIPT_PATH.'login.php?url='.WT_SCRIPT_NAME);
+ exit;
+}
+
+$earliest=WT_DB::prepare("SELECT DATE(MIN(log_time)) FROM `##log`")->execute(array())->fetchOne();
+$latest =WT_DB::prepare("SELECT DATE(MAX(log_time)) FROM `##log`")->execute(array())->fetchOne();
+
+// Filtering
+$action=safe_GET('action');
+$from =safe_GET('from', '\d\d\d\d-\d\d-\d\d', $earliest);
+$to =safe_GET('to', '\d\d\d\d-\d\d-\d\d', $latest);
+$type =safe_GET('type', array('auth','change','config','debug','edit','error','media','search'));
+$text =safe_GET('text');
+$ip =safe_GET('ip');
+$user =safe_GET('user');
+if (WT_USER_IS_ADMIN) {
+ // Administrators can see all logs
+ $gedc=safe_GET('gedc');
+} else {
+ // Managers can only see logs relating to this gedcom
+ $gedc=WT_GEDCOM;
+}
+
+$query=array();
+$args =array();
+if ($from) {
+ $query[]='log_time>=?';
+ $args []=$from;
+}
+if ($to) {
+ $query[]='log_time<TIMESTAMPADD(DAY, 1 , ?)'; // before end of the day
+ $args []=$to;
+}
+if ($type) {
+ $query[]='log_type=?';
+ $args []=$type;
+}
+if ($text) {
+ $query[]="log_message LIKE CONCAT('%', ?, '%')";
+ $args []=$text;
+}
+if ($ip) {
+ $query[]="ip_address LIKE CONCAT('%', ?, '%')";
+ $args []=$ip;
+}
+if ($user) {
+ $query[]="user_name LIKE CONCAT('%', ?, '%')";
+ $args []=$user;
+}
+if ($gedc) {
+ $query[]="gedcom_name LIKE CONCAT('%', ?, '%')";
+ $args []=$gedc;
+}
+
+$SELECT1=
+ "SELECT SQL_CACHE SQL_CALC_FOUND_ROWS log_time, log_type, log_message, ip_address, IFNULL(user_name, '<none>') AS user_name, IFNULL(gedcom_name, '<none>') AS gedcom_name".
+ " FROM `##log`".
+ " LEFT JOIN `##user` USING (user_id)". // user may be deleted
+ " LEFT JOIN `##gedcom` USING (gedcom_id)"; // gedcom may be deleted
+$SELECT2=
+ "SELECT COUNT(*) FROM `##log`".
+ " LEFT JOIN `##user` USING (user_id)". // user may be deleted
+ " LEFT JOIN `##gedcom` USING (gedcom_id)"; // gedcom may be deleted
+if ($query) {
+ $WHERE=" WHERE ".implode(' AND ', $query);
+} else {
+ $WHERE='';
+}
+
+switch($action) {
+case 'delete':
+ $DELETE=
+ "DELETE `##log` FROM `##log`".
+ " LEFT JOIN `##user` USING (user_id)". // user may be deleted
+ " LEFT JOIN `##gedcom` USING (gedcom_id)". // gedcom may be deleted
+ $WHERE;
+ WT_DB::prepare($DELETE)->execute($args);
+ break;
+case 'export':
+ header('Content-Type: text/csv');
+ header('Content-Disposition: attachment; filename="webtrees-logs.csv"');
+ $rows=WT_DB::prepare($SELECT1.$WHERE.' ORDER BY log_id')->execute($args)->fetchAll();
+ foreach ($rows as $row) {
+ echo
+ '"', $row->log_time, '",',
+ '"', $row->log_type, '",',
+ '"', str_replace('"', '""', $row->log_message), '",',
+ '"', $row->ip_address, '",',
+ '"', str_replace('"', '""', $row->user_name), '",',
+ '"', str_replace('"', '""', $row->gedcom_name), '"',
+ "\n";
+ }
+ exit;
+case 'load_json':
+ $iDisplayStart =(int)safe_GET('iDisplayStart');
+ $iDisplayLength=(int)safe_GET('iDisplayLength');
+ if ($iDisplayLength>0) {
+ $LIMIT=" LIMIT " . $iDisplayStart . ',' . $iDisplayLength;
+ } else {
+ $LIMIT="";
+ }
+ $iSortingCols=safe_GET('iSortingCols');
+ if ($iSortingCols) {
+ $ORDER_BY=' ORDER BY ';
+ for ($i=0; $i<$iSortingCols; ++$i) {
+ // Datatables numbers columns 0, 1, 2, ...
+ // MySQL numbers columns 1, 2, 3, ...
+ switch (safe_GET('sSortDir_'.$i)) {
+ case 'asc':
+ $ORDER_BY.=(1+(int)safe_GET('iSortCol_'.$i)).' ASC ';
+ break;
+ case 'desc':
+ $ORDER_BY.=(1+(int)safe_GET('iSortCol_'.$i)).' DESC ';
+ break;
+ }
+ if ($i<$iSortingCols-1) {
+ $ORDER_BY.=',';
+ }
+ }
+ } else {
+ $ORDER_BY='1 DESC';
+ }
+
+ // This becomes a JSON list, not array, so need to fetch with numeric keys.
+ $aaData=WT_DB::prepare($SELECT1.$WHERE.$ORDER_BY.$LIMIT)->execute($args)->fetchAll(PDO::FETCH_NUM);
+
+ // Total filtered/unfiltered rows
+ $iTotalDisplayRecords=WT_DB::prepare("SELECT FOUND_ROWS()")->fetchColumn();
+ $iTotalRecords=WT_DB::prepare($SELECT2.$WHERE)->execute($args)->fetchColumn();
+
+ header('Content-type: application/json; charset=utf8');
+ echo json_encode(array( // See http://www.datatables.net/usage/server-side
+ 'sEcho' =>(int)safe_GET('sEcho'),
+ 'iTotalRecords' =>$iTotalRecords,
+ 'iTotalDisplayRecords'=>$iTotalDisplayRecords,
+ 'aaData' =>$aaData
+ ));
+ exit;
+}
+
+print_header(i18n::translate('Logs'));
+echo WT_JS_START;
+
+?>
+ jQuery(document).ready(function(){
+ var oTable = jQuery('#log_list').dataTable( {
+ "sDom": '<"H"lpr>t<"F"i>',
+ "bProcessing": true,
+ "bServerSide": true,
+ "sAjaxSource": "<?php echo WT_SERVER_NAME.WT_SCRIPT_PATH.WT_SCRIPT_NAME.'?action=load_json&from=', $from,'&to=', $to, '&type=', $type, '&text=', rawurlencode($text), '&ip=', rawurlencode($ip), '&user=', rawurlencode($user), '&gedc=', rawurlencode($gedc); ?>",
+ "oLanguage": {
+ "sLengthMenu": 'Display <select><option value="10">10</option><option value="20">20</option><option value="50">50</option><option value="100">100</option></select> records'
+ },
+ "bJQueryUI": true,
+ "bAutoWidth":false,
+ "aaSorting": [[ 1, "desc" ]],
+ "iDisplayLength": 20,
+ "sPaginationType": "full_numbers",
+ });
+ });
+
+<?php
+
+$url=
+ WT_SCRIPT_NAME.'?from='.rawurlencode($from).
+ '&amp;to='.rawurlencode($to).
+ '&amp;type='.rawurlencode($type).
+ '&amp;text='.rawurlencode($text).
+ '&amp;ip='.rawurlencode($ip).
+ '&amp;user='.rawurlencode($user).
+ '&amp;gedc='.rawurlencode($gedc);
+
+echo
+ WT_JS_END,
+ '<form name="logs" method="get" action="'.WT_SCRIPT_NAME.'">',
+ '<input type="hidden" name="action", value="show"/>',
+ '<table class="site_logs">',
+ '<tr>',
+ '<td>',
+ // I18N: %s are both user-input date fields
+ i18n::translate('From %s To %s', '<input name="from" size="8" value="'.htmlspecialchars($from).'" /><br />', '&nbsp;&nbsp;&nbsp;<input name="to" size="8" value="'.htmlspecialchars($to).'" />'),
+ '</td>',
+ '<td>',
+ i18n::translate('Type'), '<br />', select_edit_control('type', array(''=>'', 'auth'=>'auth','config'=>'config','debug'=>'debug','edit'=>'edit','error'=>'error','media'=>'media','search'=>'search'), null, $type, ''),
+ '</td>',
+ '<td>',
+ i18n::translate('Message'), '<br /><input name="text" size="12" value="', htmlspecialchars($text), '" /> ',
+ '</td>',
+ '<td>',
+ i18n::translate('IP address'), '<br /><input name="ip" size="12" value="', htmlspecialchars($ip), '" /> ',
+ '</td>',
+ '<td>',
+ i18n::translate('User'), '<br /><input name="user" size="12" value="', htmlspecialchars($user), '" /> ',
+ '</td>',
+ '<td>',
+ i18n::translate('Gedcom'), '<br /><input name="gedc" size="12" value="', htmlspecialchars($gedc), '" ', WT_USER_IS_ADMIN ? '' : 'disabled', '/> ',
+ '</td>',
+ '<td class="button" rowspan="2">',
+ '<input type="submit" value="', i18n::translate('Filter'), '" />',
+ '<br/>',
+ '<input type="submit" value="', i18n::translate('Export'), '" onclick="document.logs.action.value=\'export\';return true;" ', ($action=='show' ? '' : 'disabled="disabled"'),' />',
+ '<br/>',
+ '<input type="submit" value="', i18n::translate('Delete'), '" onclick="if (confirm(\'', htmlspecialchars(i18n::translate('Permanently delete these records?')) , '\')) {document.logs.action.value=\'delete\';return true;} else {return false;}" ', ($action=='show' ? '' : 'disabled="disabled"'),' />',
+ '</td>',
+ '</tr>',
+ '</table>',
+ '</form>';
+
+if ($action) {
+ echo
+ '<br/>',
+ '<table id="log_list">',
+ '<thead>',
+ '<tr>',
+ '<th>', i18n::translate('Timestamp'), '</th>',
+ '<th>', i18n::translate('Type'), '</th>',
+ '<th>', i18n::translate('Message'), '</th>',
+ '<th>', i18n::translate('IP address'), '</th>',
+ '<th>', i18n::translate('User'), '</th>',
+ '<th>', i18n::translate('GEDCOM'), '</th>',
+ '</tr>',
+ '</thead>',
+ '<tbody>',
+ '</tbody>',
+ '</table>';
+}
+
+print_footer();
diff --git a/admin_site_readme.php b/admin_site_readme.php
new file mode 100644
index 0000000000..4c86bcafe6
--- /dev/null
+++ b/admin_site_readme.php
@@ -0,0 +1,61 @@
+<?php
+/**
+ * UI for online updating of the config file.
+ *
+ * webtrees: Web based Family History software
+ * Copyright (C) 2010 webtrees development team.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ * @version $Id$
+ */
+
+define('WT_SCRIPT_NAME', 'admin_site_readme.php');
+
+require './includes/session.php';
+
+function get_tag($txt,$tag){
+ $offset = 0;
+ $start_tag = "<".$tag;
+ $end_tag = "</".$tag.">";
+ $arr = array();
+ do{
+ $pos = strpos($txt,$start_tag,$offset);
+ if($pos){
+ $str_pos = strpos($txt,">",$pos)+1;
+ $end_pos = strpos($txt,$end_tag,$str_pos);
+ $len = $end_pos - $str_pos;
+ $f_text = substr($txt,$str_pos,$len);
+ $arr[] = $f_text;
+ $offset = $end_pos;
+ }
+ }while($pos);
+ return $arr;
+}
+
+print_header(i18n::translate("README documentation"));
+
+echo '<div id="readme">';
+
+$url = 'readme.html';
+$txt = file_get_contents($url);
+$arr = get_tag($txt, "body");
+
+foreach ($arr as $value) {
+ echo $value;
+}
+
+echo '</div>';
+print_footer();
diff --git a/admin_trees_config.php b/admin_trees_config.php
new file mode 100644
index 0000000000..d7cd9f751b
--- /dev/null
+++ b/admin_trees_config.php
@@ -0,0 +1,1673 @@
+<?php
+/**
+ * UI for online updating of the GEDCOM config file.
+ *
+ * webtrees: Web based Family History software
+ * Copyright (C) 2010 webtrees development team.
+ *
+ * Derived from PhpGedView
+ * Copyright (C) 2002 to 2010 PGV Development Team. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ * @version $Id$
+ */
+
+define('WT_SCRIPT_NAME', 'admin_trees_config.php');
+
+require './includes/session.php';
+require WT_ROOT.'includes/functions/functions_edit.php';
+
+if (!WT_USER_GEDCOM_ADMIN) {
+ header('Location: '.WT_SERVER_NAME.WT_SCRIPT_PATH.'administration.php');
+ exit;
+}
+
+$INDEX_DIRECTORY=get_site_setting('INDEX_DIRECTORY');
+
+/**
+ * find the name of the first GEDCOM file in a zipfile
+ * @param string $zipfile the path and filename
+ * @param boolean $extract true = extract and return filename, false = return filename
+ * @return string the path and filename of the gedcom file
+ */
+function GetGEDFromZIP($zipfile, $extract=true) {
+ GLOBAL $INDEX_DIRECTORY;
+
+ require_once WT_ROOT.'library/pclzip.lib.php';
+ $zip = new PclZip($zipfile);
+ // if it's not a valid zip, just return the filename
+ if (($list = $zip->listContent()) == 0) {
+ return $zipfile;
+ }
+
+ // Determine the extract directory
+ $slpos = strrpos($zipfile, "/");
+ if (!$slpos) $slpos = strrpos($zipfile, "\\");
+ if ($slpos) $path = substr($zipfile, 0, $slpos+1);
+ else $path = $INDEX_DIRECTORY;
+ // Scan the files and return the first .ged found
+ foreach ($list as $key=>$listitem) {
+ if (($listitem["status"]="ok") && (strstr(strtolower($listitem["filename"]), ".")==".ged")) {
+ $filename = basename($listitem["filename"]);
+ if ($extract == false) return $filename;
+
+ // if the gedcom exists, save the old one. NOT to bak as it will be overwritten on import
+ if (file_exists($path.$filename)) {
+ if (file_exists($path.$filename.".old")) unlink($path.$filename.".old");
+ copy($path.$filename, $path.$filename.".old");
+ unlink($path.$filename);
+ }
+ if ($zip->extract(PCLZIP_OPT_REMOVE_ALL_PATH, PCLZIP_OPT_PATH, $path, PCLZIP_OPT_BY_NAME, $listitem["filename"]) == 0) {
+ echo "ERROR cannot extract ZIP";
+ }
+ return $filename;
+ }
+ }
+ return $zipfile;
+}
+
+$errors=false;
+$error_msg='';
+
+$PRIVACY_CONSTANTS=array(
+ 'none' =>i18n::translate('Show to visitors'),
+ 'privacy' =>i18n::translate('Show to members'),
+ 'confidential'=>i18n::translate('Show to managers'),
+ 'hidden' =>i18n::translate('Hide from everyone')
+);
+
+switch (safe_POST('action')) {
+case 'delete':
+ WT_DB::prepare(
+ "DELETE FROM `##default_resn` WHERE default_resn_id=?"
+ )->execute(array(safe_POST('default_resn_id')));
+ // Reload the page, so that the new privacy restrictions are reflected in the header
+ header('Location: '.WT_SERVER_NAME.WT_SCRIPT_PATH.WT_SCRIPT_NAME.'#privacy');
+ exit;
+case 'add':
+ if ((safe_POST('xref') || safe_POST('tag_type')) && safe_POST('resn')) {
+ WT_DB::prepare(
+ "REPLACE INTO `##default_resn` (gedcom_id, xref, tag_type, resn) VALUES (?, ?, ?, ?)"
+ )->execute(array(WT_GED_ID, safe_POST('xref'), safe_POST('tag_type'), safe_POST('resn')));
+ }
+ // Reload the page, so that the new privacy restrictions are reflected in the header
+ header('Location: '.WT_SERVER_NAME.WT_SCRIPT_PATH.WT_SCRIPT_NAME.'#privacy');
+ exit;
+case 'update':
+ $_POST["NEW_MEDIA_DIRECTORY"] = trim(str_replace('\\','/',$_POST["NEW_MEDIA_DIRECTORY"]));
+ if (substr ($_POST["NEW_MEDIA_DIRECTORY"], -1) != "/") $_POST["NEW_MEDIA_DIRECTORY"] = $_POST["NEW_MEDIA_DIRECTORY"] . "/";
+ if (substr($_POST["NEW_MEDIA_DIRECTORY"], 0, 2)=="./") $_POST["NEW_MEDIA_DIRECTORY"] = substr($_POST["NEW_MEDIA_DIRECTORY"], 2);
+ if (preg_match("/.*[a-zA-Z]{1}:.*/", $_POST["NEW_MEDIA_DIRECTORY"])>0) $errors = true;
+ if ($_POST["NEW_USE_MEDIA_FIREWALL"]==true) {
+ if (substr($_POST["NEW_MEDIA_DIRECTORY"], 0, 3)=="../") $_POST["NEW_MEDIA_DIRECTORY"] = substr($_POST["NEW_MEDIA_DIRECTORY"], 3);
+ if (substr($_POST["NEW_MEDIA_DIRECTORY"], 0, 1)=="/") $_POST["NEW_MEDIA_DIRECTORY"] = substr($_POST["NEW_MEDIA_DIRECTORY"], 1);
+ }
+
+ set_gedcom_setting(WT_GED_ID, 'ABBREVIATE_CHART_LABELS', safe_POST_bool('NEW_ABBREVIATE_CHART_LABELS'));
+ set_gedcom_setting(WT_GED_ID, 'ADVANCED_NAME_FACTS', safe_POST('NEW_ADVANCED_NAME_FACTS'));
+ set_gedcom_setting(WT_GED_ID, 'ADVANCED_PLAC_FACTS', safe_POST('NEW_ADVANCED_PLAC_FACTS'));
+ set_gedcom_setting(WT_GED_ID, 'ALLOW_EDIT_GEDCOM', safe_POST_bool('NEW_ALLOW_EDIT_GEDCOM'));
+ set_gedcom_setting(WT_GED_ID, 'ALLOW_THEME_DROPDOWN', safe_POST_bool('NEW_ALLOW_THEME_DROPDOWN'));
+ set_gedcom_setting(WT_GED_ID, 'AUTO_GENERATE_THUMBS', safe_POST_bool('NEW_AUTO_GENERATE_THUMBS'));
+ set_gedcom_setting(WT_GED_ID, 'CALENDAR_FORMAT', safe_POST('NEW_CALENDAR_FORMAT'));
+ set_gedcom_setting(WT_GED_ID, 'CHART_BOX_TAGS', safe_POST('NEW_CHART_BOX_TAGS'));
+ set_gedcom_setting(WT_GED_ID, 'COMMON_NAMES_ADD', str_replace(' ', '', safe_POST('NEW_COMMON_NAMES_ADD')));
+ set_gedcom_setting(WT_GED_ID, 'COMMON_NAMES_REMOVE', str_replace(' ', '', safe_POST('NEW_COMMON_NAMES_REMOVE')));
+ set_gedcom_setting(WT_GED_ID, 'COMMON_NAMES_THRESHOLD', safe_POST('NEW_COMMON_NAMES_THRESHOLD', WT_REGEX_INTEGER, 40));
+ set_gedcom_setting(WT_GED_ID, 'CONTACT_USER_ID', safe_POST('NEW_CONTACT_USER_ID'));
+ set_gedcom_setting(WT_GED_ID, 'DEFAULT_PEDIGREE_GENERATIONS', safe_POST('NEW_DEFAULT_PEDIGREE_GENERATIONS'));
+ set_gedcom_setting(WT_GED_ID, 'DISPLAY_JEWISH_GERESHAYIM', safe_POST_bool('NEW_DISPLAY_JEWISH_GERESHAYIM'));
+ set_gedcom_setting(WT_GED_ID, 'DISPLAY_JEWISH_THOUSANDS', safe_POST_bool('NEW_DISPLAY_JEWISH_THOUSANDS'));
+ set_gedcom_setting(WT_GED_ID, 'ENABLE_AUTOCOMPLETE', safe_POST_bool('NEW_ENABLE_AUTOCOMPLETE'));
+ set_gedcom_setting(WT_GED_ID, 'EXPAND_NOTES', safe_POST_bool('NEW_EXPAND_NOTES'));
+ set_gedcom_setting(WT_GED_ID, 'EXPAND_RELATIVES_EVENTS', safe_POST_bool('NEW_EXPAND_RELATIVES_EVENTS'));
+ set_gedcom_setting(WT_GED_ID, 'EXPAND_SOURCES', safe_POST_bool('NEW_EXPAND_SOURCES'));
+ set_gedcom_setting(WT_GED_ID, 'FAM_FACTS_ADD', str_replace(' ', '', safe_POST('NEW_FAM_FACTS_ADD')));
+ set_gedcom_setting(WT_GED_ID, 'FAM_FACTS_QUICK', str_replace(' ', '', safe_POST('NEW_FAM_FACTS_QUICK')));
+ set_gedcom_setting(WT_GED_ID, 'FAM_FACTS_UNIQUE', str_replace(' ', '', safe_POST('NEW_FAM_FACTS_UNIQUE')));
+ set_gedcom_setting(WT_GED_ID, 'FAM_ID_PREFIX', safe_POST('NEW_FAM_ID_PREFIX'));
+ set_gedcom_setting(WT_GED_ID, 'FULL_SOURCES', safe_POST_bool('NEW_FULL_SOURCES'));
+ set_gedcom_setting(WT_GED_ID, 'GEDCOM_DEFAULT_TAB', safe_POST('NEW_GEDCOM_DEFAULT_TAB'));
+ set_gedcom_setting(WT_GED_ID, 'GEDCOM_ID_PREFIX', safe_POST('NEW_GEDCOM_ID_PREFIX'));
+ set_gedcom_setting(WT_GED_ID, 'GENERATE_UIDS', safe_POST_bool('NEW_GENERATE_UIDS'));
+ set_gedcom_setting(WT_GED_ID, 'HIDE_GEDCOM_ERRORS', !safe_POST_bool('NEW_HIDE_GEDCOM_ERRORS'));
+ set_gedcom_setting(WT_GED_ID, 'HIDE_LIVE_PEOPLE', safe_POST_bool('NEW_HIDE_LIVE_PEOPLE'));
+ set_gedcom_setting(WT_GED_ID, 'INDI_FACTS_ADD', str_replace(' ', '', safe_POST('NEW_INDI_FACTS_ADD')));
+ set_gedcom_setting(WT_GED_ID, 'INDI_FACTS_QUICK', str_replace(' ', '', safe_POST('NEW_INDI_FACTS_QUICK')));
+ set_gedcom_setting(WT_GED_ID, 'INDI_FACTS_UNIQUE', str_replace(' ', '', safe_POST('NEW_INDI_FACTS_UNIQUE')));
+ set_gedcom_setting(WT_GED_ID, 'KEEP_ALIVE_YEARS_BIRTH', safe_POST('KEEP_ALIVE_YEARS_BIRTH', WT_REGEX_INTEGER, 0));
+ set_gedcom_setting(WT_GED_ID, 'KEEP_ALIVE_YEARS_DEATH', safe_POST('KEEP_ALIVE_YEARS_DEATH', WT_REGEX_INTEGER, 0));
+ set_gedcom_setting(WT_GED_ID, 'LANGUAGE', safe_POST('GEDCOMLANG'));
+ set_gedcom_setting(WT_GED_ID, 'LINK_ICONS', safe_POST('NEW_LINK_ICONS'));
+ set_gedcom_setting(WT_GED_ID, 'MAX_ALIVE_AGE', safe_POST('MAX_ALIVE_AGE', WT_REGEX_INTEGER, 100));
+ set_gedcom_setting(WT_GED_ID, 'MAX_DESCENDANCY_GENERATIONS', safe_POST('NEW_MAX_DESCENDANCY_GENERATIONS'));
+ set_gedcom_setting(WT_GED_ID, 'MAX_PEDIGREE_GENERATIONS', safe_POST('NEW_MAX_PEDIGREE_GENERATIONS'));
+ set_gedcom_setting(WT_GED_ID, 'MEDIA_DIRECTORY', safe_POST('NEW_MEDIA_DIRECTORY'));
+ set_gedcom_setting(WT_GED_ID, 'MEDIA_DIRECTORY_LEVELS', safe_POST('NEW_MEDIA_DIRECTORY_LEVELS'));
+ set_gedcom_setting(WT_GED_ID, 'MEDIA_EXTERNAL', safe_POST_bool('NEW_MEDIA_EXTERNAL'));
+ set_gedcom_setting(WT_GED_ID, 'MEDIA_FIREWALL_THUMBS', safe_POST_bool('NEW_MEDIA_FIREWALL_THUMBS'));
+ set_gedcom_setting(WT_GED_ID, 'MEDIA_ID_PREFIX', safe_POST('NEW_MEDIA_ID_PREFIX'));
+ set_gedcom_setting(WT_GED_ID, 'META_DESCRIPTION', safe_POST('NEW_META_DESCRIPTION'));
+ set_gedcom_setting(WT_GED_ID, 'META_ROBOTS', safe_POST('NEW_META_ROBOTS'));
+ set_gedcom_setting(WT_GED_ID, 'META_TITLE', safe_POST('NEW_META_TITLE'));
+ set_gedcom_setting(WT_GED_ID, 'MULTI_MEDIA', safe_POST_bool('NEW_MULTI_MEDIA'));
+ set_gedcom_setting(WT_GED_ID, 'NOTE_ID_PREFIX', safe_POST('NEW_NOTE_ID_PREFIX'));
+ set_gedcom_setting(WT_GED_ID, 'NO_UPDATE_CHAN', safe_POST_bool('NEW_NO_UPDATE_CHAN'));
+ set_gedcom_setting(WT_GED_ID, 'PEDIGREE_FULL_DETAILS', safe_POST_bool('NEW_PEDIGREE_FULL_DETAILS'));
+ set_gedcom_setting(WT_GED_ID, 'PEDIGREE_LAYOUT', safe_POST_bool('NEW_PEDIGREE_LAYOUT'));
+ set_gedcom_setting(WT_GED_ID, 'PEDIGREE_ROOT_ID', safe_POST('NEW_PEDIGREE_ROOT_ID'));
+ set_gedcom_setting(WT_GED_ID, 'PEDIGREE_SHOW_GENDER', safe_POST_bool('NEW_PEDIGREE_SHOW_GENDER'));
+ set_gedcom_setting(WT_GED_ID, 'POSTAL_CODE', safe_POST_bool('NEW_POSTAL_CODE'));
+ set_gedcom_setting(WT_GED_ID, 'PREFER_LEVEL2_SOURCES', safe_POST('NEW_PREFER_LEVEL2_SOURCES'));
+ set_gedcom_setting(WT_GED_ID, 'QUICK_REQUIRED_FACTS', safe_POST('NEW_QUICK_REQUIRED_FACTS'));
+ set_gedcom_setting(WT_GED_ID, 'QUICK_REQUIRED_FAMFACTS', safe_POST('NEW_QUICK_REQUIRED_FAMFACTS'));
+ set_gedcom_setting(WT_GED_ID, 'REPO_FACTS_ADD', str_replace(' ', '', safe_POST('NEW_REPO_FACTS_ADD')));
+ set_gedcom_setting(WT_GED_ID, 'REPO_FACTS_QUICK', str_replace(' ', '', safe_POST('NEW_REPO_FACTS_QUICK')));
+ set_gedcom_setting(WT_GED_ID, 'REPO_FACTS_UNIQUE', str_replace(' ', '', safe_POST('NEW_REPO_FACTS_UNIQUE')));
+ set_gedcom_setting(WT_GED_ID, 'REPO_ID_PREFIX', safe_POST('NEW_REPO_ID_PREFIX'));
+ set_gedcom_setting(WT_GED_ID, 'REQUIRE_AUTHENTICATION', safe_POST_bool('NEW_REQUIRE_AUTHENTICATION'));
+ set_gedcom_setting(WT_GED_ID, 'SAVE_WATERMARK_IMAGE', safe_POST_bool('NEW_SAVE_WATERMARK_IMAGE'));
+ set_gedcom_setting(WT_GED_ID, 'SAVE_WATERMARK_THUMB', safe_POST_bool('NEW_SAVE_WATERMARK_THUMB'));
+ set_gedcom_setting(WT_GED_ID, 'SHOW_AGE_DIFF', safe_POST_bool('NEW_SHOW_AGE_DIFF'));
+ set_gedcom_setting(WT_GED_ID, 'SHOW_CONTEXT_HELP', safe_POST_bool('NEW_SHOW_CONTEXT_HELP'));
+ set_gedcom_setting(WT_GED_ID, 'SHOW_COUNTER', safe_POST_bool('NEW_SHOW_COUNTER'));
+ set_gedcom_setting(WT_GED_ID, 'SHOW_DEAD_PEOPLE', safe_POST('SHOW_DEAD_PEOPLE'));
+ set_gedcom_setting(WT_GED_ID, 'SHOW_EMPTY_BOXES', safe_POST_bool('NEW_SHOW_EMPTY_BOXES'));
+ set_gedcom_setting(WT_GED_ID, 'SHOW_EST_LIST_DATES', safe_POST_bool('NEW_SHOW_EST_LIST_DATES'));
+ set_gedcom_setting(WT_GED_ID, 'SHOW_FACT_ICONS', safe_POST_bool('NEW_SHOW_FACT_ICONS'));
+ set_gedcom_setting(WT_GED_ID, 'SHOW_GEDCOM_RECORD', safe_POST_bool('NEW_SHOW_GEDCOM_RECORD'));
+ set_gedcom_setting(WT_GED_ID, 'SHOW_HIGHLIGHT_IMAGES', safe_POST_bool('NEW_SHOW_HIGHLIGHT_IMAGES'));
+ set_gedcom_setting(WT_GED_ID, 'SHOW_LAST_CHANGE', safe_POST_bool('NEW_SHOW_LAST_CHANGE'));
+ set_gedcom_setting(WT_GED_ID, 'SHOW_LDS_AT_GLANCE', safe_POST_bool('NEW_SHOW_LDS_AT_GLANCE'));
+ set_gedcom_setting(WT_GED_ID, 'SHOW_LEVEL2_NOTES', safe_POST_bool('NEW_SHOW_LEVEL2_NOTES'));
+ set_gedcom_setting(WT_GED_ID, 'SHOW_LIST_PLACES', safe_POST('NEW_SHOW_LIST_PLACES'));
+ set_gedcom_setting(WT_GED_ID, 'SHOW_LIVING_NAMES', safe_POST('SHOW_LIVING_NAMES'));
+ set_gedcom_setting(WT_GED_ID, 'SHOW_MARRIED_NAMES', safe_POST_bool('NEW_SHOW_MARRIED_NAMES'));
+ set_gedcom_setting(WT_GED_ID, 'SHOW_MEDIA_DOWNLOAD', safe_POST_bool('NEW_SHOW_MEDIA_DOWNLOAD'));
+ set_gedcom_setting(WT_GED_ID, 'SHOW_MEDIA_FILENAME', safe_POST_bool('NEW_SHOW_MEDIA_FILENAME'));
+ set_gedcom_setting(WT_GED_ID, 'SHOW_NO_WATERMARK', safe_POST('NEW_SHOW_NO_WATERMARK'));
+ set_gedcom_setting(WT_GED_ID, 'SHOW_PARENTS_AGE', safe_POST_bool('NEW_SHOW_PARENTS_AGE'));
+ set_gedcom_setting(WT_GED_ID, 'SHOW_PEDIGREE_PLACES', safe_POST('NEW_SHOW_PEDIGREE_PLACES'));
+ set_gedcom_setting(WT_GED_ID, 'SHOW_PRIVATE_RELATIONSHIPS', safe_POST('SHOW_PRIVATE_RELATIONSHIPS'));
+ set_gedcom_setting(WT_GED_ID, 'SHOW_REGISTER_CAUTION', safe_POST_bool('NEW_SHOW_REGISTER_CAUTION'));
+ set_gedcom_setting(WT_GED_ID, 'SHOW_RELATIVES_EVENTS', safe_POST('NEW_SHOW_RELATIVES_EVENTS'));
+ set_gedcom_setting(WT_GED_ID, 'SHOW_SPIDER_TAGLINE', safe_POST_bool('NEW_SHOW_SPIDER_TAGLINE'));
+ set_gedcom_setting(WT_GED_ID, 'SHOW_STATS', safe_POST_bool('NEW_SHOW_STATS'));
+ set_gedcom_setting(WT_GED_ID, 'SOURCE_ID_PREFIX', safe_POST('NEW_SOURCE_ID_PREFIX'));
+ set_gedcom_setting(WT_GED_ID, 'SOUR_FACTS_ADD', str_replace(' ', '', safe_POST('NEW_SOUR_FACTS_ADD')));
+ set_gedcom_setting(WT_GED_ID, 'SOUR_FACTS_QUICK', str_replace(' ', '', safe_POST('NEW_SOUR_FACTS_QUICK')));
+ set_gedcom_setting(WT_GED_ID, 'SOUR_FACTS_UNIQUE', str_replace(' ', '', safe_POST('NEW_SOUR_FACTS_UNIQUE')));
+ set_gedcom_setting(WT_GED_ID, 'SPLIT_PLACES', safe_POST_bool('NEW_SPLIT_PLACES'));
+ set_gedcom_setting(WT_GED_ID, 'SUBLIST_TRIGGER_F', safe_POST('NEW_SUBLIST_TRIGGER_F', WT_REGEX_INTEGER, 200));
+ set_gedcom_setting(WT_GED_ID, 'SUBLIST_TRIGGER_I', safe_POST('NEW_SUBLIST_TRIGGER_I', WT_REGEX_INTEGER, 200));
+ set_gedcom_setting(WT_GED_ID, 'SURNAME_LIST_STYLE', safe_POST('NEW_SURNAME_LIST_STYLE'));
+ set_gedcom_setting(WT_GED_ID, 'SURNAME_TRADITION', safe_POST('NEW_SURNAME_TRADITION'));
+ set_gedcom_setting(WT_GED_ID, 'THEME_DIR', safe_POST('NEW_THEME_DIR'));
+ set_gedcom_setting(WT_GED_ID, 'THUMBNAIL_WIDTH', safe_POST('NEW_THUMBNAIL_WIDTH'));
+ set_gedcom_setting(WT_GED_ID, 'UNDERLINE_NAME_QUOTES', safe_POST_bool('NEW_UNDERLINE_NAME_QUOTES'));
+ set_gedcom_setting(WT_GED_ID, 'USE_GEONAMES', safe_POST_bool('NEW_USE_GEONAMES'));
+ set_gedcom_setting(WT_GED_ID, 'USE_MEDIA_FIREWALL', safe_POST_bool('NEW_USE_MEDIA_FIREWALL'));
+ set_gedcom_setting(WT_GED_ID, 'USE_MEDIA_VIEWER', safe_POST_bool('NEW_USE_MEDIA_VIEWER'));
+ set_gedcom_setting(WT_GED_ID, 'USE_RIN', safe_POST_bool('NEW_USE_RIN'));
+ set_gedcom_setting(WT_GED_ID, 'USE_SILHOUETTE', safe_POST_bool('NEW_USE_SILHOUETTE'));
+ set_gedcom_setting(WT_GED_ID, 'USE_THUMBS_MAIN', safe_POST_bool('NEW_USE_THUMBS_MAIN'));
+ set_gedcom_setting(WT_GED_ID, 'WATERMARK_THUMB', safe_POST_bool('NEW_WATERMARK_THUMB'));
+ set_gedcom_setting(WT_GED_ID, 'WEBMASTER_USER_ID', safe_POST('NEW_WEBMASTER_USER_ID'));
+ set_gedcom_setting(WT_GED_ID, 'WEBTREES_EMAIL', safe_POST('NEW_WEBTREES_EMAIL'));
+ set_gedcom_setting(WT_GED_ID, 'WELCOME_TEXT_AUTH_MODE', safe_POST('NEW_WELCOME_TEXT_AUTH_MODE'));
+ set_gedcom_setting(WT_GED_ID, 'WELCOME_TEXT_AUTH_MODE_'.WT_LOCALE, safe_POST('NEW_WELCOME_TEXT_AUTH_MODE_4', WT_REGEX_UNSAFE));
+ set_gedcom_setting(WT_GED_ID, 'WELCOME_TEXT_CUST_HEAD', safe_POST_bool('NEW_WELCOME_TEXT_CUST_HEAD'));
+ set_gedcom_setting(WT_GED_ID, 'WORD_WRAPPED_NOTES', safe_POST_bool('NEW_WORD_WRAPPED_NOTES'));
+ set_gedcom_setting(WT_GED_ID, 'ZOOM_BOXES', safe_POST('NEW_ZOOM_BOXES'));
+ set_gedcom_setting(WT_GED_ID, 'title', safe_POST('gedcom_title', WT_REGEX_UNSAFE));
+
+ if (!$_POST["NEW_MEDIA_FIREWALL_ROOTDIR"]) {
+ $NEW_MEDIA_FIREWALL_ROOTDIR = $INDEX_DIRECTORY;
+ } else {
+ $_POST["NEW_MEDIA_FIREWALL_ROOTDIR"] = trim(str_replace('\\','/',$_POST["NEW_MEDIA_FIREWALL_ROOTDIR"]));
+ if (substr ($_POST["NEW_MEDIA_FIREWALL_ROOTDIR"], -1) != "/") $_POST["NEW_MEDIA_FIREWALL_ROOTDIR"] = $_POST["NEW_MEDIA_FIREWALL_ROOTDIR"] . "/";
+ $NEW_MEDIA_FIREWALL_ROOTDIR = safe_POST("NEW_MEDIA_FIREWALL_ROOTDIR");
+ }
+ if (!is_dir($NEW_MEDIA_FIREWALL_ROOTDIR)) {
+ $errors = true;
+ $error_msg .= "<span class=\"error\">".i18n::translate('The Media Firewall root directory you requested does not exist. You must create it first.')."</span><br />";
+ }
+ if (!$errors) {
+ // create the media directory
+ // if NEW_MEDIA_FIREWALL_ROOTDIR is the INDEX_DIRECTORY, WT will have perms to create it
+ // if WT is unable to create the directory, tell the user to create it
+ if ($_POST["NEW_USE_MEDIA_FIREWALL"]==true) {
+ if (!is_dir($NEW_MEDIA_FIREWALL_ROOTDIR.$MEDIA_DIRECTORY)) {
+ @mkdir($NEW_MEDIA_FIREWALL_ROOTDIR.$MEDIA_DIRECTORY, WT_PERM_EXE);
+ if (!is_dir($NEW_MEDIA_FIREWALL_ROOTDIR.$MEDIA_DIRECTORY)) {
+ $errors = true;
+ $error_msg .= "<span class=\"error\">".i18n::translate('The protected media directory could not be created in the Media Firewall root directory. Please create this directory and make it world-writable.')." ".$NEW_MEDIA_FIREWALL_ROOTDIR.$MEDIA_DIRECTORY."</span><br />";
+ }
+ }
+ }
+ }
+ if (!$errors) {
+ // create the thumbs dir to make sure we have write perms
+ if ($_POST["NEW_USE_MEDIA_FIREWALL"]==true) {
+ if (!is_dir($NEW_MEDIA_FIREWALL_ROOTDIR.$MEDIA_DIRECTORY."thumbs")) {
+ @mkdir($NEW_MEDIA_FIREWALL_ROOTDIR.$MEDIA_DIRECTORY."thumbs", WT_PERM_EXE);
+ if (!is_dir($NEW_MEDIA_FIREWALL_ROOTDIR.$MEDIA_DIRECTORY."thumbs")) {
+ $errors = true;
+ $error_msg .= "<span class=\"error\">".i18n::translate('The protected media directory in the Media Firewall root directory is not world writable. ')." ".$NEW_MEDIA_FIREWALL_ROOTDIR.$MEDIA_DIRECTORY."</span><br />";
+ }
+ }
+ }
+ }
+ if (!$errors) {
+ // copy the .htaccess file from INDEX_DIRECTORY to NEW_MEDIA_FIREWALL_ROOTDIR in case it is still in a web-accessible area
+ if ($_POST["NEW_USE_MEDIA_FIREWALL"]==true) {
+ if ((file_exists($INDEX_DIRECTORY.".htaccess")) && (is_dir($NEW_MEDIA_FIREWALL_ROOTDIR.$MEDIA_DIRECTORY)) && (!file_exists($NEW_MEDIA_FIREWALL_ROOTDIR.$MEDIA_DIRECTORY.".htaccess")) ) {
+ @copy($INDEX_DIRECTORY.".htaccess", $NEW_MEDIA_FIREWALL_ROOTDIR.$MEDIA_DIRECTORY.".htaccess");
+ if (!file_exists($NEW_MEDIA_FIREWALL_ROOTDIR.$MEDIA_DIRECTORY.".htaccess")) {
+ $errors = true;
+ $error_msg .= "<span class=\"error\">".i18n::translate('The protected media directory in the Media Firewall root directory is not world writable. ')." ".$NEW_MEDIA_FIREWALL_ROOTDIR.$MEDIA_DIRECTORY."</span><br />";
+ }
+ }
+ }
+ }
+ if (!$errors) {
+ set_gedcom_setting(WT_GED_ID, 'MEDIA_FIREWALL_ROOTDIR', safe_POST('NEW_MEDIA_FIREWALL_ROOTDIR'));
+ }
+
+ if ($_POST["NEW_USE_MEDIA_FIREWALL"]==true ) {
+ AddToLog("Media Firewall enabled", 'config');
+
+ if (!$errors) {
+ // create/modify an htaccess file in the main media directory
+ $httext = "";
+ if (file_exists($MEDIA_DIRECTORY.".htaccess")) {
+ $httext = implode('', file($MEDIA_DIRECTORY.".htaccess"));
+ // remove all WT media firewall sections from the .htaccess
+ $httext = preg_replace('/\n?^[#]*\s*BEGIN WT MEDIA FIREWALL SECTION(.*\n){10}[#]*\s*END WT MEDIA FIREWALL SECTION\s*[#]*\n?/m', "", $httext);
+ // comment out any existing lines that set ErrorDocument 404
+ $httext = preg_replace('/^(ErrorDocument\s*404(.*))\n?/', "#$1\n", $httext);
+ $httext = preg_replace('/[^#](ErrorDocument\s*404(.*))\n?/', "\n#$1\n", $httext);
+ }
+ // add new WT media firewall section to the end of the file
+ $httext .= "\n######## BEGIN WT MEDIA FIREWALL SECTION ##########";
+ $httext .= "\n################## DO NOT MODIFY ###################";
+ $httext .= "\n## THERE MUST BE EXACTLY 11 LINES IN THIS SECTION ##";
+ $httext .= "\n<IfModule mod_rewrite.c>";
+ $httext .= "\n\tRewriteEngine On";
+ $httext .= "\n\tRewriteCond %{REQUEST_FILENAME} !-f";
+ $httext .= "\n\tRewriteCond %{REQUEST_FILENAME} !-d";
+ $httext .= "\n\tRewriteRule .* ".WT_SCRIPT_PATH."mediafirewall.php"." [L]";
+ $httext .= "\n</IfModule>";
+ $httext .= "\nErrorDocument\t404\t".WT_SCRIPT_PATH."mediafirewall.php";
+ $httext .= "\n########## END WT MEDIA FIREWALL SECTION ##########";
+
+ $whichFile = $MEDIA_DIRECTORY.".htaccess";
+ $fp = @fopen($whichFile, "wb");
+ if (!$fp) {
+ $errors = true;
+ $error_msg .= "<span class=\"error\">".i18n::translate('E R R O R !!!<br />Could not write to file <i>%s</i>. Please check it for proper Write permissions.', $whichFile)."</span><br />";
+ } else {
+ fwrite($fp, $httext);
+ fclose($fp);
+ chmod($whichFile, 0644); // Make sure apache can read this file
+ }
+ }
+ } elseif ($_POST["NEW_USE_MEDIA_FIREWALL"]==false) {
+ AddToLog("Media Firewall disabled", 'config');
+
+ if (file_exists($MEDIA_DIRECTORY.".htaccess")) {
+ $httext = implode('', file($MEDIA_DIRECTORY.".htaccess"));
+ // remove all WT media firewall sections from the .htaccess
+ $httext = preg_replace('/\n?^[#]*\s*BEGIN WT MEDIA FIREWALL SECTION(.*\n){10}[#]*\s*END WT MEDIA FIREWALL SECTION\s*[#]*\n?/m', "", $httext);
+ // comment out any lines that set ErrorDocument 404
+ $httext = preg_replace('/^(ErrorDocument\s*404(.*))\n?/', "#$1\n", $httext);
+ $httext = preg_replace('/[^#](ErrorDocument\s*404(.*))\n?/', "\n#$1\n", $httext);
+ $whichFile = $MEDIA_DIRECTORY.".htaccess";
+ $fp = @fopen($whichFile, "wb");
+ if (!$fp) {
+ $errors = true;
+ $error_msg .= "<span class=\"error\">".i18n::translate('E R R O R !!!<br />Could not write to file <i>%s</i>. Please check it for proper Write permissions.', $whichFile)."</span><br />";
+ } else {
+ fwrite($fp, $httext);
+ fclose($fp);
+ chmod($whichFile, 0644); // Make sure apache can read this file
+ }
+ }
+
+ }
+
+ if (!$errors) {
+ $gednews = getUserNews(WT_GEDCOM);
+ if (count($gednews)==0) {
+ $news = array();
+ $news["title"] = i18n::translate('Welcome to Your Genealogy');
+ $news["username"] = WT_GEDCOM;
+ $news["text"] = i18n::translate('The genealogy information on this website is powered by <a href="http://webtrees.net/" target="_blank">webtrees</a>. This page provides an introduction and overview to this genealogy.<br /><br />To begin working with the data, choose one of the charts from the Charts menu, go to the Individual list, or search for a name or place.<br /><br />If you have trouble using the site, you can click on the Help icon to give you information on how to use the page that you are currently viewing.<br /><br />Thank you for visiting this site.');
+ $news["date"] = client_time();
+ addNews($news);
+ }
+ header('Location: '.WT_SERVER_NAME.WT_SCRIPT_PATH.WT_SCRIPT_NAME);
+ exit;
+ }
+}
+
+print_header(i18n::translate('Configure family tree'));
+?>
+<script type="text/javascript">
+//<![CDATA[
+ jQuery.noConflict();
+ jQuery(document).ready(function() {
+ jQuery("#tabs").tabs();
+ });
+//]]>
+</script>
+<script language="JavaScript" type="text/javascript">
+<!--
+ var pastefield;
+ function paste_id(value) {
+ pastefield.value=value;
+ }
+//-->
+</script>
+
+<form enctype="multipart/form-data" method="post" id="configform" name="configform" action="editconfig_gedcom.php">
+
+<input type="hidden" name="action" value="update" />
+<?php
+ if (!empty($error_msg)) echo "<br /><span class=\"error\">".$error_msg."</span><br />";
+ $i = 0;
+?>
+
+<table class="center <?php echo $TEXT_DIRECTION; ?>">
+ <tr>
+ <td colspan="2">
+ <div id="tabs">
+ <ul>
+ <li><a href="#file-options"><span><?php echo i18n::translate('GEDCOM Basics'); ?></span></a></li>
+ <li><a href="#privacy"><span><?php echo i18n::translate('Privacy'); ?></span></a></li>
+ <li><a href="#config-media"><span><?php echo i18n::translate('Multimedia'); ?></span></a></li>
+ <li><a href="#access-options"><span><?php echo i18n::translate('Access'); ?></span></a></li>
+ <li><a href="#layout-options"><span><?php echo i18n::translate('Layout'); ?></span></a></li>
+ <li><a href="#hide-show"><span><?php echo i18n::translate('Hide &amp; Show'); ?></span></a></li>
+ <li><a href="#edit-options"><span><?php echo i18n::translate('Edit Options'); ?></span></a></li>
+ </ul>
+ <!-- GEDCOM BASICS -->
+ <div id="file-options">
+ <table class="facts_table">
+ <tr>
+ <td class="descriptionbox nowrap">
+ <?php echo i18n::translate('GEDCOM title'), help_link('gedcom_title'); ?>
+ </td>
+ <td class="optionbox width60">
+ <input type="text" name="gedcom_title" dir="ltr" value="<?php echo htmlspecialchars(get_gedcom_setting(WT_GED_ID, 'title')); ?>" size="40" />
+ </td>
+ </tr>
+ <tr>
+ <td class="descriptionbox nowrap"><?php echo i18n::translate('Language'), help_link('LANGUAGE'); ?></td>
+ <td class="optionbox width60"><?php echo edit_field_language('GEDCOMLANG', $LANGUAGE); ?></td>
+ </tr>
+ <tr>
+ <td class="descriptionbox nowrap">
+ <?php echo i18n::translate('Default person for pedigree and descendancy charts'), help_link('PEDIGREE_ROOT_ID'); ?>
+ </td>
+ <td class="optionbox width60 wrap">
+ <input type="text" name="NEW_PEDIGREE_ROOT_ID" id="NEW_PEDIGREE_ROOT_ID" value="<?php echo $PEDIGREE_ROOT_ID; ?>" size="5" />
+ <?php
+ print_findindi_link("NEW_PEDIGREE_ROOT_ID", "");
+ if ($PEDIGREE_ROOT_ID) {
+ $person=Person::getInstance($PEDIGREE_ROOT_ID);
+ if ($person) {
+ echo ' <span class="list_item">', $person->getFullName(), ' ', $person->format_first_major_fact(WT_EVENTS_BIRT, 1), '</span>';
+ } else {
+ echo ' <span class="error">', i18n::translate('Unable to find record with ID'), '</span>';
+ }
+ }
+ ?>
+ </td>
+ </tr>
+ <tr>
+ <td class="descriptionbox nowrap">
+ <?php echo i18n::translate('Calendar format'), help_link('CALENDAR_FORMAT'); ?>
+ </td>
+ <td class="optionbox width60">
+ <select id="NEW_CALENDAR_FORMAT" name="NEW_CALENDAR_FORMAT">
+ <?php
+ foreach (array(
+ 'none'=>i18n::translate('No calendar conversion'),
+ 'gregorian'=>i18n::translate('Gregorian'),
+ 'julian'=>i18n::translate('Julian'),
+ 'french'=>i18n::translate('French'),
+ 'jewish'=>i18n::translate('Jewish'),
+ 'jewish_and_gregorian'=>i18n::translate('Jewish and Gregorian'),
+ 'hebrew'=>i18n::translate('Hebrew'),
+ 'hebrew_and_gregorian'=>i18n::translate('Hebrew and Gregorian'),
+ 'hijri'=>i18n::translate('Hijri'),
+ 'arabic'=>i18n::translate('Arabic')
+ ) as $cal=>$name) {
+ echo '<option value="', $cal, '"';
+ if ($CALENDAR_FORMAT==$cal) {
+ echo ' selected="selected"';
+ }
+ echo '>', $name, '</option>';
+ }
+ ?>
+ </select></td>
+ </tr>
+ <tr>
+ <td class="descriptionbox nowrap">
+ <?php echo i18n::translate('Display Hebrew thousands'), help_link('DISPLAY_JEWISH_THOUSANDS'); ?>
+ </td>
+ <td class="optionbox width60">
+ <?php echo edit_field_yes_no('NEW_DISPLAY_JEWISH_THOUSANDS', get_gedcom_setting(WT_GED_ID, 'DISPLAY_JEWISH_THOUSANDS')); ?>
+ </td>
+ </tr>
+ <tr>
+ <td class="descriptionbox nowrap">
+ <?php echo i18n::translate('Display Hebrew gershayim'), help_link('DISPLAY_JEWISH_GERESHAYIM'); ?>
+ </td>
+ <td class="optionbox width60">
+ <?php echo edit_field_yes_no('NEW_DISPLAY_JEWISH_GERESHAYIM', get_gedcom_setting(WT_GED_ID, 'DISPLAY_JEWISH_GERESHAYIM')); ?>
+ </td>
+ </tr>
+ <tr>
+ <td class="descriptionbox nowrap">
+ <?php echo i18n::translate('Use RIN number instead of GEDCOM ID'), help_link('USE_RIN'); ?>
+ </td>
+ <td class="optionbox width60">
+ <?php echo edit_field_yes_no('NEW_USE_RIN', get_gedcom_setting(WT_GED_ID, 'USE_RIN')); ?>
+ </td>
+ </tr>
+ <tr>
+ <td class="descriptionbox nowrap">
+ <?php echo i18n::translate('Automatically create globally unique IDs'), help_link('GENERATE_GUID'); ?>
+ </td>
+ <td class="optionbox width60">
+ <?php echo edit_field_yes_no('NEW_GENERATE_UIDS', get_gedcom_setting(WT_GED_ID, 'GENERATE_UIDS')); ?>
+ </td>
+ </tr>
+ <tr>
+ <td class="descriptionbox nowrap">
+ <?php echo i18n::translate('Add spaces where notes were wrapped'), help_link('WORD_WRAPPED_NOTES'); ?>
+ </td>
+ <td class="optionbox width60">
+ <?php echo edit_field_yes_no('NEW_WORD_WRAPPED_NOTES', get_gedcom_setting(WT_GED_ID, 'WORD_WRAPPED_NOTES')); ?>
+ </td>
+ </tr>
+ </table>
+ <table class="facts_table">
+ <tr>
+ <td colspan="6" class="subbar" colspan="2"><?php echo i18n::translate('ID settings'); ?></td>
+ </tr>
+ <tr>
+ <td class="descriptionbox nowrap">
+ <?php echo i18n::translate('Individual ID prefix'), help_link('GEDCOM_ID_PREFIX'); ?>
+ </td>
+ <td class="optionbox">
+ <input type="text" name="NEW_GEDCOM_ID_PREFIX" dir="ltr" value="<?php echo $GEDCOM_ID_PREFIX; ?>" size="5" />
+ </td>
+ <td class="descriptionbox nowrap">
+ <?php echo i18n::translate('Family ID prefix'), help_link('FAM_ID_PREFIX'); ?>
+ </td>
+ <td class="optionbox">
+ <input type="text" name="NEW_FAM_ID_PREFIX" dir="ltr" value="<?php echo $FAM_ID_PREFIX; ?>" size="5" />
+ </td>
+ <td class="descriptionbox nowrap">
+ <?php echo i18n::translate('Source ID prefix'), help_link('SOURCE_ID_PREFIX'); ?>
+ </td>
+ <td class="optionbox">
+ <input type="text" name="NEW_SOURCE_ID_PREFIX" dir="ltr" value="<?php echo $SOURCE_ID_PREFIX; ?>" size="5" />
+ </td>
+ </tr>
+ <tr>
+ <td class="descriptionbox nowrap"><?php echo i18n::translate('Repository ID prefix'), help_link('REPO_ID_PREFIX'); ?></td>
+ <td class="optionbox"><input type="text" name="NEW_REPO_ID_PREFIX" dir="ltr" value="<?php echo $REPO_ID_PREFIX; ?>" size="5" />
+ </td>
+ <td class="descriptionbox nowrap"><?php echo i18n::translate('Media ID prefix'), help_link('MEDIA_ID_PREFIX'); ?></td>
+ <td class="optionbox"><input type="text" name="NEW_MEDIA_ID_PREFIX" dir="ltr" value="<?php echo $MEDIA_ID_PREFIX; ?>" size="5" />
+ </td>
+ <td class="descriptionbox nowrap"><?php echo i18n::translate('Note ID prefix'), help_link('NOTE_ID_PREFIX'); ?></td>
+ <td class="optionbox"><input type="text" name="NEW_NOTE_ID_PREFIX" dir="ltr" value="<?php echo $NOTE_ID_PREFIX; ?>" size="5" />
+ </td>
+ </tr>
+ </table>
+ <table class="facts_table">
+ <tr>
+ <td class="subbar" colspan="2"><?php echo i18n::translate('Contact Information'); ?></td>
+ </tr>
+ <tr>
+ <?php
+ if (empty($WEBTREES_EMAIL)) {
+ $WEBTREES_EMAIL = "webtrees-noreply@".preg_replace("/^www\./i", "", $_SERVER["SERVER_NAME"]);
+ }
+ ?>
+ <td class="descriptionbox nowrap">
+ <?php echo i18n::translate('webtrees reply address'), help_link('WEBTREES_EMAIL'); ?>
+ </td>
+ <td class="optionbox width60"><input type="text" name="NEW_WEBTREES_EMAIL" value="<?php echo $WEBTREES_EMAIL; ?>" size="50" dir="ltr" /></td>
+ </tr>
+ <tr>
+ <td class="descriptionbox nowrap">
+ <?php echo i18n::translate('Genealogy contact'), help_link('CONTACT_USER_ID'); ?>
+ </td>
+ <td class="optionbox width60"><select name="NEW_CONTACT_USER_ID">
+ <?php
+ $CONTACT_USER_ID=get_gedcom_setting(WT_GED_ID, 'CONTACT_USER_ID');
+ foreach (get_all_users() as $user_id=>$user_name) {
+ if (get_user_setting($user_id, 'verified_by_admin')) {
+ echo "<option value=\"".$user_id."\"";
+ if ($CONTACT_USER_ID==$user_id) echo " selected=\"selected\"";
+ echo ">".getUserFullName($user_id)." - ".$user_name."</option>";
+ }
+ }
+ ?>
+ </select>
+ </td>
+ </tr>
+ <tr>
+ <td class="descriptionbox nowrap">
+ <?php echo i18n::translate('Support contact'), help_link('WEBMASTER_USER_ID'); ?>
+ </td>
+ <td class="optionbox width60"><select name="NEW_WEBMASTER_USER_ID">
+ <?php
+ $WEBMASTER_USER_ID=get_gedcom_setting(WT_GED_ID, 'WEBMASTER_USER_ID');
+ foreach (get_all_users() as $user_id=>$user_name) {
+ if (userIsAdmin($user_id)) {
+ echo "<option value=\"".$user_id."\"";
+ if ($WEBMASTER_USER_ID==$user_id) echo " selected=\"selected\"";
+ echo ">".getUserFullName($user_id)." - ".$user_name."</option>";
+ }
+ }
+ ?>
+ </select>
+ </td>
+ </tr>
+ <tr>
+ <td class="subbar" colspan="2"><?php echo i18n::translate('Web Site and META Tag Settings'); ?></td>
+ </tr>
+ <tr>
+ <td class="descriptionbox nowrap">
+ <?php echo i18n::translate('Add to TITLE header tag'), help_link('META_TITLE'); ?>
+ </td>
+ <td class="optionbox width60"><input type="text" dir="ltr" name="NEW_META_TITLE" value="<?php echo htmlspecialchars(get_gedcom_setting(WT_GED_ID, 'META_TITLE')); ?>" />
+ </td>
+ </tr>
+ <td class="descriptionbox nowrap">
+ <?php echo i18n::translate('Description META tag'), help_link('META_DESCRIPTION'); ?>
+ </td>
+ <td class="optionbox width60"><input type="text" dir="ltr" name="NEW_META_DESCRIPTION" value="<?php echo get_gedcom_setting(WT_GED_ID, 'META_DESCRIPTION'); ?>" /><br />
+ <?php echo i18n::translate('Leave this field empty to use the title of the currently active database.'); ?></td>
+ </tr>
+ <tr>
+ <td class="descriptionbox nowrap">
+ <?php echo i18n::translate('Robots META tag'), help_link('META_ROBOTS'); ?>
+ </td>
+ <td class="optionbox width60"><input type="text" dir="ltr" name="NEW_META_ROBOTS" value="<?php echo get_gedcom_setting(WT_GED_ID, 'META_ROBOTS'); ?>" /><br />
+ </td>
+ </tr>
+ </table>
+ </div>
+ <!-- PRIVACY OPTIONS -->
+ <div id="privacy">
+ <table class="facts_table">
+ <tr>
+ <td class="descriptionbox nowrap">
+ <?php echo i18n::translate('Privacy options'), help_link('HIDE_LIVE_PEOPLE'); ?>
+ </td>
+ <td class="optionbox width60">
+ <?php echo radio_buttons('NEW_HIDE_LIVE_PEOPLE', array(false=>i18n::translate('disable'),true=>i18n::translate('enable')), $HIDE_LIVE_PEOPLE, ''); ?>
+ </td>
+ </tr>
+ <tr>
+ <td class="descriptionbox nowrap">
+ <?php echo i18n::translate('Show dead people'), help_link('SHOW_DEAD_PEOPLE'); ?>
+ </td>
+ <td class="optionbox">
+ <?php echo edit_field_access_level("SHOW_DEAD_PEOPLE", get_gedcom_setting(WT_GED_ID, 'SHOW_DEAD_PEOPLE')); ?>
+ </td>
+ </tr>
+ <tr>
+ <td class="descriptionbox nowrap">
+ <?php /* I18N: ... [who were] born in the last XX years or died in the last YY years */ echo i18n::translate('Extend privacy to dead people'), help_link('KEEP_ALIVE'); ?>
+ </td>
+ <td class="optionbox">
+ <?php
+ echo
+ /* I18N: Extend privacy to dead people [who were] ... */
+ i18n::translate(
+ 'born in the last %1$s years or died in the last %2$s years',
+ '<input type="text" name="KEEP_ALIVE_YEARS_BIRTH" value="'.get_gedcom_setting(WT_GED_ID, 'KEEP_ALIVE_YEARS_BIRTH').'" size="5" />',
+ '<input type="text" name="KEEP_ALIVE_YEARS_DEATH" value="'.get_gedcom_setting(WT_GED_ID, 'KEEP_ALIVE_YEARS_DEATH').'" size="5" />'
+ ); ?>
+ </td>
+ </tr>
+ <tr>
+ <td class="descriptionbox nowrap">
+ <?php echo i18n::translate('Show living names'), help_link('SHOW_LIVING_NAMES'); ?>
+ </td>
+ <td class="optionbox">
+ <?php echo edit_field_access_level("SHOW_LIVING_NAMES", get_gedcom_setting(WT_GED_ID, 'SHOW_LIVING_NAMES')); ?>
+ </td>
+ </tr>
+ <tr>
+ <td class="descriptionbox nowrap">
+ <?php echo i18n::translate('Show private relationships'), help_link('SHOW_PRIVATE_RELATIONSHIPS'); ?>
+ </td>
+ <td class="optionbox width60">
+ <?php echo edit_field_yes_no('SHOW_PRIVATE_RELATIONSHIPS', get_gedcom_setting(WT_GED_ID, 'SHOW_PRIVATE_RELATIONSHIPS')); ?>
+ </td>
+ </tr>
+ <tr>
+ <td class="descriptionbox nowrap">
+ <?php echo i18n::translate('Age at which to assume a person is dead'), help_link('MAX_ALIVE_AGE'); ?>
+ </td>
+ <td class="optionbox">
+ <input type="text" name="MAX_ALIVE_AGE" value="<?php echo get_gedcom_setting(WT_GED_ID, 'MAX_ALIVE_AGE'); ?>" size="5" />
+ </td>
+ </tr>
+ </table>
+ <br />
+ <table class="facts_table">
+ <tr>
+ <td class="topbottombar" colspan="4">
+ <?php echo i18n::translate('Privacy restrictions - these apply to records and facts that do not contain a GEDCOM RESN tag'); ?>
+ </td>
+ </tr>
+ <?php
+
+ $all_tags=array();
+ $tags=array_unique(array_merge(
+ explode(',', get_gedcom_setting(WT_GED_ID, 'INDI_FACTS_ADD')), explode(',', get_gedcom_setting(WT_GED_ID, 'INDI_FACTS_UNIQUE')),
+ explode(',', get_gedcom_setting(WT_GED_ID, 'FAM_FACTS_ADD' )), explode(',', get_gedcom_setting(WT_GED_ID, 'FAM_FACTS_UNIQUE' )),
+ explode(',', get_gedcom_setting(WT_GED_ID, 'NOTE_FACTS_ADD')), explode(',', get_gedcom_setting(WT_GED_ID, 'NOTE_FACTS_UNIQUE')),
+ explode(',', get_gedcom_setting(WT_GED_ID, 'SOUR_FACTS_ADD')), explode(',', get_gedcom_setting(WT_GED_ID, 'SOUR_FACTS_UNIQUE')),
+ explode(',', get_gedcom_setting(WT_GED_ID, 'REPO_FACTS_ADD')), explode(',', get_gedcom_setting(WT_GED_ID, 'REPO_FACTS_UNIQUE')),
+ array('SOUR', 'REPO', 'OBJE', '_PRIM', '_THUM', 'NOTE', 'SUBM', 'SUBN')
+ ));
+
+ foreach ($tags as $tag) {
+ if ($tag) {
+ $all_tags[$tag]=translate_fact($tag);
+ }
+ }
+
+ uasort($all_tags, 'utf8_strcasecmp');
+
+ echo '<tr><td class="optionbox" width="*">';
+ echo '<input type="text" class="pedigree_form" name="xref" id="xref" size="6" />';
+ print_findindi_link("xref","");
+ print_findfamily_link("xref");
+ print_findsource_link("xref");
+ print_findrepository_link("xref");
+ print_findmedia_link("xref", "1media");
+ echo '</td><td class="optionbox" width="*">';
+ echo select_edit_control('tag_type', $all_tags, '', null, null);
+ echo '</td><td class="optionbox" width="1">';
+ echo select_edit_control('resn', $PRIVACY_CONSTANTS, null, 'privacy', null);
+ echo '</td><td class="optionbox" width="1">';
+ echo '<input type="button" value="', i18n::translate('Add'), '" onClick="document.configform.elements[\'action\'].value=\'add\';document.configform.submit();" />';
+ echo '<input type="hidden" name="default_resn_id" value="">'; // value set by JS
+ echo '</td></tr>';
+ $rows=WT_DB::prepare(
+ "SELECT default_resn_id, tag_type, xref, resn".
+ " FROM `##default_resn`".
+ " WHERE gedcom_id=?".
+ " ORDER BY xref IS NULL, tag_type IS NULL, xref, tag_type"
+ )->execute(array(WT_GED_ID))->fetchAll();
+ foreach ($rows as $row) {
+ echo '<tr><td class="optionbox" width="*">';
+ if ($row->xref) {
+ $record=GedcomRecord::getInstance($row->xref);
+ if ($record) {
+ $name=$record->getFullName();
+ } else {
+ $name=i18n::translate('this record does not exist');
+ }
+ // I18N: e.g. John DOE (I1234)
+ echo i18n::translate('%1$s (%2$s)', $name, $row->xref);
+ } else {
+ echo '&nbsp;';
+ }
+ echo '</td><td class="optionbox" width="*">';
+ if ($row->tag_type) {
+ // I18N: e.g. Marriage (MARR)
+ echo i18n::translate('%1$s [%2$s]', translate_fact($row->tag_type), $row->tag_type);
+ } else {
+ echo '&nbsp;';
+ }
+ echo '</td><td class="optionbox" width="1">';
+ echo $PRIVACY_CONSTANTS[$row->resn];
+ echo '</td><td class="optionbox" width="1">';
+ echo '<input type="button" value="', i18n::translate('Delete'), '" onClick="document.configform.elements[\'action\'].value=\'delete\';document.configform.elements[\'default_resn_id\'].value=\''.$row->default_resn_id.'\';document.configform.submit();" />';
+ echo '</td></tr>';
+ }
+ echo '</table>';
+ ?>
+ </div>
+ <!-- MULTIMEDIA -->
+ <div id="config-media">
+ <table class="facts_table">
+ <tr>
+ <td class="descriptionbox nowrap">
+ <?php echo i18n::translate('Enable multimedia features'), help_link('MULTI_MEDIA'); ?>
+ </td>
+ <td class="optionbox width60">
+ <?php echo edit_field_yes_no('NEW_MULTI_MEDIA', get_gedcom_setting(WT_GED_ID, 'MULTI_MEDIA')); ?>
+ </td>
+ </tr>
+ <tr>
+ <td class="subbar" colspan="2"><?php echo i18n::translate('General'); ?></td>
+ </tr>
+ <tr>
+ <td class="descriptionbox nowrap">
+ <?php echo i18n::translate('Keep links'), help_link('MEDIA_EXTERNAL'); ?>
+ </td>
+ <td class="optionbox width60">
+ <?php echo edit_field_yes_no('NEW_MEDIA_EXTERNAL', get_gedcom_setting(WT_GED_ID, 'MEDIA_EXTERNAL')); ?>
+ </td>
+ </tr>
+ <tr>
+ <td class="descriptionbox nowrap">
+ <?php echo i18n::translate('Multimedia directory'), help_link('MEDIA_DIRECTORY'); ?>
+ </td>
+ <td class="optionbox width60">
+ <input type="text" size="50" name="NEW_MEDIA_DIRECTORY" value="<?php echo $MEDIA_DIRECTORY; ?>" dir="ltr" />
+ <?php if (preg_match("/.*[a-zA-Z]{1}:.*/", $MEDIA_DIRECTORY)>0) echo "<span class=\"error\">".i18n::translate('Media path should not contain a drive letter; media may not be displayed.')."</span>"; ?>
+ </td>
+ </tr>
+ <tr>
+ <td class="descriptionbox nowrap">
+ <?php echo i18n::translate('Multi-Media directory levels to keep'), help_link('MEDIA_DIRECTORY_LEVELS'); ?>
+ </td>
+ <td class="optionbox width60">
+ <input type="text" name="NEW_MEDIA_DIRECTORY_LEVELS" value="<?php echo $MEDIA_DIRECTORY_LEVELS; ?>" size="5" />
+ </td>
+ </tr>
+ <tr>
+ <td class="descriptionbox nowrap">
+ <?php echo i18n::translate('Width of generated thumbnails'), help_link('THUMBNAIL_WIDTH'); ?>
+ </td>
+ <td class="optionbox width60">
+ <input type="text" name="NEW_THUMBNAIL_WIDTH" value="<?php echo $THUMBNAIL_WIDTH; ?>" size="5" />
+ </td>
+ </tr>
+ <tr>
+ <td class="descriptionbox nowrap">
+ <?php echo i18n::translate('Automatically generate thumbnails'), help_link('AUTO_GENERATE_THUMBS'); ?>
+ </td>
+ <td class="optionbox width60">
+ <?php echo edit_field_yes_no('NEW_AUTO_GENERATE_THUMBS', get_gedcom_setting(WT_GED_ID, 'AUTO_GENERATE_THUMBS')); ?>
+ </td>
+ </tr>
+ <tr>
+ <td class="descriptionbox nowrap">
+ <?php echo i18n::translate('Use thumbnail'), help_link('USE_THUMBS_MAIN'); ?>
+ </td>
+ <td class="optionbox width60">
+ <?php echo edit_field_yes_no('NEW_USE_THUMBS_MAIN', get_gedcom_setting(WT_GED_ID, 'USE_THUMBS_MAIN')); ?>
+ </td>
+ </tr>
+ <tr>
+ <td class="descriptionbox nowrap">
+ <?php echo i18n::translate('Use silhouettes'), help_link('USE_SILHOUETTE'); ?>
+ </td>
+ <td class="optionbox width60">
+ <?php echo edit_field_yes_no('NEW_USE_SILHOUETTE', get_gedcom_setting(WT_GED_ID, 'USE_SILHOUETTE')); ?>
+ </td>
+ </tr>
+ <tr>
+ <td class="descriptionbox nowrap">
+ <?php echo i18n::translate('Show highlight images in people boxes'), help_link('SHOW_HIGHLIGHT_IMAGES'); ?>
+ </td>
+ <td class="optionbox width60">
+ <?php echo edit_field_yes_no('NEW_SHOW_HIGHLIGHT_IMAGES', get_gedcom_setting(WT_GED_ID, 'SHOW_HIGHLIGHT_IMAGES')); ?>
+ </td>
+ </tr>
+ <tr>
+ <td class="descriptionbox nowrap">
+ <?php echo i18n::translate('Use media viewer'), help_link('USE_MEDIA_VIEWER'); ?>
+ </td>
+ <td class="optionbox width60">
+ <?php echo edit_field_yes_no('NEW_USE_MEDIA_VIEWER', get_gedcom_setting(WT_GED_ID, 'USE_MEDIA_VIEWER')); ?>
+ </td>
+ </tr>
+ <tr>
+ <td class="descriptionbox nowrap">
+ <?php echo i18n::translate('Show file name in media viewer'), help_link('SHOW_MEDIA_FILENAME'); ?>
+ </td>
+ <td class="optionbox width60">
+ <?php echo edit_field_yes_no('NEW_SHOW_MEDIA_FILENAME', get_gedcom_setting(WT_GED_ID, 'SHOW_MEDIA_FILENAME')); ?>
+ </td>
+ </tr>
+ <tr>
+ <td class="descriptionbox nowrap">
+ <?php echo i18n::translate('Show download link in media viewer'), help_link('SHOW_MEDIA_DOWNLOAD'); ?>
+ </td>
+ <td class="optionbox width60">
+ <?php echo edit_field_yes_no('NEW_SHOW_MEDIA_DOWNLOAD', get_gedcom_setting(WT_GED_ID, 'SHOW_MEDIA_DOWNLOAD')); ?>
+ </td>
+ </tr>
+ <tr>
+ <td class="subbar" colspan="2">
+ <?php echo i18n::translate('Media Firewall'); ?>
+ </td>
+ </tr>
+ <tr>
+ <td class="descriptionbox nowrap">
+ <?php echo i18n::translate('Use media firewall'), help_link('USE_MEDIA_FIREWALL'); ?>
+ </td>
+ <td class="optionbox width60">
+ <?php echo edit_field_yes_no('NEW_USE_MEDIA_FIREWALL', get_gedcom_setting(WT_GED_ID, 'USE_MEDIA_FIREWALL')); ?>
+ </td>
+ </tr>
+ <tr>
+ <td class="descriptionbox nowrap">
+ <?php echo i18n::translate('Media firewall root directory'), help_link('MEDIA_FIREWALL_ROOTDIR'); ?>
+ </td>
+ <td class="optionbox width60">
+ <input type="text" name="NEW_MEDIA_FIREWALL_ROOTDIR" size="50" dir="ltr" value="<?php echo ($MEDIA_FIREWALL_ROOTDIR == $INDEX_DIRECTORY) ? "" : $MEDIA_FIREWALL_ROOTDIR; ?>" /><br />
+ <?php echo i18n::translate('When this field is empty, the <b>%s</b> directory will be used.', $INDEX_DIRECTORY); ?>
+ </td>
+ </tr>
+ <tr>
+ <td class="descriptionbox nowrap">
+ <?php echo i18n::translate('Protect thumbnails of protected images'), help_link('MEDIA_FIREWALL_THUMBS'); ?>
+ </td>
+ <td class="optionbox width60">
+ <?php echo edit_field_yes_no('NEW_MEDIA_FIREWALL_THUMBS', get_gedcom_setting(WT_GED_ID, 'MEDIA_FIREWALL_THUMBS')); ?>
+ </td>
+ </tr>
+ <tr>
+ <td class="descriptionbox nowrap">
+ <?php echo i18n::translate('Who can view non-watermarked images?'), help_link('SHOW_NO_WATERMARK'); ?>
+ </td>
+ <td class="optionbox width60">
+ <?php echo edit_field_access_level("NEW_SHOW_NO_WATERMARK", $SHOW_NO_WATERMARK); ?>
+ </td>
+ </tr>
+ <tr>
+ <td class="descriptionbox nowrap">
+ <?php echo i18n::translate('Add watermarks to thumbnails?'), help_link('WATERMARK_THUMB'); ?>
+ </td>
+ <td class="optionbox width60">
+ <?php echo edit_field_yes_no('NEW_WATERMARK_THUMB', get_gedcom_setting(WT_GED_ID, 'WATERMARK_THUMB')); ?>
+ </td>
+ </tr>
+ <tr>
+ <td class="descriptionbox nowrap">
+ <?php echo i18n::translate('Store watermarked full size images on server?'), help_link('SAVE_WATERMARK_IMAGE'); ?>
+ </td>
+ <td class="optionbox width60">
+ <?php echo edit_field_yes_no('NEW_SAVE_WATERMARK_IMAGE', get_gedcom_setting(WT_GED_ID, 'SAVE_WATERMARK_IMAGE')); ?>
+ </td>
+ </tr>
+ <tr>
+ <td class="descriptionbox nowrap">
+ <?php echo i18n::translate('Store watermarked thumbnails on server?'), help_link('SAVE_WATERMARK_THUMB'); ?>
+ </td>
+ <td class="optionbox width60">
+ <?php echo edit_field_yes_no('NEW_SAVE_WATERMARK_THUMB', get_gedcom_setting(WT_GED_ID, 'SAVE_WATERMARK_THUMB')); ?>
+ </td>
+ </tr>
+ </table>
+ </div>
+ <!-- ACCESS -->
+ <div id="access-options">
+ <table class="facts_table">
+ <tr>
+ <td class="subbar" colspan="2">
+ <?php echo i18n::translate('Visitor options'); ?>
+ </td>
+ </tr>
+ <tr>
+ <td class="descriptionbox nowrap">
+ <?php echo i18n::translate('Require visitor authentication'), help_link('REQUIRE_AUTHENTICATION'); ?>
+ </td>
+ <td class="optionbox width60">
+ <?php echo edit_field_yes_no('NEW_REQUIRE_AUTHENTICATION', get_gedcom_setting(WT_GED_ID, 'REQUIRE_AUTHENTICATION')); ?>
+ </td>
+ </tr>
+ <tr>
+ <td class="descriptionbox nowrap">
+ <?php echo i18n::translate('Welcome text on login page'), help_link('WELCOME_TEXT_AUTH_MODE'); ?>
+ </td>
+ <td class="optionbox width60"><select name="NEW_WELCOME_TEXT_AUTH_MODE">
+ <option value="0" <?php if ($WELCOME_TEXT_AUTH_MODE=='0') echo "selected=\"selected\""; ?>><?php echo i18n::translate('No predefined text'); ?></option>
+ <option value="1" <?php if ($WELCOME_TEXT_AUTH_MODE=='1') echo "selected=\"selected\""; ?>><?php echo i18n::translate('Predefined text that states all users can request a user account'); ?></option>
+ <option value="2" <?php if ($WELCOME_TEXT_AUTH_MODE=='2') echo "selected=\"selected\""; ?>><?php echo i18n::translate('Predefined text that states admin will decide on each request for a user account'); ?></option>
+ <option value="3" <?php if ($WELCOME_TEXT_AUTH_MODE=='3') echo "selected=\"selected\""; ?>><?php echo i18n::translate('Predefined text that states only family members can request a user account'); ?></option>
+ <option value="4" <?php if ($WELCOME_TEXT_AUTH_MODE=='4') echo "selected=\"selected\""; ?>><?php echo i18n::translate('Choose user defined welcome text typed below'); ?></option>
+ </select>
+ </td>
+ </tr>
+ <tr>
+ <td class="descriptionbox nowrap">
+ <?php echo i18n::translate('Standard header for custom welcome text'), help_link('WELCOME_TEXT_AUTH_MODE_CUST_HEAD'); ?>
+ </td>
+ <td class="optionbox width60">
+ <?php echo edit_field_yes_no('NEW_WELCOME_TEXT_CUST_HEAD', get_gedcom_setting(WT_GED_ID, 'WELCOME_TEXT_CUST_HEAD')); ?>
+ </td>
+ </tr>
+ <tr>
+ <td class="descriptionbox nowrap">
+ <?php echo i18n::translate('Custom welcome text'), help_link('WELCOME_TEXT_AUTH_MODE_CUST'); ?>
+ </td>
+ <td class="optionbox width60">
+ <textarea name="NEW_WELCOME_TEXT_AUTH_MODE_4" rows="5" cols="60"><?php echo get_gedcom_setting(WT_GED_ID, 'WELCOME_TEXT_AUTH_MODE_'.WT_LOCALE); ?></textarea>
+ </td>
+ </tr>
+ <tr>
+ <td class="descriptionbox nowrap">
+ <?php echo i18n::translate('Show acceptable use agreement on «Request new user account» page'), help_link('SHOW_REGISTER_CAUTION'); ?>
+ </td>
+ <td class="optionbox width60">
+ <?php echo edit_field_yes_no('NEW_SHOW_REGISTER_CAUTION', get_gedcom_setting(WT_GED_ID, 'SHOW_REGISTER_CAUTION')); ?>
+ </td>
+ </tr>
+ <tr>
+ <td class="subbar" colspan="2">
+ <?php echo i18n::translate('User options'); ?>
+ </td>
+ </tr>
+ <tr>
+ <td class="descriptionbox nowrap">
+ <?php echo i18n::translate('Contextual Help links'), help_link('SHOW_CONTEXT_HELP'); ?>
+ </td>
+ <td class="optionbox width60">
+ <?php echo radio_buttons('NEW_SHOW_CONTEXT_HELP', array(false=>i18n::translate('hide'),true=>i18n::translate('show')), get_gedcom_setting(WT_GED_ID, 'SHOW_CONTEXT_HELP')); ?>
+ </td>
+ </tr>
+ <tr>
+ <td class="descriptionbox nowrap">
+ <?php echo i18n::translate('Theme dropdown selector for theme changes'), help_link('ALLOW_THEME_DROPDOWN'); ?>
+ </td>
+ <td class="optionbox width60">
+ <?php echo radio_buttons('NEW_ALLOW_THEME_DROPDOWN', array(false=>i18n::translate('hide'),true=>i18n::translate('show')), $ALLOW_THEME_DROPDOWN); ?>
+ </td>
+ </tr>
+ <tr>
+ <td class="descriptionbox nowrap">
+ <?php echo i18n::translate('Default Theme'), help_link('THEME'); ?>
+ </td>
+ <td class="optionbox width60">
+ <select name="NEW_THEME_DIR">
+ <?php
+ echo '<option value="">', i18n::translate('&lt;default theme&gt;'), '</option>';
+ $current_themedir=get_gedcom_setting(WT_GED_ID, 'THEME_DIR');
+ foreach (get_theme_names() as $themename=>$themedir) {
+ echo '<option value="', $themedir, '"';
+ if ($themedir==$current_themedir) {
+ echo ' selected="selected"';
+ }
+ echo '>', $themename, '</option>';
+ }
+ ?>
+ </select>
+ </td>
+ </tr>
+ </table>
+ </div>
+ <!-- LAYOUT -->
+ <div id="layout-options">
+ <table class="facts_table">
+ <tr>
+ <td class="subbar" colspan="2">
+ <?php echo i18n::translate('Names'); ?>
+ </td>
+ </tr>
+ <tr>
+ <td class="descriptionbox nowrap">
+ <?php echo i18n::translate('Underline names in quotes'), help_link('UNDERLINE_NAME_QUOTES'); ?>
+ </td>
+ <td class="optionbox width60">
+ <?php echo edit_field_yes_no('NEW_UNDERLINE_NAME_QUOTES', get_gedcom_setting(WT_GED_ID, 'UNDERLINE_NAME_QUOTES')); ?>
+ </td>
+ </tr>
+ <tr>
+ <td class="descriptionbox nowrap">
+ <?php echo i18n::translate('Show married names on individual list'), help_link('SHOW_MARRIED_NAMES'); ?>
+ </td>
+ <td class="optionbox width60">
+ <?php echo edit_field_yes_no('NEW_SHOW_MARRIED_NAMES', get_gedcom_setting(WT_GED_ID, 'SHOW_MARRIED_NAMES')); ?>
+ </td>
+ </tr>
+ <td class="descriptionbox nowrap">
+ <?php echo i18n::translate('Min. no. of occurrences to be a "common surname"'), help_link('COMMON_NAMES_THRESHOLD'); ?>
+ </td>
+ <td class="optionbox width60">
+ <input type="text" name="NEW_COMMON_NAMES_THRESHOLD" value="<?php echo get_gedcom_setting(WT_GED_ID, 'COMMON_NAMES_THRESHOLD'); ?>" size="5" />
+ </td>
+ </tr>
+ <tr>
+ <td class="descriptionbox nowrap">
+ <?php echo i18n::translate('Names to add to common surnames (comma separated)'), help_link('COMMON_NAMES_ADD'); ?>
+ </td>
+ <td class="optionbox width60">
+ <input type="text" name="NEW_COMMON_NAMES_ADD" dir="ltr" value="<?php echo get_gedcom_setting(WT_GED_ID, 'COMMON_NAMES_ADD'); ?>" size="50" />
+ </td>
+ </tr>
+ <tr>
+ <td class="descriptionbox nowrap">
+ <?php echo i18n::translate('Names to remove from common surnames (comma separated)'), help_link('COMMON_NAMES_REMOVE'); ?>
+ </td>
+ <td class="optionbox width60">
+ <input type="text" name="NEW_COMMON_NAMES_REMOVE" dir="ltr" value="<?php echo get_gedcom_setting(WT_GED_ID, 'COMMON_NAMES_REMOVE'); ?>" size="50" />
+ </td>
+ </tr>
+ <tr>
+ <td class="descriptionbox nowrap">
+ <?php echo i18n::translate('Surname list style'), help_link('SURNAME_LIST_STYLE'); ?>
+ </td>
+ <td class="optionbox width60">
+ <select name="NEW_SURNAME_LIST_STYLE">
+ <option value="style1" <?php if ($SURNAME_LIST_STYLE=="style1") echo "selected=\"selected\""; ?>><?php echo i18n::translate('list'); ?></option>
+ <option value="style2" <?php if ($SURNAME_LIST_STYLE=="style2") echo "selected=\"selected\""; ?>><?php echo i18n::translate('table'); ?></option>
+ <option value="style3" <?php if ($SURNAME_LIST_STYLE=="style3") echo "selected=\"selected\""; ?>><?php echo i18n::translate('tag cloud'); ?></option>
+ </select>
+ </td>
+ </tr>
+ <tr>
+ <td class="descriptionbox nowrap">
+ <?php echo i18n::translate('Maximum number of surnames on individual list'), help_link('SUBLIST_TRIGGER_I'); ?>
+ </td>
+ <td class="optionbox width60">
+ <input type="text" name="NEW_SUBLIST_TRIGGER_I" value="<?php echo get_gedcom_setting(WT_GED_ID, 'SUBLIST_TRIGGER_I'); ?>" size="5" />
+ </td>
+ </tr>
+ <tr>
+ <td class="descriptionbox nowrap">
+ <?php echo i18n::translate('Maximum number of surnames on family list'), help_link('SUBLIST_TRIGGER_F'); ?>
+ </td>
+ <td class="optionbox width60">
+ <input type="text" name="NEW_SUBLIST_TRIGGER_F" value="<?php echo get_gedcom_setting(WT_GED_ID, 'SUBLIST_TRIGGER_F'); ?>" size="5" />
+ </td>
+ </tr>
+ <tr>
+ <td class="subbar" colspan="2">
+ <?php echo i18n::translate('Charts'); ?>
+ </td>
+ </tr>
+ <tr>
+ <td class="descriptionbox nowrap">
+ <?php echo i18n::translate('Default pedigree chart layout'), help_link('PEDIGREE_LAYOUT'); ?>
+ </td>
+ <td class="optionbox width60">
+ <select name="NEW_PEDIGREE_LAYOUT">
+ <option value="yes" <?php if ($PEDIGREE_LAYOUT) echo "selected=\"selected\""; ?>><?php echo i18n::translate('Landscape'); ?></option>
+ <option value="no" <?php if (!$PEDIGREE_LAYOUT) echo "selected=\"selected\""; ?>><?php echo i18n::translate('Portrait'); ?></option>
+ </select>
+ </td>
+ </tr>
+ <tr>
+ <td class="descriptionbox nowrap">
+ <?php echo i18n::translate('Default pedigree generations'), help_link('DEFAULT_PEDIGREE_GENERATIONS'); ?>
+ </td>
+ <td class="optionbox width60">
+ <input type="text" name="NEW_DEFAULT_PEDIGREE_GENERATIONS" value="<?php echo $DEFAULT_PEDIGREE_GENERATIONS; ?>" size="5" />
+ </td>
+ </tr>
+ <tr>
+ <td class="descriptionbox nowrap">
+ <?php echo i18n::translate('Maximum pedigree generations'), help_link('MAX_PEDIGREE_GENERATIONS'); ?>
+ </td>
+ <td class="optionbox width60">
+ <input type="text" name="NEW_MAX_PEDIGREE_GENERATIONS" value="<?php echo $MAX_PEDIGREE_GENERATIONS; ?>" size="5" />
+ </td>
+ </tr>
+ <tr>
+ <td class="descriptionbox nowrap">
+ <?php echo i18n::translate('Maximum descendancy generations'), help_link('MAX_DESCENDANCY_GENERATIONS'); ?>
+ </td>
+ <td class="optionbox width60">
+ <input type="text" name="NEW_MAX_DESCENDANCY_GENERATIONS" value="<?php echo $MAX_DESCENDANCY_GENERATIONS; ?>" size="5" />
+ </td>
+ </tr>
+ <tr>
+ <td class="descriptionbox nowrap">
+ <?php echo i18n::translate('Place levels to show in person boxes'), help_link('SHOW_PEDIGREE_PLACES'); ?>
+ </td>
+ <td class="optionbox width60">
+ <input type="text" size="5" name="NEW_SHOW_PEDIGREE_PLACES" value="<?php echo $SHOW_PEDIGREE_PLACES; ?>" />
+ </td>
+ </tr>
+ <tr>
+ <td class="descriptionbox nowrap">
+ <?php echo i18n::translate('Zoom boxes on charts'), help_link('ZOOM_BOXES'); ?>
+ </td>
+ <td class="optionbox width60">
+ <select name="NEW_ZOOM_BOXES">
+ <option value="disabled" <?php if ($ZOOM_BOXES=='disabled') echo "selected=\"selected\""; ?>><?php echo i18n::translate('Disabled'); ?></option>
+ <option value="mouseover" <?php if ($ZOOM_BOXES=='mouseover') echo "selected=\"selected\""; ?>><?php echo i18n::translate('On Mouse Over'); ?></option>
+ <option value="mousedown" <?php if ($ZOOM_BOXES=='mousedown') echo "selected=\"selected\""; ?>><?php echo i18n::translate('On Mouse Down'); ?></option>
+ <option value="click" <?php if ($ZOOM_BOXES=='click') echo "selected=\"selected\""; ?>><?php echo i18n::translate('On Mouse Click'); ?></option>
+ </select>
+ </td>
+ </tr>
+ <tr>
+ <td class="descriptionbox nowrap">
+ <?php echo i18n::translate('PopUp links on charts'), help_link('LINK_ICONS'); ?>
+ </td>
+ <td class="optionbox width60">
+ <select name="NEW_LINK_ICONS">
+ <option value="disabled" <?php if ($LINK_ICONS=='disabled') echo "selected=\"selected\""; ?>><?php echo i18n::translate('Disabled'); ?></option>
+ <option value="mouseover" <?php if ($LINK_ICONS=='mouseover') echo "selected=\"selected\""; ?>><?php echo i18n::translate('On Mouse Over'); ?></option>
+ <option value="click" <?php if ($LINK_ICONS=='click') echo "selected=\"selected\""; ?>><?php echo i18n::translate('On Mouse Click'); ?></option>
+ </select>
+ </td>
+ </tr>
+ <tr>
+ <td class="subbar" colspan="2">
+ <?php echo i18n::translate('Individual pages'); ?>
+ </td>
+ </tr>
+ <tr>
+ <td class="descriptionbox nowrap">
+ <?php echo i18n::translate('Default tab to show on individual page'), help_link('GEDCOM_DEFAULT_TAB'); ?>
+ </td>
+ <td class="optionbox width60">
+ <?php echo edit_field_default_tab('NEW_GEDCOM_DEFAULT_TAB', get_gedcom_setting(WT_GED_ID, 'GEDCOM_DEFAULT_TAB')); ?>
+ </td>
+ </tr>
+ <tr>
+ <td class="descriptionbox nowrap">
+ <?php echo i18n::translate('Automatically expand list of events of close relatives'), help_link('EXPAND_RELATIVES_EVENTS'); ?>
+ </td>
+ <td class="optionbox width60">
+ <?php echo edit_field_yes_no('NEW_EXPAND_RELATIVES_EVENTS', get_gedcom_setting(WT_GED_ID, 'EXPAND_RELATIVES_EVENTS')); ?>
+ </td>
+ </tr>
+ <tr>
+ <td class="descriptionbox nowrap">
+ <?php echo i18n::translate('Show events of close relatives on individual page'), help_link('SHOW_RELATIVES_EVENTS'); ?>
+ </td>
+ <td class="optionbox width60">
+ <input type="hidden" name="NEW_SHOW_RELATIVES_EVENTS" value="<?php echo $SHOW_RELATIVES_EVENTS; ?>" />
+ <table>
+ <?php
+ $rel_events=array(
+ array('_BIRT_GCHI', '_MARR_GCHI', '_DEAT_GCHI'),
+ array('_BIRT_CHIL', '_MARR_CHIL', '_DEAT_CHIL'),
+ array('_BIRT_SIBL', '_MARR_SIBL', '_DEAT_SIBL'),
+ array(null, null, '_DEAT_SPOU'),
+ array(null, '_MARR_PARE', '_DEAT_PARE'),
+ array(null, null, '_DEAT_GPAR'),
+ );
+ foreach ($rel_events as $row) {
+ echo '<tr>';
+ foreach ($row as $col) {
+ echo '<td>';
+ if (is_null($col)) {
+ echo '&nbsp;';
+ } else {
+ echo "<input type=\"checkbox\" name=\"SHOW_RELATIVES_EVENTS_checkbox\" value=\"".$col."\"";
+ if (strstr($SHOW_RELATIVES_EVENTS, $col)) {
+ echo " checked=\"checked\"";
+ }
+ echo " onchange=\"var old=document.configform.NEW_SHOW_RELATIVES_EVENTS.value; if (this.checked) old+=','+this.value; else old=old.replace(/".$col."/g,''); old=old.replace(/[,]+/gi,','); old=old.replace(/^[,]/gi,''); old=old.replace(/[,]$/gi,''); document.configform.NEW_SHOW_RELATIVES_EVENTS.value=old\" /> ";
+ echo translate_fact($col);
+ }
+ echo '</td>';
+ }
+ echo '</td>';
+ }
+ ?>
+ </table>
+ </td>
+ </tr>
+ <tr>
+ <td class="subbar" colspan="2">
+ <?php echo i18n::translate('Other'); ?>
+ </td>
+ </tr>
+ <tr>
+ <td class="descriptionbox nowrap">
+ <?php echo i18n::translate('Place levels to show on lists'), help_link('SHOW_LIST_PLACES'); ?>
+ </td>
+ <td class="optionbox width60">
+ <input type="text" size="5" name="NEW_SHOW_LIST_PLACES" value="<?php echo $SHOW_LIST_PLACES; ?>" />
+ </td>
+ </tr>
+ <tr>
+ <td class="descriptionbox nowrap">
+ <?php echo i18n::translate('Postal code position'), help_link('POSTAL_CODE'); ?>
+ </td>
+ <td class="optionbox width60">
+ <select name="NEW_POSTAL_CODE">
+ <option value="yes" <?php if ($POSTAL_CODE) echo "selected=\"selected\""; ?>><?php echo ucfirst(i18n::translate('after')); ?></option>
+ <option value="no" <?php if (!$POSTAL_CODE) echo "selected=\"selected\""; ?>><?php echo ucfirst(i18n::translate('before')); ?></option>
+ </select>
+ </td>
+ </tr>
+ </table>
+ </div>
+ <!-- HIDE & SHOW -->
+ <div id="hide-show">
+ <table class="facts_table">
+ <tr>
+ <td class="subbar" colspan="2">
+ <?php echo i18n::translate('On charts'); ?>
+ </td>
+ </tr>
+ <tr>
+ <td class="descriptionbox nowrap">
+ <?php echo i18n::translate('Empty boxes on pedigree charts'), help_link('SHOW_EMPTY_BOXES'); ?>
+ </td>
+ <td class="optionbox width60">
+ <?php echo radio_buttons('NEW_SHOW_EMPTY_BOXES', array(false=>i18n::translate('hide'),true=>i18n::translate('show')), $SHOW_EMPTY_BOXES); ?>
+ </td>
+ </tr>
+ <tr>
+ <td class="descriptionbox nowrap">
+ <?php echo i18n::translate('Abbreviate chart labels'), help_link('ABBREVIATE_CHART_LABELS'); ?>
+ </td>
+ <td class="optionbox width60">
+ <?php echo edit_field_yes_no('NEW_ABBREVIATE_CHART_LABELS', get_gedcom_setting(WT_GED_ID, 'ABBREVIATE_CHART_LABELS')); ?>
+ </td>
+ </tr>
+ <tr>
+ <td class="descriptionbox nowrap">
+ <?php echo i18n::translate('Birth and death details on charts'), help_link('PEDIGREE_FULL_DETAILS'); ?>
+ </td>
+ <td class="optionbox width60">
+ <?php echo radio_buttons('NEW_PEDIGREE_FULL_DETAILS', array(false=>i18n::translate('hide'),true=>i18n::translate('show')), $PEDIGREE_FULL_DETAILS); ?>
+ </td>
+ </tr>
+ <tr>
+ <td class="descriptionbox nowrap">
+ <?php echo i18n::translate('Gender icon on charts'), help_link('PEDIGREE_SHOW_GENDER'); ?>
+ </td>
+ <td class="optionbox width60">
+ <?php echo radio_buttons('NEW_PEDIGREE_SHOW_GENDER', array(false=>i18n::translate('hide'),true=>i18n::translate('show')), $PEDIGREE_SHOW_GENDER); ?>
+ </td>
+ </tr>
+ <tr>
+ <td class="descriptionbox nowrap">
+ <?php echo i18n::translate('Age of parents next to child\'s birthdate'), help_link('SHOW_PARENTS_AGE'); ?>
+ </td>
+ <td class="optionbox width60">
+ <?php echo radio_buttons('NEW_SHOW_PARENTS_AGE', array(false=>i18n::translate('hide'),true=>i18n::translate('show')), $SHOW_PARENTS_AGE); ?>
+ </td>
+ </tr>
+ <tr>
+ <td class="descriptionbox nowrap">
+ <?php echo i18n::translate('LDS ordinance codes in chart boxes'), help_link('SHOW_LDS_AT_GLANCE'); ?>
+ </td>
+ <td class="optionbox width60">
+ <?php echo radio_buttons('NEW_SHOW_LDS_AT_GLANCE', array(false=>i18n::translate('hide'),true=>i18n::translate('show')), $SHOW_LDS_AT_GLANCE); ?>
+ </td>
+ </tr>
+ <tr>
+ <td class="descriptionbox nowrap">
+ <?php echo i18n::translate('Other facts to show in charts'), help_link('CHART_BOX_TAGS'); ?>
+ </td>
+ <td class="optionbox width60">
+ <input type="text" size="50" id="NEW_CHART_BOX_TAGS" name="NEW_CHART_BOX_TAGS" value="<?php echo $CHART_BOX_TAGS; ?>" dir="ltr" /><?php print_findfact_link("NEW_CHART_BOX_TAGS", $GEDCOM); ?>
+ </td>
+ </tr>
+ <tr>
+ <td class="subbar" colspan="2">
+ <?php echo i18n::translate('On individual pages'); ?>
+ </td>
+ </tr>
+ <tr>
+ <td class="descriptionbox nowrap">
+ <?php echo i18n::translate('Fact icons'), help_link('SHOW_FACT_ICONS'); ?>
+ </td>
+ <td class="optionbox width60">
+ <?php echo radio_buttons('NEW_SHOW_FACT_ICONS', array(false=>i18n::translate('hide'),true=>i18n::translate('show')), $SHOW_FACT_ICONS); ?>
+ </td>
+ </tr>
+ <tr>
+ <td class="descriptionbox nowrap">
+ <?php echo i18n::translate('Automatically expand notes'), help_link('EXPAND_NOTES'); ?>
+ </td>
+ <td class="optionbox width60">
+ <?php echo edit_field_yes_no('NEW_EXPAND_NOTES', get_gedcom_setting(WT_GED_ID, 'EXPAND_NOTES')); ?>
+ </td>
+ </tr>
+ <tr>
+ <td class="descriptionbox nowrap">
+ <?php echo i18n::translate('Automatically expand sources'), help_link('EXPAND_SOURCES'); ?>
+ </td>
+ <td class="optionbox width60">
+ <?php echo edit_field_yes_no('NEW_EXPAND_SOURCES', get_gedcom_setting(WT_GED_ID, 'EXPAND_SOURCES')); ?>
+ </td>
+ </tr>
+ <tr>
+ <td class="descriptionbox nowrap">
+ <?php echo i18n::translate('Show all notes and source references on notes and sources tabs'), help_link('SHOW_LEVEL2_NOTES'); ?>
+ </td>
+ <td class="optionbox width60">
+ <?php echo edit_field_yes_no('NEW_SHOW_LEVEL2_NOTES', get_gedcom_setting(WT_GED_ID, 'SHOW_LEVEL2_NOTES')); ?>
+ </td>
+ </tr>
+ <tr>
+ <td class="descriptionbox nowrap">
+ <?php echo i18n::translate('Date differences'), help_link('SHOW_AGE_DIFF'); ?>
+ </td>
+ <td class="optionbox width60">
+ <?php echo radio_buttons('NEW_SHOW_AGE_DIFF', array(false=>i18n::translate('hide'),true=>i18n::translate('show')), $SHOW_AGE_DIFF); ?>
+ </td>
+ </tr>
+ <tr>
+ <td class="descriptionbox nowrap">
+ <?php echo i18n::translate('Estimated dates for birth and death'), help_link('SHOW_EST_LIST_DATES'); ?>
+ </td>
+ <td class="optionbox width60">
+ <?php echo radio_buttons('NEW_SHOW_EST_LIST_DATES', array(false=>i18n::translate('hide'),true=>i18n::translate('show')), get_gedcom_setting(WT_GED_ID, 'SHOW_EST_LIST_DATES')); ?>
+ </td>
+ </tr>
+ <tr>
+ <td class="subbar" colspan="2">
+ <?php echo i18n::translate('General'); ?>
+ </td>
+ </tr>
+ <tr>
+ <td class="descriptionbox nowrap">
+ <?php echo i18n::translate('Allow users to see raw GEDCOM records'), help_link('SHOW_GEDCOM_RECORD'); ?>
+ </td>
+ <td class="optionbox width60">
+ <?php echo edit_field_yes_no('NEW_SHOW_GEDCOM_RECORD', get_gedcom_setting(WT_GED_ID, 'SHOW_GEDCOM_RECORD')); ?>
+ </td>
+ </tr>
+ <tr>
+ <td class="descriptionbox nowrap">
+ <?php echo i18n::translate('GEDCOM errors'), help_link('HIDE_GEDCOM_ERRORS'); ?>
+ </td>
+ <td class="optionbox width60">
+ <?php echo radio_buttons('NEW_HIDE_GEDCOM_ERRORS', array(false=>i18n::translate('hide'),true=>i18n::translate('show')), !$HIDE_GEDCOM_ERRORS); /* Note: name of object is reverse of description */ ?>
+ </td>
+ </tr>
+ <tr>
+ <td class="descriptionbox nowrap">
+ <?php echo i18n::translate('Hit counters'), help_link('SHOW_COUNTER'); ?>
+ </td>
+ <td class="optionbox width60">
+ <?php echo radio_buttons('NEW_SHOW_COUNTER', array(false=>i18n::translate('hide'),true=>i18n::translate('show')), $SHOW_COUNTER); ?>
+ </td>
+ </tr>
+ <tr>
+ <td class="descriptionbox nowrap">
+ <?php echo i18n::translate('Spider tagline'), help_link('SHOW_SPIDER_TAGLINE'); ?>
+ </td>
+ <td class="optionbox width60">
+ <?php echo radio_buttons('NEW_SHOW_SPIDER_TAGLINE', array(false=>i18n::translate('hide'),true=>i18n::translate('show')), $SHOW_SPIDER_TAGLINE); ?>
+ </td>
+ </tr>
+ <tr>
+ <td class="descriptionbox nowrap">
+ <?php echo i18n::translate('Execution statistics'), help_link('SHOW_STATS'); ?>
+ </td>
+ <td class="optionbox width60">
+ <?php echo radio_buttons('NEW_SHOW_STATS', array(false=>i18n::translate('hide'),true=>i18n::translate('show')), $SHOW_STATS); ?>
+ </td>
+ </tr>
+ <tr>
+ <td class="descriptionbox nowrap">
+ <?php echo i18n::translate('GEDCOM record last change date on lists'), help_link('SHOW_LAST_CHANGE'); ?>
+ </td>
+ <td class="optionbox width60">
+ <?php echo radio_buttons('NEW_SHOW_LAST_CHANGE', array(false=>i18n::translate('hide'),true=>i18n::translate('show')), $SHOW_LAST_CHANGE); ?>
+ </td>
+ </tr>
+ </table>
+ </div>
+ <!-- EDIT -->
+ <div id="edit-options">
+ <table class="facts_table">
+ <tr>
+ <td class="descriptionbox nowrap">
+ <?php echo i18n::translate('Online editing'), help_link('ALLOW_EDIT_GEDCOM'); ?>
+ </td>
+ <td class="optionbox width60"><?php echo radio_buttons('NEW_ALLOW_EDIT_GEDCOM', array(false=>i18n::translate('disable'),true=>i18n::translate('enable')), $ALLOW_EDIT_GEDCOM); ?>
+ </td>
+ </tr>
+ <tr>
+ <td class="subbar" colspan="2">
+ <?php echo i18n::translate('Facts for Individual records'); ?>
+ </td>
+ </tr>
+ <tr>
+ <td class="descriptionbox nowrap">
+ <?php echo i18n::translate('All facts'), help_link('INDI_FACTS_ADD'); ?>
+ </td>
+ <td class="optionbox width60">
+ <input type="text" id="NEW_INDI_FACTS_ADD" name="NEW_INDI_FACTS_ADD" value="<?php echo get_gedcom_setting(WT_GED_ID, 'INDI_FACTS_ADD'); ?>" size="60" dir="ltr" /><?php print_findfact_link("NEW_INDI_FACTS_ADD", $GEDCOM); ?>
+ </td>
+ </tr>
+ <tr>
+ <td class="descriptionbox nowrap">
+ <?php echo i18n::translate('Unique facts'), help_link('INDI_FACTS_UNIQUE'); ?>
+ </td>
+ <td class="optionbox width60">
+ <input type="text" id="NEW_INDI_FACTS_UNIQUE" name="NEW_INDI_FACTS_UNIQUE" value="<?php echo get_gedcom_setting(WT_GED_ID, 'INDI_FACTS_UNIQUE'); ?>" size="60" dir="ltr" /><?php print_findfact_link("NEW_INDI_FACTS_UNIQUE", $GEDCOM); ?>
+ </td>
+ </tr>
+ <tr>
+ <td class="descriptionbox nowrap">
+ <?php echo i18n::translate('New entry facts'), help_link('QUICK_REQUIRED_FACTS'); ?>
+ </td>
+ <td class="optionbox width60">
+ <input type="text" id="NEW_QUICK_REQUIRED_FACTS" name="NEW_QUICK_REQUIRED_FACTS" value="<?php echo $QUICK_REQUIRED_FACTS; ?>" size="60" dir="ltr" /><?php print_findfact_link("NEW_QUICK_REQUIRED_FACTS", $GEDCOM); ?>
+ </td>
+ </tr>
+ <tr>
+ <td class="descriptionbox nowrap">
+ <?php echo i18n::translate('Quick facts'), help_link('INDI_FACTS_QUICK'); ?>
+ </td>
+ <td class="optionbox width60">
+ <input type="text" id="NEW_INDI_FACTS_QUICK" name="NEW_INDI_FACTS_QUICK" value="<?php echo get_gedcom_setting(WT_GED_ID, 'INDI_FACTS_QUICK'); ?>" size="60" dir="ltr" /><?php print_findfact_link("NEW_INDI_FACTS_QUICK", $GEDCOM); ?>
+ </td>
+ </tr>
+ <tr>
+ <td class="subbar" colspan="2">
+ <?php echo i18n::translate('Facts for Family records'); ?>
+ </td>
+ </tr>
+ <tr>
+ <td class="descriptionbox nowrap">
+ <?php echo i18n::translate('All facts'), help_link('FAM_FACTS_ADD'); ?>
+ </td>
+ <td class="optionbox width60">
+ <input type="text" id="NEW_FAM_FACTS_ADD" name="NEW_FAM_FACTS_ADD" value="<?php echo get_gedcom_setting(WT_GED_ID, 'FAM_FACTS_ADD'); ?>" size="60" dir="ltr" /><?php print_findfact_link("NEW_FAM_FACTS_ADD", $GEDCOM); ?>
+ </td>
+ </tr>
+ <tr>
+ <td class="descriptionbox nowrap">
+ <?php echo i18n::translate('Unique facts'), help_link('FAM_FACTS_UNIQUE'); ?>
+ </td>
+ <td class="optionbox width60">
+ <input type="text" id="NEW_FAM_FACTS_UNIQUE" name="NEW_FAM_FACTS_UNIQUE" value="<?php echo get_gedcom_setting(WT_GED_ID, 'FAM_FACTS_UNIQUE'); ?>" size="60" dir="ltr" /><?php print_findfact_link("NEW_FAM_FACTS_UNIQUE", $GEDCOM); ?>
+ </td>
+ </tr>
+ <tr>
+ <td class="descriptionbox nowrap">
+ <?php echo i18n::translate('New entry facts'), help_link('QUICK_REQUIRED_FAMFACTS'); ?>
+ </td>
+ <td class="optionbox width60">
+ <input type="text" id="NEW_QUICK_REQUIRED_FAMFACTS" name="NEW_QUICK_REQUIRED_FAMFACTS" value="<?php echo $QUICK_REQUIRED_FAMFACTS; ?>" size="60" dir="ltr" /><?php print_findfact_link("NEW_QUICK_REQUIRED_FAMFACTS", $GEDCOM); ?>
+ </td>
+ </tr>
+ <tr>
+ <td class="descriptionbox nowrap">
+ <?php echo i18n::translate('Quick facts'), help_link('FAM_FACTS_QUICK'); ?>
+ </td>
+ <td class="optionbox width60">
+ <input type="text" id="NEW_FAM_FACTS_QUICK" name="NEW_FAM_FACTS_QUICK" value="<?php echo get_gedcom_setting(WT_GED_ID, 'FAM_FACTS_QUICK'); ?>" size="60" dir="ltr" /><?php print_findfact_link("NEW_FAM_FACTS_QUICK", $GEDCOM); ?>
+ </td>
+ </tr>
+ <tr>
+ <td class="subbar" colspan="2">
+ <?php echo i18n::translate('Facts for Source records'); ?>
+ </td>
+ </tr>
+ <tr>
+ <td class="descriptionbox nowrap">
+ <?php echo i18n::translate('All facts'), help_link('SOUR_FACTS_ADD'); ?>
+ </td>
+ <td class="optionbox width60">
+ <input type="text" id="NEW_SOUR_FACTS_ADD" name="NEW_SOUR_FACTS_ADD" value="<?php echo get_gedcom_setting(WT_GED_ID, 'SOUR_FACTS_ADD'); ?>" size="60" dir="ltr" /><?php print_findfact_link("NEW_SOUR_FACTS_ADD", $GEDCOM); ?>
+ </td>
+ </tr>
+ <tr>
+ <td class="descriptionbox nowrap">
+ <?php echo i18n::translate('Unique facts'), help_link('SOUR_FACTS_UNIQUE'); ?>
+ </td>
+ <td class="optionbox width60">
+ <input type="text" id="NEW_SOUR_FACTS_UNIQUE" name="NEW_SOUR_FACTS_UNIQUE" value="<?php echo get_gedcom_setting(WT_GED_ID, 'SOUR_FACTS_UNIQUE'); ?>" size="60" dir="ltr" /><?php print_findfact_link("NEW_SOUR_FACTS_UNIQUE", $GEDCOM); ?>
+ </td>
+ </tr>
+ <tr>
+ <td class="descriptionbox nowrap">
+ <?php echo i18n::translate('Quick facts'), help_link('SOUR_FACTS_QUICK'); ?>
+ </td>
+ <td class="optionbox width60">
+ <input type="text" id="NEW_SOUR_FACTS_QUICK" name="NEW_SOUR_FACTS_QUICK" value="<?php echo get_gedcom_setting(WT_GED_ID, 'SOUR_FACTS_QUICK'); ?>" size="60" dir="ltr" /><?php print_findfact_link("NEW_SOUR_FACTS_QUICK", $GEDCOM); ?>
+ </td>
+ </tr>
+ <tr>
+ <td class="subbar" colspan="2">
+ <?php echo i18n::translate('Facts for Repository records'); ?>
+ </td>
+ </tr>
+ <tr>
+ <td class="descriptionbox nowrap">
+ <?php echo i18n::translate('All facts'), help_link('REPO_FACTS_ADD'); ?>
+ </td>
+ <td class="optionbox width60">
+ <input type="text" id="NEW_REPO_FACTS_ADD" name="NEW_REPO_FACTS_ADD" value="<?php echo get_gedcom_setting(WT_GED_ID, 'REPO_FACTS_ADD'); ?>" size="60" dir="ltr" /><?php print_findfact_link("NEW_REPO_FACTS_ADD", $GEDCOM); ?>
+ </td>
+ </tr>
+ <tr>
+ <td class="descriptionbox nowrap">
+ <?php echo i18n::translate('Unique facts'), help_link('REPO_FACTS_UNIQUE'); ?>
+ </td>
+ <td class="optionbox width60">
+ <input type="text" id="NEW_REPO_FACTS_UNIQUE" name="NEW_REPO_FACTS_UNIQUE" value="<?php echo get_gedcom_setting(WT_GED_ID, 'REPO_FACTS_UNIQUE'); ?>" size="60" dir="ltr" /><?php print_findfact_link("NEW_REPO_FACTS_UNIQUE", $GEDCOM); ?>
+ </td>
+ </tr>
+ <tr>
+ <td class="descriptionbox nowrap">
+ <?php echo i18n::translate('Quick facts'), help_link('REPO_FACTS_QUICK'); ?>
+ </td>
+ <td class="optionbox width60">
+ <input type="text" id="NEW_REPO_FACTS_QUICK" name="NEW_REPO_FACTS_QUICK" value="<?php echo get_gedcom_setting(WT_GED_ID, 'REPO_FACTS_QUICK'); ?>" size="60" dir="ltr" /><?php print_findfact_link("NEW_REPO_FACTS_QUICK", $GEDCOM); ?>
+ </td>
+ </tr>
+ <tr>
+ <td class="subbar" colspan="2">
+ <?php echo i18n::translate('Advanced fact settings'); ?>
+ </td>
+ </tr>
+ <tr>
+ <td class="descriptionbox nowrap">
+ <?php echo i18n::translate('Advanced name facts'), help_link('ADVANCED_NAME_FACTS'); ?>
+ </td>
+ <td class="optionbox width60">
+ <input type="text" id="NEW_ADVANCED_NAME_FACTS" name="NEW_ADVANCED_NAME_FACTS" value="<?php echo $ADVANCED_NAME_FACTS; ?>" size="40" dir="ltr" /><?php print_findfact_link("NEW_ADVANCED_NAME_FACTS", $GEDCOM); ?>
+ </td>
+ </tr>
+ <tr>
+ <td class="descriptionbox nowrap">
+ <?php echo i18n::translate('Advanced place name facts'), help_link('ADVANCED_PLAC_FACTS'); ?>
+ </td>
+ <td class="optionbox width60">
+ <input type="text" id="NEW_ADVANCED_PLAC_FACTS" name="NEW_ADVANCED_PLAC_FACTS" value="<?php echo $ADVANCED_PLAC_FACTS; ?>" size="40" dir="ltr" /><?php print_findfact_link("NEW_ADVANCED_PLAC_FACTS", $GEDCOM); ?>
+ </td>
+ </tr>
+ <tr>
+ <td class="subbar" colspan="2">
+ <?php echo i18n::translate('Other settings'); ?>
+ </td>
+ </tr>
+ <tr>
+ <td class="descriptionbox nowrap">
+ <?php echo i18n::translate('Split places in edit mode'), help_link('SPLIT_PLACES'); ?>
+ </td>
+ <td class="optionbox width60">
+ <?php echo edit_field_yes_no('NEW_SPLIT_PLACES', get_gedcom_setting(WT_GED_ID, 'SPLIT_PLACES')); ?>
+ </td>
+ </tr>
+ <tr>
+ <td class="descriptionbox nowrap">
+ <?php echo i18n::translate('Surname tradition'), help_link('SURNAME_TRADITION'); ?>
+ </td>
+ <td class="optionbox width60">
+ <?php echo select_edit_control('NEW_SURNAME_TRADITION', array('paternal'=>i18n::translate_c('Surname tradition', 'paternal'), 'spanish'=>i18n::translate_c('Surname tradition', 'Spanish'), 'portuguese'=>i18n::translate_c('Surname tradition', 'Portuguese'), 'icelandic'=>i18n::translate_c('Surname tradition', 'Icelandic'), 'polish'=>i18n::translate_c('Surname tradition', 'Polish'), 'none'=>i18n::translate_c('Surname tradition', 'none')), null, get_gedcom_setting(WT_GED_ID, 'SURNAME_TRADITION')); ?>
+ </td>
+ </tr>
+ <tr>
+ <td class="descriptionbox nowrap">
+ <?php echo i18n::translate('Use full source citations'), help_link('FULL_SOURCES'); ?>
+ </td>
+ <td class="optionbox width60">
+ <?php echo edit_field_yes_no('NEW_FULL_SOURCES', get_gedcom_setting(WT_GED_ID, 'FULL_SOURCES')); ?>
+ </td>
+ </tr>
+ <tr>
+ <td class="descriptionbox nowrap">
+ <?php echo i18n::translate('Source type'), help_link('PREFER_LEVEL2_SOURCES'); ?>
+ </td>
+ <td class="optionbox width60">
+ <?php echo select_edit_control('NEW_PREFER_LEVEL2_SOURCES', array(0=>i18n::translate('none'), 1=>i18n::translate('facts'), 2=>i18n::translate('records')), null, get_gedcom_setting(WT_GED_ID, 'PREFER_LEVEL2_SOURCES')); ?>
+ </td>
+ </tr>
+ <tr>
+ <td class="descriptionbox nowrap">
+ <?php echo i18n::translate('Autocomplete'), help_link('ENABLE_AUTOCOMPLETE'); ?>
+ </td>
+ <td class="optionbox width60">
+ <?php echo radio_buttons('NEW_ENABLE_AUTOCOMPLETE', array(false=>i18n::translate('disable'),true=>i18n::translate('enable')), get_gedcom_setting(WT_GED_ID, 'ENABLE_AUTOCOMPLETE')); ?>
+ </td>
+ </tr>
+ <tr>
+ <td class="descriptionbox nowrap">
+ <?php echo i18n::translate('Use GeoNames database for autocomplete on places'), help_link('USE_GEONAMES'); ?>
+ </td>
+ <td class="optionbox width60">
+ <?php echo edit_field_yes_no('NEW_USE_GEONAMES', get_gedcom_setting(WT_GED_ID, 'USE_GEONAMES')); ?>
+ </td>
+ </tr>
+ <tr>
+ <td class="descriptionbox nowrap">
+ <?php echo i18n::translate('Do not update the CHAN (Last Change) record'), help_link('no_update_CHAN'); ?>
+ </td>
+ <td class="optionbox width60">
+ <?php echo edit_field_yes_no('NEW_NO_UPDATE_CHAN', get_gedcom_setting(WT_GED_ID, 'NO_UPDATE_CHAN')); ?>
+ </td>
+ </tr>
+ </table>
+ </div>
+ </td>
+ </tr>
+ <tr>
+ <td>
+ <table class="facts_table" border="0">
+ <tr>
+ <td style="padding: 5px" class="topbottombar">
+ <div class="btn">
+ <button type="submit" tabindex="<?php echo ++$i; ?>" value="<?php echo i18n::translate('Save configuration'); ?>" /><span><?php echo i18n::translate('Save configuration'); ?></span></button>
+ &nbsp;&nbsp;
+ <button type="reset" tabindex="<?php echo ++$i; ?>" value="<?php echo i18n::translate('Reset'); ?>" /><span><?php echo i18n::translate('Reset'); ?></span></button>
+ </div>
+ </td>
+ </tr>
+ </table>
+ </td>
+ </tr>
+</table>
+</form>
+<?php
+print_footer();
diff --git a/admin_trees_manage.php b/admin_trees_manage.php
new file mode 100644
index 0000000000..413f12b6b6
--- /dev/null
+++ b/admin_trees_manage.php
@@ -0,0 +1,345 @@
+<?php
+/**
+ * UI for online updating of the GEDCOM configuration.
+ *
+ * webtrees: Web based Family History software
+ * Copyright (C) 2010 webtrees development team.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ * @version $Id$
+ */
+
+define('WT_SCRIPT_NAME', 'admin_trees_manage.php');
+
+require './includes/session.php';
+
+// The gedcom admin page is for managers only!
+if (!WT_USER_GEDCOM_ADMIN) {
+ header('Location: '.WT_SERVER_NAME.WT_SCRIPT_PATH.'login.php?url='.WT_SCRIPT_NAME);
+ exit;
+}
+
+// Which directory contains our data files?
+$INDEX_DIRECTORY=get_site_setting('INDEX_DIRECTORY');
+
+// Don't allow the user to cancel the request. We do not want to be left
+// with an incomplete transaction.
+ignore_user_abort(true);
+
+function import_gedcom_file($gedcom_id, $file_name) {
+ // Read the file in blocks of roughly 64K. Ensure that each block
+ // contains complete gedcom records. This will ensure we don't split
+ // multi-byte characters, as well as simplifying the code to import
+ // each block.
+
+ $file_data='';
+ $fp=fopen($file_name, 'rb');
+
+ WT_DB::exec("START TRANSACTION");
+ WT_DB::prepare("DELETE FROM `##gedcom_chunk` WHERE gedcom_id=?")->execute(array($gedcom_id));
+
+ while (!feof($fp)) {
+ $file_data.=fread($fp, 65536);
+ // There is no strrpos() function that searches for substrings :-(
+ for ($pos=strlen($file_data)-1; $pos>0; --$pos) {
+ if ($file_data[$pos]=='0' && ($file_data[$pos-1]=="\n" || $file_data[$pos-1]=="\r")) {
+ // We've found the last record boundary in this chunk of data
+ break;
+ }
+ }
+ if ($pos) {
+ WT_DB::prepare(
+ "INSERT INTO `##gedcom_chunk` (gedcom_id, chunk_data) VALUES (?, ?)"
+ )->execute(array($gedcom_id, substr($file_data, 0, $pos)));
+ $file_data=substr($file_data, $pos);
+ }
+ }
+ WT_DB::prepare(
+ "INSERT INTO `##gedcom_chunk` (gedcom_id, chunk_data) VALUES (?, ?)"
+ )->execute(array($gedcom_id, $file_data));
+
+ WT_DB::exec("COMMIT");
+ fclose($fp);
+}
+
+// Process GET actions
+switch (safe_GET('action')) {
+case 'delete':
+ $ged=safe_GET('ged');
+ delete_gedcom(get_id_from_gedcom($ged));
+ break;
+}
+
+// Process POST actions
+switch (safe_POST('action')) {
+case 'setdefault':
+ set_site_setting('DEFAULT_GEDCOM', safe_POST('default_ged'));
+ break;
+case 'add_ged':
+ $ged_name=basename(safe_POST('ged_name'));
+ $gedcom_id=get_id_from_gedcom($ged_name);
+ // check it doesn't already exist before we create it
+ if (!$gedcom_id && file_exists($INDEX_DIRECTORY.$ged_name)) {
+ $gedcom_id=get_id_from_gedcom($ged_name, true);
+ import_gedcom_file($gedcom_id, $INDEX_DIRECTORY.$ged_name);
+ }
+ header('Location: '.WT_SERVER_NAME.WT_SCRIPT_PATH.WT_SCRIPT_NAME);
+ exit;
+case 'new_ged':
+ $ged_name=basename(safe_POST('ged_name'));
+ $gedcom_id=get_id_from_gedcom($ged_name);
+ // check it doesn't already exist before we create it
+ if (!$gedcom_id) {
+ $gedcom_id=get_id_from_gedcom($ged_name, true);
+ // I18N: This should be a common/default/placeholder name of a person. Put slashes around the surname.
+ $john_doe=i18n::translate('John /DOE/');
+ $note=i18n::translate('Edit this individual and replace their details with your own');
+ WT_DB::prepare("DELETE FROM `##gedcom_chunk` WHERE gedcom_id=?")->execute(array($gedcom_id));
+ WT_DB::prepare(
+ "INSERT INTO `##gedcom_chunk` (gedcom_id, chunk_data) VALUES (?, ?)"
+ )->execute(array(
+ $gedcom_id,
+ "0 HEAD\n0 @I1@ INDI\n1 NAME {$john_doe}\n1 SEX M\n1 BIRT\n2 DATE 01 JAN 1850\n2 NOTE {$note}\n0 TRLR\n"
+ ));
+ }
+ break;
+case 'upload_ged':
+ foreach ($_FILES as $FILE) {
+ if ($FILE['error']==0 && is_readable($FILE['tmp_name'])) {
+ $ged_name=$FILE['name'];
+ $gedcom_id=get_id_from_gedcom($ged_name);
+ // check it doesn't already exist before we create it
+ if (!$gedcom_id) {
+ $gedcom_id=get_id_from_gedcom($ged_name, true);
+ import_gedcom_file($gedcom_id, $FILE['tmp_name']);
+ }
+ }
+ }
+ header('Location: '.WT_SERVER_NAME.WT_SCRIPT_PATH.WT_SCRIPT_NAME);
+ exit;
+case 'replace_upload':
+ $gedcom_id=safe_POST('gedcom_id');
+ // Make sure the gedcom still exists
+ if (get_gedcom_from_id($gedcom_id)) {
+ foreach ($_FILES as $FILE) {
+ if ($FILE['error']==0 && is_readable($FILE['tmp_name'])) {
+ import_gedcom_file($gedcom_id, $FILE['tmp_name']);
+ }
+ }
+ }
+ header('Location: '.WT_SERVER_NAME.WT_SCRIPT_PATH.WT_SCRIPT_NAME);
+ exit;
+case 'replace_import':
+ $gedcom_id=safe_POST('gedcom_id');
+ // Make sure the gedcom still exists
+ if (get_gedcom_from_id($gedcom_id)) {
+ $ged_name=basename(safe_POST('ged_name'));
+ import_gedcom_file($gedcom_id, $INDEX_DIRECTORY.$ged_name);
+ }
+ header('Location: '.WT_SERVER_NAME.WT_SCRIPT_PATH.WT_SCRIPT_NAME);
+ exit;
+}
+
+$gedcoms=get_all_gedcoms();
+
+print_header(i18n::translate('Manage family trees'));
+
+// Process GET actions
+switch (safe_GET('action')) {
+case 'uploadform':
+case 'importform':
+ $gedcom_id=safe_GET('gedcom_id');
+ $gedcom_name=get_gedcom_from_id($gedcom_id);
+ // Check it exists
+ if (!$gedcom_name) {
+ break;
+ }
+ echo '<p>', i18n::translate('This will delete all the genealogical data from <b>%s</b> and replace it with data from another GEDCOM.', $gedcom_name), '</p>';
+ // the javascript in the next line strips any path associated with the file before comparing it to the current GEDCOM name (both Chrome and IE8 include c:\fakepath\ in the filename).
+ echo '<form name="replaceform" method="post" enctype="multipart/form-data" action="', WT_SCRIPT_NAME, '" onsubmit="var newfile = document.replaceform.ged_name.value; newfile = newfile.substr(newfile.lastIndexOf(\'\\\\\')+1); if (newfile!=\'', htmlspecialchars($gedcom_name), '\') return confirm(\'', htmlspecialchars(i18n::translate('You have selected a GEDCOM with a different name. Is this correct?')), '\'); else return true;">';
+ echo '<input type="hidden" name="gedcom_id" value="', $gedcom_id, '" />';
+ if (safe_GET('action')=='uploadform') {
+ echo '<input type="hidden" name="action" value="replace_upload" />';
+ echo '<input type="file" name="ged_name" />';
+ } else {
+ echo '<input type="hidden" name="action" value="replace_import" />';
+ $d=opendir($INDEX_DIRECTORY);
+ $files=array();
+ while (($f=readdir($d))!==false) {
+ if (!is_dir($INDEX_DIRECTORY.$f) && is_readable($INDEX_DIRECTORY.$f)) {
+ $fp=fopen($INDEX_DIRECTORY.$f, 'rb');
+ $header=fread($fp, 64);
+ fclose($fp);
+ if (preg_match('/^('.WT_UTF8_BOM.')?0 *HEAD/', $header)) {
+ $files[]=$f;
+ }
+ }
+ }
+ if ($files) {
+ echo $INDEX_DIRECTORY, '<select name="ged_name" />';
+ foreach ($files as $file) {
+ echo '<option value="', htmlspecialchars($file), '"';
+ if ($file==$gedcom_name) {
+ echo ' selected="selected"';
+ }
+ echo'>', htmlspecialchars($file), '</option>';
+ }
+ echo '</select>';
+ } else {
+ echo '<p>', i18n::translate('No GEDCOM files found. You need to copy files to the <b>%s</b> directory on your server.', $INDEX_DIRECTORY);
+ echo '</form>';
+ echo '<br /><br />';
+ echo '<form name="cancel" method="get" action="', WT_SCRIPT_NAME, '"><input type="submit" value="', i18n::translate('Cancel'), '" /></form>';
+ print_footer();
+ exit;
+ }
+ }
+ echo '<br /><br /><input type="checkbox" name="keep_media', $gedcom_id, '" value="1">';
+ echo i18n::translate('If you have created media objects in webtrees, and have edited your gedcom off-line using a program that deletes media objects, then check this box to merge the current media objects with the new GEDCOM.');
+ echo '<br /><br /><input type="submit" value="', i18n::translate('Save'), '" /></form>';
+ echo '</form>';
+ echo '<form name="cancel" method="get" action="', WT_SCRIPT_NAME, '"><input type="submit" value="', i18n::translate('Cancel'), '" /></form>';
+ print_footer();
+ exit;
+}
+
+
+// List the gedcoms available to this user
+foreach ($gedcoms as $gedcom_id=>$gedcom_name) {
+ if (userGedcomAdmin(WT_USER_ID, $gedcom_id)) {
+
+ echo
+ '<table class="gedcom_table" width="100%">',
+ '<tr><th>', i18n::translate('GEDCOM name'),
+ '</th><th a href="index.php?ctype=gedcom&ged=', rawurlencode($gedcom_name), '">', htmlspecialchars($gedcom_name), ' - ',
+ i18n::translate('%s', get_gedcom_setting($gedcom_id, 'title')), '</a>',
+ '</th></tr><tr><td>', i18n::translate('GEDCOM administration'),
+ '</td><td>';
+
+ // The third row shows an optional progress bar and a list of maintenance options
+ $importing=WT_DB::prepare(
+ "SELECT 1 FROM `##gedcom_chunk` WHERE gedcom_id=? AND imported=0 LIMIT 1"
+ )->execute(array($gedcom_id))->fetchOne();
+ if ($importing) {
+ echo
+ '<div id="import', $gedcom_id, '"></div>',
+ WT_JS_START,
+ 'jQuery("#import', $gedcom_id, '").load("import.php?gedcom_id=', $gedcom_id, '&keep_media=', safe_POST('keep_media'.$gedcom_id), '");',
+ WT_JS_END,
+ '<table border="0" width="100%" id="actions', $gedcom_id, '" style="display:none">';
+ } else {
+ echo '<table border="0" width="100%" id="actions', $gedcom_id, '">';
+ }
+ echo
+ '<tr align="center">',
+ // export
+ '<td><a href="javascript:" onclick="window.open(\'', "export_gedcom.php?export=", rawurlencode($gedcom_name), '\', \'_blank\',\'left=50,top=50,width=500,height=500,resizable=1,scrollbars=1\');">', i18n::translate('Export'), '</a>',
+ help_link('export_gedcom'),
+ '</td>',
+ // import
+ '<td><a href="', WT_SCRIPT_NAME, '?action=importform&amp;gedcom_id=', $gedcom_id, '">', i18n::translate('Import'), '</a>',
+ help_link('import_gedcom'),
+ '</td>',
+ // download
+ '<td><a href="downloadgedcom.php?ged=', rawurlencode($gedcom_name),'">', i18n::translate('Download'), '</a>',
+ help_link('download_gedcom'),
+ '</td>',
+ // upload
+ '<td><a href="', WT_SCRIPT_NAME, '?action=uploadform&amp;gedcom_id=', $gedcom_id, '">', i18n::translate('Upload'), '</a>',
+ help_link('upload_gedcom'),
+ '</td>',
+ // delete
+ '<td><a href="editgedcoms.php?action=delete&ged=', rawurlencode($gedcom_name), '" onclick="return confirm(\''.htmlspecialchars(i18n::translate('Permanently delete the GEDCOM %s and all its settings?', $gedcom_name)),'\');">', i18n::translate('Delete'), '</a>',
+ help_link('delete_gedcom'),
+ '</td></tr></table></td></tr></table><br />';
+ }
+}
+
+// Options for creating new gedcoms and setting defaults
+if (WT_USER_IS_ADMIN) {
+ echo
+ '<table class="gedcom_table2"><tr>',
+ '<th>', i18n::translate('Default GEDCOM'), help_link('default_gedcom'), '</th>',
+ '<th>', i18n::translate('Add a new GEDCOM'), help_link('add_gedcom'), '</h',
+ '<th>', i18n::translate('Upload a new GEDCOM'), help_link('upload_gedcom'), '</th>',
+ '<th>', i18n::translate('Create a new GEDCOM'), help_link('add_new_gedcom'), '</th>',
+ '</tr><tr>',
+ '<td>',
+ '<form name="defaultform" method="post" action="', WT_SCRIPT_NAME, '">',
+ '<input type="hidden" name="action" value="setdefault" />',
+ '<select name="default_ged" class="header_select" onchange="document.defaultform.submit();">';
+ $DEFAULT_GEDCOM=get_site_setting('DEFAULT_GEDCOM');
+ if (empty($DEFAULT_GEDCOM)) {
+ echo '<option value="" selected="selected"></option>';
+ }
+ foreach ($gedcoms as $gedcom_name) {
+ echo '<option value="', urlencode($gedcom_name), '"';
+ if ($DEFAULT_GEDCOM==$gedcom_name) echo ' selected="selected"';
+ echo '>', htmlspecialchars($gedcom_name), '</option>';
+ }
+ echo
+ '</select>',
+ '</form></td>',
+ '<td>',
+ '<form name="addform" method="post" action="', WT_SCRIPT_NAME, '">',
+ $INDEX_DIRECTORY,
+ '<input type="hidden" name="action" value="add_ged" />',
+ '<select name="ged_name" onchange="document.addform.submit();" />',
+ '<option>', i18n::translate('&lt;select&gt;'), '</option>',
+ $d=opendir($INDEX_DIRECTORY);
+ $files=false;
+ while (($f=readdir($d))!==false) {
+ if (!in_array($f, $gedcoms) && !is_dir($INDEX_DIRECTORY.$f) && is_readable($INDEX_DIRECTORY.$f)) {
+ $fp=fopen($INDEX_DIRECTORY.$f, 'rb');
+ $header=fread($fp, 64);
+ fclose($fp);
+ if (preg_match('/^('.WT_UTF8_BOM.')?0 *HEAD/', $header)) {
+ echo '<option>', htmlspecialchars($f), '</option>';
+ $files=true;
+ }
+ }
+ }
+ echo
+ '</select>',
+ '</form>',
+ '</td>',
+ '<td class="button">',
+ '<form name="uploadform" method="post" action="', WT_SCRIPT_NAME, '" enctype="multipart/form-data">',
+ '<input type="hidden" name="action" value="upload_ged" />',
+ '<input type="file" name="ged_name" onchange="document.uploadform.submit();" />',
+ '</form>',
+ '</td>',
+ '<td class="button">',
+ '<form name="createform" method="post" action="', WT_SCRIPT_NAME, '">',
+ '<input type="hidden" name="action" value="new_ged" />',
+ '<input name="ged_name" />',
+ ' <input type="submit" value="', i18n::translate('Save') , '"/>',
+ '</form>',
+ '</td>',
+ '</tr></table><br/>';
+
+ // display link to PGV-WT transfer wizard on first visit to this page, before any GEDCOM is loaded
+ if (count($gedcoms)==0 && get_user_count()==1) {
+ echo
+ '<div class="center">',
+ '<a style="color:green; font-weight:bold;" href="pgv_to_wt.php">',
+ i18n::translate('Click here for PhpGedView to <b>webtrees</b> transfer wizard'),
+ '</a>',
+ help_link('PGV_WIZARD'),
+ '</div>';
+ }
+}
+print_footer();
diff --git a/admin_users_add.php b/admin_users_add.php
new file mode 100644
index 0000000000..6051751735
--- /dev/null
+++ b/admin_users_add.php
@@ -0,0 +1,408 @@
+<?php
+/**
+ * Administrative User Interface.
+ *
+ * webtrees: Web based Family History software
+ * Copyright (C) 2010 webtrees development team.
+ *
+ * Derived from PhpGedView
+ * Copyright (C) 2002 to 2009 PGV Development Team. All rights reserved.
+ *
+ * Modifications Copyright (c) 2010 Greg Roach
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ * @package webtrees
+ * @subpackage Admin
+ * @version $Id$
+*/
+
+define('WT_SCRIPT_NAME', 'admin_users_add.php');
+
+require './includes/session.php';
+require WT_ROOT.'includes/functions/functions_edit.php';
+
+// Only admin users can access this page
+if (!WT_USER_IS_ADMIN) {
+ header('Location: '.WT_SERVER_NAME.WT_SCRIPT_PATH.'login.php?url='.WT_SCRIPT_NAME);
+ exit;
+}
+
+print_header(i18n::translate('Add user'));
+
+// Valid values for form variables
+$ALL_ACTIONS=array('cleanup', 'cleanup2', 'createform', 'createuser', 'deleteuser', 'edituser', 'edituser2', 'listusers');
+$ALL_THEMES_DIRS=array();
+foreach (get_theme_names() as $themename=>$themedir) {
+ $ALL_THEME_DIRS[]=$themedir;
+}
+$ALL_EDIT_OPTIONS=array(
+ 'none' => /* I18N: Listbox entry; name of a role */ i18n::translate('Visitor'),
+ 'access'=> /* I18N: Listbox entry; name of a role */ i18n::translate('Member'),
+ 'edit' => /* I18N: Listbox entry; name of a role */ i18n::translate('Editor'),
+ 'accept'=> /* I18N: Listbox entry; name of a role */ i18n::translate('Moderator'),
+ 'admin' => /* I18N: Listbox entry; name of a role */ i18n::translate('Manager')
+);
+
+// Extract form actions (GET overrides POST if both set)
+$action =safe_POST('action', $ALL_ACTIONS);
+$usrlang =safe_POST('usrlang', array_keys(i18n::installed_languages()));
+$username =safe_POST('username', WT_REGEX_USERNAME);
+$filter =safe_POST('filter' );
+$ged =safe_POST('ged' );
+
+$action =safe_GET('action', $ALL_ACTIONS, $action);
+$usrlang =safe_GET('usrlang', array_keys(i18n::installed_languages()), $usrlang);
+$username =safe_GET('username', WT_REGEX_USERNAME, $username);
+$filter =safe_GET('filter', WT_REGEX_NOSCRIPT, $filter);
+$ged =safe_GET('ged', WT_REGEX_NOSCRIPT, $ged);
+
+// Extract form variables
+$oldusername =safe_POST('oldusername', WT_REGEX_USERNAME);
+$oldemailaddress =safe_POST('oldemailaddress', WT_REGEX_EMAIL);
+$realname =safe_POST('realname' );
+$pass1 =safe_POST('pass1', WT_REGEX_PASSWORD);
+$pass2 =safe_POST('pass2', WT_REGEX_PASSWORD);
+$emailaddress =safe_POST('emailaddress', WT_REGEX_EMAIL);
+$user_theme =safe_POST('user_theme', $ALL_THEME_DIRS);
+$user_language =safe_POST('user_language', array_keys(i18n::installed_languages()), WT_LOCALE);
+$new_contact_method =safe_POST('new_contact_method');
+$new_default_tab =safe_POST('new_default_tab', array_keys(WT_Module::getActiveTabs()), get_gedcom_setting(WT_GED_ID, 'GEDCOM_DEFAULT_TAB'));
+$new_comment =safe_POST('new_comment', WT_REGEX_UNSAFE);
+$new_comment_exp =safe_POST('new_comment_exp' );
+$new_auto_accept =safe_POST_bool('new_auto_accept');
+$canadmin =safe_POST_bool('canadmin');
+$visibleonline =safe_POST_bool('visibleonline');
+$editaccount =safe_POST_bool('editaccount');
+$verified =safe_POST_bool('verified');
+$verified_by_admin =safe_POST_bool('verified_by_admin');
+
+if (empty($ged)) {
+ $ged=$GEDCOM;
+}
+
+// Load all available gedcoms
+$all_gedcoms = get_all_gedcoms();
+//-- sorting by gedcom filename
+asort($all_gedcoms);
+
+// Save new user info to the database
+if ($action=='createuser') {
+ if (($action=='createuser' && $username!=$oldusername) && get_user_id($username)) {
+ print_header(i18n::translate('Add user'));
+ echo "<span class=\"error\">", i18n::translate('Duplicate user name. A user with that user name already exists. Please choose another user name.'), "</span><br />";
+ } elseif (($action=='createuser' || $action=='edituser2' && $emailaddress!=$oldemailaddress) && get_user_by_email($emailaddress)) {
+ print_header(i18n::translate('Add user'));
+ echo "<span class=\"error\">", i18n::translate('Duplicate email address. A user with that email already exists.'), "</span><br />";
+ } else {
+ if ($pass1!=$pass2) {
+ print_header(i18n::translate('Add user'));
+ echo "<span class=\"error\">", i18n::translate('Passwords do not match.'), "</span><br />";
+ } else {
+ // New user
+ if ($action=='createuser') {
+ if ($user_id=create_user($username, $realname, $emailaddress, crypt($pass1))) {
+ set_user_setting($user_id, 'reg_timestamp', date('U'));
+ set_user_setting($user_id, 'sessiontime', '0');
+ AddToLog("User ->{$username}<- created", 'auth');
+ } else {
+ AddToLog("User ->{$username}<- was not created", 'auth');
+ $user_id=get_user_id($username);
+ }
+ } else {
+ $user_id=get_user_id($oldusername);
+ }
+ // Change password
+ if ($action=='edituser2' && !empty($pass1)) {
+ set_user_password($user_id, crypt($pass1));
+ AddToLog("User ->{$oldusername}<- had password changed", 'auth');
+ }
+ // Change username
+ if ($action=='edituser2' && $username!=$oldusername) {
+ rename_user($oldusername, $username);
+ AddToLog("User ->{$oldusername}<- renamed to ->{$username}<-", 'auth');
+ }
+ // Create/change settings that can be updated in the user's gedcom record?
+ $email_changed=($emailaddress!=getUserEmail($user_id));
+ $newly_verified=($verified_by_admin && !get_user_setting($user_id, 'verified_by_admin'));
+ // Create/change other settings
+ setUserFullName ($user_id, $realname);
+ setUserEmail ($user_id, $emailaddress);
+ set_user_setting($user_id, 'theme', $user_theme);
+ set_user_setting($user_id, 'language', $user_language);
+ set_user_setting($user_id, 'contactmethod', $new_contact_method);
+ set_user_setting($user_id, 'defaulttab', $new_default_tab);
+ set_user_setting($user_id, 'comment', $new_comment);
+ set_user_setting($user_id, 'comment_exp', $new_comment_exp);
+ set_user_setting($user_id, 'auto_accept', $new_auto_accept);
+ set_user_setting($user_id, 'canadmin', $canadmin);
+ set_user_setting($user_id, 'visibleonline', $visibleonline);
+ set_user_setting($user_id, 'editaccount', $editaccount);
+ set_user_setting($user_id, 'verified', $verified);
+ set_user_setting($user_id, 'verified_by_admin', $verified_by_admin);
+ foreach ($all_gedcoms as $ged_id=>$ged_name) {
+ set_user_gedcom_setting($user_id, $ged_id, 'gedcomid', safe_POST_xref('gedcomid'.$ged_id));
+ set_user_gedcom_setting($user_id, $ged_id, 'rootid', safe_POST_xref('rootid'.$ged_id));
+ set_user_gedcom_setting($user_id, $ged_id, 'canedit', safe_POST('canedit'.$ged_id, array_keys($ALL_EDIT_OPTIONS)));
+ if (safe_POST_xref('gedcomid'.$ged_id)) {
+ set_user_gedcom_setting($user_id, $ged_id, 'RELATIONSHIP_PATH_LENGTH', safe_POST_integer('RELATIONSHIP_PATH_LENGTH'.$ged_id, 0, 10, 0));
+ } else {
+ // Do not allow a path length to be set if the individual ID is not
+ set_user_gedcom_setting($user_id, $ged_id, 'RELATIONSHIP_PATH_LENGTH', null);
+ }
+ }
+
+ // If we're verifying a new user, send them a message to let them know
+ if ($newly_verified && $action=='edituser2') {
+ i18n::init($user_language);
+ $message=array();
+ $message["to"]=$username;
+ $headers="From: ".$WEBTREES_EMAIL;
+ $message["from"]=WT_USER_NAME;
+ $message["subject"]=i18n::translate('Approval of account at %s', WT_SERVER_NAME.WT_SCRIPT_PATH);
+ $message["body"]=i18n::translate('The administrator at the webtrees site %s has approved your application for an account. You may now login by accessing the following link: %s', WT_SERVER_NAME.WT_SCRIPT_PATH, WT_SERVER_NAME.WT_SCRIPT_PATH);
+ $message["created"]="";
+ $message["method"]="messaging2";
+ addMessage($message);
+ // and send a copy to the admin
+ /*
+ $message=array();
+ $message["to"]=WT_USER_NAME;
+ $headers="From: ".$WEBTREES_EMAIL;
+ $message["from"]=$username; // fake the from address - so the admin can "reply" to it.
+ $message["subject"]=i18n::translate('Approval of account at %s', WT_SERVER_NAME.WT_SCRIPT_PATH));
+ $message["body"]=i18n::translate('The administrator at the webtrees site %s has approved your application for an account. You may now login by accessing the following link: %s', WT_SERVER_NAME.WT_SCRIPT_PATH, WT_SERVER_NAME.WT_SCRIPT_PATH));
+ $message["created"]="";
+ $message["method"]="messaging2";
+ addMessage($message); */
+ }
+ // Reload the form cleanly, to allow the user to verify their changes
+ header('Location: '.WT_SERVER_NAME.WT_SCRIPT_PATH."useradmin.php?action=edituser&username=".rawurlencode($username)."&ged=".rawurlencode($ged));
+ exit;
+ }
+ }
+}
+
+// -- echo out the form to add a new user
+// NOTE: WORKING
+if ($action == "createform") {
+ init_calendar_popup();
+ ?>
+ <script language="JavaScript" type="text/javascript">
+ <!--
+ function checkform(frm) {
+ if (frm.username.value=="") {
+ alert("<?php echo i18n::translate('You must enter a user name.'); ?>");
+ frm.username.focus();
+ return false;
+ }
+ if (frm.realname.value=="") {
+ alert("<?php echo i18n::translate('You must enter a real name.'); ?>");
+ frm.realname.focus();
+ return false;
+ }
+ if (frm.pass1.value=="") {
+ alert("<?php echo i18n::translate('You must enter a password.'); ?>");
+ frm.pass1.focus();
+ return false;
+ }
+ if (frm.pass2.value=="") {
+ alert("<?php echo i18n::translate('You must confirm the password.'); ?>");
+ frm.pass2.focus();
+ return false;
+ }
+ if (frm.pass1.value.length < 6) {
+ alert("<?php echo i18n::translate('Passwords must contain at least 6 characters.'); ?>");
+ frm.pass1.value = "";
+ frm.pass2.value = "";
+ frm.pass1.focus();
+ return false;
+ }
+ if (frm.emailaddress.value.indexOf("@")==-1) {
+ alert("<?php echo i18n::translate('You must enter an email address.'); ?>");
+ frm.emailaddress.focus();
+ return false;
+ }
+ return true;
+ }
+ var pastefield;
+ function paste_id(value) {
+ pastefield.value=value;
+ }
+ jQuery(document).ready(function() {
+ jQuery('.relpath').change(function() {
+ var fieldIDx = jQuery(this).attr('id');
+ var idNum = fieldIDx.replace('RELATIONSHIP_PATH_LENGTH','');
+ var newIDx = "gedcomid"+idNum;
+ if (jQuery('#'+newIDx).val()=='') {
+ alert("<?php echo i18n::translate('You must specify an individual record before you can restrict the user to their immediate family.'); ?>");
+ jQuery(this).val('');
+ }
+ });
+ });
+
+ //-->
+ </script>
+
+ <form name="newform" method="post" action="useradmin.php" onsubmit="return checkform(this);" autocomplete="off">
+ <input type="hidden" name="action" value="createuser" />
+ <!--table-->
+ <table id="adduser" class="<?php echo $TEXT_DIRECTION; ?>">
+ <tr>
+ <td><?php echo i18n::translate('User name'), help_link('useradmin_username'); ?></td>
+ <td colspan="3" ><input type="text" name="username" autofocus /></td>
+ </tr>
+ <tr>
+ <td><?php echo i18n::translate('Real name'), help_link('useradmin_realname'); ?></td>
+ <td colspan="3" ><input type="text" name="realname" size="50" /></td>
+ </tr>
+ <tr>
+ <td><?php echo i18n::translate('Password'), help_link('useradmin_password'); ?></td>
+ <td ><input type="password" name="pass1" /></td>
+ <td><?php echo i18n::translate('Confirm password'), help_link('useradmin_conf_password'); ?></td>
+ <td ><input type="password" name="pass2" /></td>
+ </tr>
+ <tr>
+ <tr>
+ <td><?php echo i18n::translate('Email address'), help_link('useradmin_email'); ?></td>
+ <td ><input type="text" name="emailaddress" value="" size="50" /></td>
+ <td><?php echo i18n::translate('Preferred contact method'), help_link('useradmin_user_contact'); ?></td>
+ <td >
+ <?php
+ echo edit_field_contact('new_contact_method');
+ ?>
+ </td>
+ </tr>
+ <tr>
+ <td><?php echo i18n::translate('Email verified'), help_link('useradmin_verification'); ?></td>
+ <td ><input type="checkbox" name="verified" value="1" checked="checked" /></td>
+ <td><?php echo i18n::translate('Approved by administrator'), help_link('useradmin_verification'); ?></td>
+ <td ><input type="checkbox" name="verified_by_admin" value="1" checked="checked" /></td>
+ </tr>
+ <tr>
+ <td><?php echo i18n::translate('Automatically approve changes made by this user'), help_link('useradmin_auto_accept'); ?></td>
+ <td ><input type="checkbox" name="new_auto_accept" value="1" /></td>
+ <td><?php echo i18n::translate('Allow this user to edit his account information'), help_link('useradmin_editaccount'); ?></td>
+ <td ><input type="checkbox" name="editaccount" value="1" <?php echo "checked=\"checked\""; ?> /></td>
+ </tr>
+ <tr>
+ <td><?php echo i18n::translate('Administrator'), help_link('role'); ?></td>
+ <td ><input type="checkbox" name="canadmin" value="1" /></td>
+ <td><?php echo i18n::translate('Visible to other users when online'), help_link('useradmin_visibleonline'); ?></td>
+ <td ><input type="checkbox" name="visibleonline" value="1" <?php echo "checked=\"checked\""; ?> /></td>
+ </tr>
+ <?php if (WT_USER_IS_ADMIN) { ?>
+ <tr>
+ <td><?php echo i18n::translate('Admin comments on user'), help_link('useradmin_comment'); ?></td>
+ <td ><textarea cols="38" rows="5" name="new_comment"></textarea></td>
+ <td><?php echo i18n::translate('Date'), help_link('useradmin_comment_exp'); ?></td>
+ <td ><input type="text" name="new_comment_exp" id="new_comment_exp" />&nbsp;&nbsp;<?php print_calendar_popup("new_comment_exp"); ?></td>
+ </tr>
+ <?php } ?>
+ <tr>
+ <td><?php echo i18n::translate('Language'), help_link('useradmin_change_lang'); ?></td>
+ <td colspan="3" ><?php echo edit_field_language('user_language', get_user_setting(WT_USER_ID, 'language')); ?></td>
+ </tr>
+ <?php if (get_site_setting('ALLOW_USER_THEMES')) { ?>
+ <tr>
+ <td><?php echo i18n::translate('Theme'), help_link('THEME'); ?></td>
+ <td colspan="3">
+ <select name="new_user_theme">
+ <option value="" selected="selected"><?php echo i18n::translate('Site Default'); ?></option>
+ <?php
+ foreach (get_theme_names() as $themename=>$themedir) {
+ echo "<option value=\"", $themedir, "\"";
+ echo ">", $themename, "</option>";
+ }
+ ?>
+ </select>
+ </td>
+ </tr>
+ <?php } ?>
+ <tr>
+ <td><?php echo i18n::translate('Default Tab to show on Individual Information page'), help_link('useradmin_user_default_tab'); ?></td>
+ <td colspan="3">
+ <?php echo edit_field_default_tab('new_default_tab', get_gedcom_setting(WT_GED_ID, 'GEDCOM_DEFAULT_TAB')); ?>
+ </td>
+ </tr>
+ <!-- access and relationship path details -->
+ <tr>
+ <th colspan="4"><?php print i18n::translate('Family tree access and settings'); ?></th>
+ </tr>
+ <tr>
+ <td colspan="4">
+ <table id="adduser2">
+ <tr>
+ <th><?php echo i18n::translate('Family tree'); ?></th>
+ <th><?php echo i18n::translate('Pedigree chart root person'), help_link('useradmin_rootid'); ?></th>
+ <th><?php echo i18n::translate('Individual record'), help_link('useradmin_gedcomid'); ?></th>
+ <th><?php echo i18n::translate('Role'), help_link('role'); ?></th>
+ <th><?php echo i18n::translate('Restrict to immediate family'), help_link('RELATIONSHIP_PATH_LENGTH'); ?></th>
+ </tr>
+ <?php
+ foreach ($all_gedcoms as $ged_id=>$ged_name) {
+ echo '<tr>',
+ '<td >', $ged_name, '</td>',
+ //Pedigree root person
+ '<td >';
+ $varname='rootid'.$ged_id;
+ echo '<input type="text" name="', $varname, '" id="', $varname, '" value="" />', print_findindi_link($varname, "", false, false, $ged_name),
+ '</td>',
+ // GEDCOM INDI Record ID
+ '<td >';
+ $varname='gedcomid'.$ged_id;
+ echo '<input type="text" name="',$varname, '" id="',$varname, '" value="" />' ,print_findindi_link($varname, "", false, false, $ged_name),
+ '</td>',
+ '<td >';
+ $varname='canedit'.$ged_id;
+ echo '<select name="', $varname, '">';
+ foreach ($ALL_EDIT_OPTIONS as $EDIT_OPTION=>$desc) {
+ echo '<option value="', $EDIT_OPTION, '" ';
+ if ($EDIT_OPTION == i18n::translate('None')) {
+ echo 'selected="selected" ';
+ }
+ echo '>', $desc, '</option>';
+ }
+ echo '</select>',
+ '</td>',
+ //Relationship path
+ '<td>';
+ $varname = 'RELATIONSHIP_PATH_LENGTH'.$ged_id;
+ echo '<select name="', $varname, '" id="', $varname, '" class="relpath" />';
+ for ($n=0; $n<=10; ++$n) {
+ echo
+ '<option value="', $n, '">',
+ $n ? $n : '',
+ '</option>';
+ }
+ echo '</select>',
+ '</td>',
+ '</tr>';
+ }
+ ?>
+ </table>
+ </td>
+ </tr>
+ <td class="topbottombar" colspan="4">
+ <input type="submit" value="<?php echo i18n::translate('Create User'); ?>" />
+ <input type="button" value="<?php echo i18n::translate('Back'); ?>" onclick="window.location='useradmin.php';"/>
+ </td>
+ </tr>
+ </table>
+ </form>
+ <?php
+ print_footer();
+ exit;
+}
diff --git a/admin_users_list.php b/admin_users_list.php
new file mode 100644
index 0000000000..c6390d337e
--- /dev/null
+++ b/admin_users_list.php
@@ -0,0 +1,358 @@
+<?php
+/**
+ * Administrative User Interface.
+ *
+ * webtrees: Web based Family History software
+ * Copyright (C) 2010 webtrees development team.
+ *
+ * Derived from PhpGedView
+ * Copyright (C) 2002 to 2009 PGV Development Team. All rights reserved.
+ *
+ * Modifications Copyright (c) 2010 Greg Roach
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ * @package webtrees
+ * @subpackage Admin
+ * @version $Id: user_list.php 9870 2010-11-17 07:24:46Z nigel $
+ */
+
+define('WT_SCRIPT_NAME', 'admin_users_list.php');
+
+require './includes/session.php';
+require WT_ROOT.'includes/functions/functions_edit.php';
+
+// Only admin users can access this page
+if (!WT_USER_IS_ADMIN) {
+ header('Location: '.WT_SERVER_NAME.WT_SCRIPT_PATH.'login.php?url='.WT_SCRIPT_NAME);
+ exit;
+}
+
+print_header(i18n::translate('User administration'));
+
+?>
+<script type="text/javascript">
+ jQuery(document).ready(function(){
+
+ /* Insert a 'details' column to the table */
+ var nCloneTh = document.createElement( 'th' );
+ var nCloneTd = document.createElement( 'td' );
+ nCloneTh.innerHTML = 'Details';
+ nCloneTd.innerHTML = '<img class="open" src="./themes/_administration/images/open.png" width="11px">';
+ nCloneTd.className = "";
+
+ jQuery('#list thead tr').each( function () {
+ this.insertBefore( nCloneTh, this.childNodes[0] );
+ } );
+
+ jQuery('#list tbody tr').each( function () {
+ this.insertBefore( nCloneTd.cloneNode( true ), this.childNodes[0] );
+ } );
+
+ var oTable = jQuery('#list').dataTable( {
+ "oLanguage": {
+ "sLengthMenu": 'Display <select><option value="10">10</option><option value="20">20</option><option value="30">30</option><option value="40">40</option><option value="50">50</option><option value="-1">All</option></select> records'
+ },
+ "bJQueryUI": true,
+ "bAutoWidth":false,
+ "aaSorting": [[ 1, "asc" ]],
+ "iDisplayLength": 10,
+ "sPaginationType": "full_numbers",
+ "aoColumnDefs": [
+ { "bSortable": false, "aTargets": [ 0 ] }
+ ]
+ });
+
+ /* Add event listener for opening and closing details
+ * Note that the indicator for showing which row is open is not controlled by DataTables,
+ * rather it is done here
+ */
+ jQuery('#list tbody td img.open').live('click', function () {
+ var nTr = this.parentNode.parentNode;
+ if ( this.src.match('close') )
+ {
+ /* This row is already open - close it */
+ this.src = "./themes/_administration/images/open.png";
+ oTable.fnClose( nTr );
+ }
+ else
+ {
+ /* Open this row */
+ this.src = "./themes/_administration/images/close.png";
+ oTable.fnOpen( nTr, fnFormatDetails(oTable, nTr), 'details' );
+ }
+ } );
+
+ });
+
+ /* Formating function for row details */
+ function fnFormatDetails ( oTable, nTr )
+ {
+ var aData = oTable.fnGetData( nTr );
+ var sOut = '<table class="details">';
+ sOut += '<tr><th>'+'<?php echo i18n::translate('Role');?>'+': </th><td>'+aData[5]+'</td>';
+ sOut += '<th>'+'<?php echo i18n::translate('Language');?>'+': </th><td>'+aData[4]+'</td></tr>';
+ sOut += '</table>';
+
+ return sOut;
+ }
+</script>
+<?php
+
+//if ($ENABLE_AUTOCOMPLETE) require './js/autocomplete.js.htm';
+
+// Valid values for form variables
+$ALL_ACTIONS=array('cleanup', 'cleanup2', 'createform', 'createuser', 'deleteuser', 'edituser', 'edituser2', 'listusers');
+$ALL_THEMES_DIRS=array();
+foreach (get_theme_names() as $themename=>$themedir) {
+ $ALL_THEME_DIRS[]=$themedir;
+}
+$ALL_EDIT_OPTIONS=array(
+ 'none' => /* I18N: Listbox entry; name of a role */ i18n::translate('Visitor'),
+ 'access'=> /* I18N: Listbox entry; name of a role */ i18n::translate('Member'),
+ 'edit' => /* I18N: Listbox entry; name of a role */ i18n::translate('Editor'),
+ 'accept'=> /* I18N: Listbox entry; name of a role */ i18n::translate('Moderator'),
+ 'admin' => /* I18N: Listbox entry; name of a role */ i18n::translate('Manager')
+);
+
+// Extract form actions (GET overrides POST if both set)
+$action =safe_POST('action', $ALL_ACTIONS);
+$usrlang =safe_POST('usrlang', array_keys(i18n::installed_languages()));
+$username =safe_POST('username', WT_REGEX_USERNAME);
+$filter =safe_POST('filter' );
+$sort =safe_POST('sort' );
+$ged =safe_POST('ged' );
+
+$action =safe_GET('action', $ALL_ACTIONS, $action);
+$usrlang =safe_GET('usrlang', array_keys(i18n::installed_languages()), $usrlang);
+$username =safe_GET('username', WT_REGEX_USERNAME, $username);
+$filter =safe_GET('filter', WT_REGEX_NOSCRIPT, $filter);
+$sort =safe_GET('sort', WT_REGEX_NOSCRIPT, $sort);
+$ged =safe_GET('ged', WT_REGEX_NOSCRIPT, $ged);
+
+// Extract form variables
+$oldusername =safe_POST('oldusername', WT_REGEX_USERNAME);
+$realname =safe_POST('realname' );
+$pass1 =safe_POST('pass1', WT_REGEX_PASSWORD);
+$pass2 =safe_POST('pass2', WT_REGEX_PASSWORD);
+$emailaddress =safe_POST('emailaddress', WT_REGEX_EMAIL);
+$user_theme =safe_POST('user_theme', $ALL_THEME_DIRS);
+$user_language =safe_POST('user_language', array_keys(i18n::installed_languages()), WT_LOCALE);
+$new_contact_method =safe_POST('new_contact_method');
+$new_comment =safe_POST('new_comment', WT_REGEX_UNSAFE);
+$new_comment_exp =safe_POST('new_comment_exp' );
+$new_auto_accept =safe_POST_bool('new_auto_accept');
+$canadmin =safe_POST_bool('canadmin');
+$visibleonline =safe_POST_bool('visibleonline');
+$editaccount =safe_POST_bool('editaccount');
+$verified =safe_POST_bool('verified');
+$verified_by_admin =safe_POST_bool('verified_by_admin');
+
+if (empty($ged)) {
+ $ged=$GEDCOM;
+}
+
+// Load all available gedcoms
+$all_gedcoms = get_all_gedcoms();
+//-- sorting by gedcom filename
+asort($all_gedcoms);
+
+// Delete a user
+if ($action=='deleteuser') {
+ // don't delete ourselves
+ $user_id=get_user_id($username);
+ if ($user_id!=WT_USER_ID) {
+ delete_user($user_id);
+ AddToLog("deleted user ->{$username}<-", 'auth');
+ }
+ // User data is cached, so reload the page to ensure we're up to date
+ header("Location: admin_users_list.php");
+ exit;
+}
+
+//-- echo out a list of the current users
+ob_start();
+ $users = get_all_users();
+
+ // First filter the users, otherwise the javascript to unfold priviledges gets disturbed
+ foreach($users as $user_id=>$user_name) {
+ if ($filter == "warnings") {
+ if (get_user_setting($user_id, 'comment_exp')) {
+ if ((strtotime(get_user_setting($user_id, 'comment_exp')) == "-1") || (strtotime(get_user_setting($user_id, 'comment_exp')) >= time("U"))) unset($users[$user_id]);
+ }
+ else if (((date("U") - (int)get_user_setting($user_id, 'reg_timestamp')) <= 604800) || get_user_setting($user_id, 'verified')) unset($users[$user_id]);
+ }
+ else if ($filter == "adminusers") {
+ if (!get_user_setting($user_id, 'canadmin')) unset($users[$user_id]);
+ }
+ else if ($filter == "usunver") {
+ if (get_user_setting($user_id, 'verified')) unset($users[$user_id]);
+ }
+ else if ($filter == "admunver") {
+ if ((get_user_setting($user_id, 'verified_by_admin')) || (!get_user_setting($user_id, 'verified'))) {
+ unset($users[$user_id]);
+ }
+ }
+ else if ($filter == "language") {
+ if (get_user_setting($user_id, 'language') != $usrlang) {
+ unset($users[$user_id]);
+ }
+ }
+ else if ($filter == "gedadmin") {
+ if (get_user_gedcom_setting($user_id, $ged, 'canedit') != "admin") {
+ unset($users[$user_id]);
+ }
+ }
+ }
+
+ echo
+ '<table id="user-list" width="100%">',
+ '<thead>',
+ '<tr>',
+ '<th>User ID</th>',
+ '<th>', i18n::translate('Real name'), '</th>',
+ '<th>', i18n::translate('User name'), '</th>',
+ '<th>', i18n::translate('Email'), '</th>',
+ '<th>', i18n::translate('Language'), '</th>',
+ '<th>', i18n::translate('Date registered'), '</th>',
+ '<th>', i18n::translate('Last logged in'), '</th>',
+ '<th>', i18n::translate('Verified '), '</th>',
+ '<th>', i18n::translate('Approved'), '</th>',
+ '</tr>',
+ '</thead>',
+ '<tbody>',
+ '</tbody>',
+ '</table>',
+ WT_JS_START,
+ 'jQuery(document).ready(function() {',
+ ' jQuery("#user-list").dataTable( {',
+ ' "oLanguage": {',
+ ' "sLengthMenu": "Display <select><option value=10>10</option><option value=20>20</option><option value=30>30</option><option value=40>40</option><option value=50>50</option><option value=-1>All</option></select> records"',
+ ' },',
+ ' "bAutoWidth":false,',
+ ' "aaSorting": [[ 1, "asc" ]],',
+ ' "bProcessing": true,',
+ ' "bServerSide": true,',
+ ' "sAjaxSource": "', WT_SERVER_NAME, WT_SCRIPT_PATH, 'load.php?src=user_list",',
+ ' "aaSorting": [[ 1, "asc" ]],',
+ ' "bJQueryUI": true,',
+ ' "sPaginationType": "full_numbers"',
+ ' } );',
+ '} );',
+ WT_JS_END;
+
+ // Then show the users
+ echo
+ '<table id="list">',
+ '<thead>',
+ '<tr>',
+ '<th>', i18n::translate('Message'), '</th>',
+ '<th>', i18n::translate('Real name'), '</th>',
+ '<th>', i18n::translate('User name'), '</th>',
+// '<th>', i18n::translate('Languages'), '</th>',
+// '<th>', i18n::translate('Role'), '</th>',
+ '<th>', i18n::translate('Date registered'), '</th>',
+ '<th>', i18n::translate('Last logged in'), '</th>',
+ '<th>', i18n::translate('Verified '), '</th>',
+ '<th>', i18n::translate('Approved'), '</th>',
+ '<th>', i18n::translate('Delete'), '</th>',
+ '</tr>',
+ '</thead>',
+ '<tbody>';
+ foreach($users as $user_id=>$user_name) {
+ echo "<tr><td>";
+ if ($user_id!=WT_USER_ID && get_user_setting($user_id, 'contactmethod')!='none') {
+ echo "<a href=\"javascript:;\" onclick=\"return message('", $user_name, "');\"><img src=\"".$WT_IMAGES['email']."\" \"alt=\"", i18n::translate('Send Message'), "\" title=\"", i18n::translate('Send Message'), "\" /></a>";
+ } else {
+ echo '&nbsp;';
+ }
+ echo '</td>';
+ $userName = getUserFullName($user_id);
+ echo "<td><a class=\"edit_link\" href=\"useradmin.php?action=edituser&amp;username={$user_name}&amp;filter={$filter}&amp;usrlang={$usrlang}&amp;ged={$ged}\" title=\"", i18n::translate('Edit'), "\">", $userName, '</a>';
+ if (get_user_setting($user_id, 'canadmin')) {
+ echo '<div class="warning">', i18n::translate('Administrator'), '</div>';
+ }
+ echo "</td>";
+ if (get_user_setting($user_id, "comment_exp")) {
+ if ((strtotime(get_user_setting($user_id, "comment_exp")) != "-1") && (strtotime(get_user_setting($user_id, "comment_exp")) < time("U")))
+ echo '<td class="red">', $user_name;
+ else echo '<td>', $user_name;
+ }
+ else echo '<td>', $user_name;
+ if (get_user_setting($user_id, "comment")) {
+ $tempTitle = PrintReady(get_user_setting($user_id, "comment"));
+ echo '<img class="adminicon" align="top" alt="', $tempTitle, '" title="', $tempTitle, '" src="images/notes.png" />';
+ }
+ echo "</td>\n";
+ echo '<td style="display:none;">', Zend_Locale::getTranslation(get_user_setting($user_id, 'language'), 'language', WT_LOCALE), '</td>';
+ echo '<td style="display:none;">';
+ //echo '<div id="role">';
+ echo "<ul>";
+ foreach ($all_gedcoms as $ged_id=>$ged_name) {
+ $role=get_user_gedcom_setting($user_id, $ged_id, 'canedit');
+ switch ($role) {
+ case 'admin':
+ case 'accept':
+ echo '<li class="warning">', $ALL_EDIT_OPTIONS[$role];
+ break;
+ case 'edit':
+ case 'access':
+ case 'none':
+ echo '<li>', $ALL_EDIT_OPTIONS[$role];
+ break;
+ default:
+ echo '<li>', $ALL_EDIT_OPTIONS['none'];
+ break;
+ }
+ $uged = get_user_gedcom_setting($user_id, $ged_id, 'gedcomid');
+ if ($uged) {
+ echo ' <a href="individual.php?pid=', $uged, '&amp;ged=', rawurlencode($ged_name), '">', $ged_name, '</a></li>';
+ } else {
+ echo ' ', $ged_name, '</li>';
+ }
+ }
+ echo "</ul>";
+ // echo "</div>";
+ echo '</td>';
+ if (((date("U") - (int)get_user_setting($user_id, 'reg_timestamp')) > 604800) && !get_user_setting($user_id, 'verified'))
+ echo '<td class="red">';
+ else echo '<td>';
+ echo format_timestamp((int)get_user_setting($user_id, 'reg_timestamp'));
+ echo '</td>';
+ echo '<td>';
+ if ((int)get_user_setting($user_id, 'reg_timestamp') > (int)get_user_setting($user_id, 'sessiontime')) {
+ echo i18n::translate('Never'), '<br />', i18n::time_ago(time() - (int)get_user_setting($user_id, 'reg_timestamp'));
+ } else {
+ echo format_timestamp((int)get_user_setting($user_id, 'sessiontime')), '<br />', i18n::time_ago(time() - (int)get_user_setting($user_id, 'sessiontime'));
+ }
+ echo '</td>',
+ '<td class="center">';
+ if (get_user_setting($user_id, 'verified')) echo i18n::translate('Yes');
+ else echo i18n::translate('No');
+ echo '</td>',
+ '<td class="center">';
+ if (get_user_setting($user_id, 'verified_by_admin')) echo i18n::translate('Yes');
+ else echo i18n::translate('No');
+ echo '</td>',
+ '<td>';
+ if (WT_USER_ID!=$user_id)
+ echo "<a href=\"user_admin.php?action=deleteuser&amp;username=", rawurlencode($user_name)."&amp;sort={$sort}&amp;filter={$filter}&amp;usrlang={$usrlang}&amp;ged=", rawurlencode($ged), "\" onclick=\"return confirm('", i18n::translate('Are you sure you want to delete the user'), " $user_name');\"><img src=\"images/delete.png\" alt=\"", i18n::translate('Delete'), "\" title=\"", i18n::translate('Delete'), "\" /></a>";
+ echo '</td>',
+ '</tr>';
+ }
+ echo '</tbody>',
+ '</table>';
+ print_footer();
+ob_flush();
diff --git a/administration.php b/administration.php
new file mode 100644
index 0000000000..aa1bdb7a97
--- /dev/null
+++ b/administration.php
@@ -0,0 +1,236 @@
+<?php
+/**
+ * Welcome page for the administration module
+ *
+ * webtrees: Web based Family History software
+ * Copyright (C) 2010 webtrees development team.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ * @package webtrees
+ * @subpackage Admin
+ * @version $Id$
+ */
+
+define('WT_SCRIPT_NAME', 'administration.php');
+
+require './includes/session.php';
+require WT_ROOT.'includes/functions/functions_edit.php';
+//global $SOURCE_ID_PREFIX, $REPO_ID_PREFIX, $MEDIA_ID_PREFIX, $FAM_ID_PREFIX, $GEDCOM_ID_PREFIX;
+
+print_header(i18n::translate('Administration'));
+
+// Check for updates
+$latest_version_txt=fetch_latest_version();
+if ($latest_version_txt) {
+ list($latest_version, $earliest_version, $download_url)=explode('|', $latest_version_txt);
+ // If the latest version is newer than this version, show a download link.
+ if (version_compare(WT_VERSION, $latest_version)<=0) {
+ // A newer version is available. Make a link to it
+ $latest_version='<a href="'.$download_url.'" style="font-weight:bold; color:red;">'.$latest_version.'</a>';
+ }
+} else {
+ // Cannot determine the latest version
+ $latest_version='-';
+}
+
+// Load all available gedcoms
+$all_gedcoms = get_all_gedcoms();
+
+// Display a series of "blocks" of general information, vary according to admin or manager.
+echo '<div id="about">';
+echo
+ '<h2>', i18n::translate('About webtrees'), '</h2>',
+ '<p>', i18n::translate('Welcome to the <b>webtrees</b> administration page. This page provides access to all the site and family tree configuration settings as well as providing some useful information blocks. Administrators can upgrade to the lastest version with a single click, whenever the page reports that a new version is available.'), '</p>',
+ '<p>' ,i18n::translate('Your installed version of <b>webtrees</b> is: %s', WT_VERSION_TEXT),'</p>';
+if (version_compare(WT_VERSION, $latest_version)>0) {
+ echo '<p>' ,i18n::translate('The latest stable <b>webtrees</b> version is: %s', $latest_version), '&nbsp;<span class="accepted">' ,i18n::translate('No upgrade required.'), '</span></p>';
+} else {
+ echo '<p class="warning">' ,i18n::translate('We recommend you click here to upgrade to the latest stable webtrees version: %s', $latest_version), '</p>';
+}
+echo '</div>';
+
+echo '<div id="x">';
+
+echo '<div id="block1">';
+$stats=new WT_Stats(WT_GEDCOM);
+ $totusers =0; // Total number of users
+ $warnusers =0; // Users with warning
+ $applusers =0; // Users who have not verified themselves
+ $nverusers =0; // Users not verified by admin but verified themselves
+ $adminusers=0; // Administrators
+ $userlang =array(); // Array for user languages
+ $gedadmin =array(); // Array for managers
+
+echo
+ '<h2>', i18n::translate('User information'), '</h2>';
+
+foreach(get_all_users() as $user_id=>$user_name) {
+ $totusers = $totusers + 1;
+ if (((date("U") - (int)get_user_setting($user_id, 'reg_timestamp')) > 604800) && !get_user_setting($user_id, 'verified')) {
+ $warnusers++;
+ } else {
+ if (get_user_setting($user_id, 'comment_exp')) {
+ if ((strtotime(get_user_setting($user_id, 'comment_exp')) != "-1") && (strtotime(get_user_setting($user_id, 'comment_exp')) < time("U"))) {
+ $warnusers++;
+ }
+ }
+ }
+ if (!get_user_setting($user_id, 'verified_by_admin') && get_user_setting($user_id, 'verified')) {
+ $nverusers++;
+ }
+ if (!get_user_setting($user_id, 'verified')) {
+ $applusers++;
+ }
+ if (get_user_setting($user_id, 'canadmin')) {
+ $adminusers++;
+ }
+ foreach ($all_gedcoms as $ged_id=>$ged_name) {
+ if (get_user_gedcom_setting($user_id, $ged_id, 'canedit')=='admin') {
+ $title=PrintReady(strip_tags(get_gedcom_setting($ged_id, 'title')));
+ if (isset($gedadmin[$title])) {
+ $gedadmin[$title]["number"]++;
+ } else {
+ $gedadmin[$title]["name"] = $title;
+ $gedadmin[$title]["number"] = 1;
+ $gedadmin[$title]["ged"] = $ged_name;
+ }
+ }
+ }
+ if ($user_lang=get_user_setting($user_id, 'language')) {
+ if (isset($userlang[$user_lang]))
+ $userlang[$user_lang]["number"]++;
+ else {
+ $userlang[$user_lang]["langname"] = Zend_Locale::getTranslation($user_lang, 'language', WT_LOCALE);
+ $userlang[$user_lang]["number"] = 1;
+ }
+ }
+}
+
+echo
+ '<table>',
+ '<tr><td>', i18n::translate('Total number of users'), '</td><td>', $totusers, '</td></tr>',
+ '<tr><td>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<a href="user_admin.php?action=listusers&amp;filter=adminusers">', i18n::translate('Administrators'), '</a></td><td>', $adminusers, '</td></tr>',
+ '<tr><td colspan="2">', i18n::translate('Managers'), '</td></tr>';
+foreach ($gedadmin as $key=>$geds) {
+ echo '<tr><td>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<a href="user_admin.php?action=listusers&amp;filter=gedadmin&amp;ged='.rawurlencode($geds['ged']), '">', $geds['name'], '</a></td><td>', $geds['number'], '</td></tr>';
+}
+echo '<tr><td>';
+if ($warnusers == 0) {
+ echo i18n::translate('Users with warnings');
+} else {
+ echo '<a href="user_admin.php?action=listusers&amp;filter=warnings">', i18n::translate('Users with warnings'), '</a>';
+}
+echo '</td><td>', $warnusers, '</td></tr><tr><td>';
+if ($applusers == 0) {
+ echo i18n::translate('Unverified by User');
+} else {
+ echo '<a href="user_admin.php?action=listusers&amp;filter=usunver">', i18n::translate('Unverified by User'), '</a>';
+}
+echo '</td><td>', $applusers, '</td></tr><tr><td>';
+if ($nverusers == 0) {
+ echo i18n::translate('Unverified by Administrator');
+} else {
+ echo '<a href="user_admin.php?action=listusers&amp;filter=admunver">', i18n::translate('Unverified by Administrator'), '</a>';
+}
+echo '</td><td>', $nverusers, '</td></tr>';
+echo '<tr><td colspan="2">', i18n::translate('Users\' languages'), '</td></tr>';
+foreach ($userlang as $key=>$ulang) {
+ echo '<tr><td>&nbsp;&nbsp;&nbsp;&nbsp;<a href="user_admin.php?action=listusers&amp;filter=language&amp;usrlang=', $key, '">', $ulang['langname'], '</a></td><td>', $ulang['number'], '</td></tr>';
+}
+echo
+ '</tr>',
+ '<tr><td colspan="2">', i18n::translate('Users logged in'), '</td></tr>',
+ '<tr><td colspan="2">&nbsp;&nbsp;&nbsp;&nbsp;', $stats->_usersLoggedIn(), '</td></tr>',
+ '</table>';
+echo '</div>'; // id=block2
+
+echo
+ '<div id="block3">',
+ '<h2>', i18n::translate('Recent changes'), '</h2>',
+ '<div id="changes">';
+$n=0;
+foreach ($all_gedcoms as $ged_id=>$gedcom) {
+ if ($ged_id==WT_GED_ID) {
+ $accordian_element=$n;
+ }
+ ++$n;
+ echo
+ '<h3>', get_gedcom_setting($ged_id, 'title'), '</h3>',
+ '<div>',
+ '<table>',
+ '<tr><td>&nbsp;</td><td><u>', i18n::translate('Today'), '</u></td><td><u>', i18n::translate('This week'), '</u></td><td><u>', i18n::translate('This month'), '</u></td>',
+ '<tr><th>', i18n::translate('Individuals'), '</th><td>', count_changes_today($GEDCOM_ID_PREFIX, $ged_id), '</td><td>', count_changes_week($GEDCOM_ID_PREFIX, $ged_id), '</td><td>', count_changes_month($GEDCOM_ID_PREFIX, $ged_id), '</td></tr>',
+ '<tr><th>', i18n::translate('Families'), '</th><td>', count_changes_today($FAM_ID_PREFIX, $ged_id), '</td><td>', count_changes_week($FAM_ID_PREFIX, $ged_id), '</td><td>', count_changes_month($FAM_ID_PREFIX, $ged_id), '</td></tr>',
+ '<tr><th>', i18n::translate('Sources'), '</th><td>', count_changes_today($SOURCE_ID_PREFIX, $ged_id), '</td><td>', count_changes_week($SOURCE_ID_PREFIX, $ged_id), '</td><td>', count_changes_month($SOURCE_ID_PREFIX, $ged_id), '</td></tr>',
+ '<tr><th>', i18n::translate('Repositories'), '</th><td>', count_changes_today($REPO_ID_PREFIX, $ged_id), '</td><td>', count_changes_week($REPO_ID_PREFIX, $ged_id), '</td><td>', count_changes_month($REPO_ID_PREFIX, $ged_id), '</td></tr>',
+ '<tr><th>', i18n::translate('Media objects'), '</th><td>', count_changes_today($MEDIA_ID_PREFIX, $ged_id), '</td><td>', count_changes_week($MEDIA_ID_PREFIX, $ged_id), '</td><td>', count_changes_month($MEDIA_ID_PREFIX, $ged_id), '</td></tr>',
+ '<tr><th>', i18n::translate('Notes'), '</th><td>', count_changes_today($NOTE_ID_PREFIX, $ged_id), '</td><td>', count_changes_week($NOTE_ID_PREFIX, $ged_id), '</td><td>', count_changes_month($NOTE_ID_PREFIX, $ged_id), '</td></tr>',
+ '</table>',
+ '</div>';
+}
+echo
+ '</div>', // id=changes
+ WT_JS_START,
+ 'jQuery("#changes").accordion({active:',$accordian_element,', icons:false});',
+ WT_JS_END,
+ '</div>'; // id=block3
+
+echo
+ '<div id="block2">',
+ '<h2>', i18n::translate('Family tree statistics'), '</h2>',
+ '<div id="tree_stats">';
+$n=0;
+foreach ($all_gedcoms as $ged_id=>$gedcom) {
+ $stats = new WT_Stats($gedcom);
+ if ($ged_id==WT_GED_ID) {
+ $accordian_element=$n;
+ }
+ ++$n;
+ echo
+ '<h3>', $stats->gedcomTitle(), '</h3>',
+ '<div>',
+ '<table>',
+ '<tr><th><a href="indilist.php?ged=', rawurlencode($gedcom), '">',
+ i18n::translate('Individuals'), '</a></th><td>', $stats->totalIndividuals(),
+ '</td></tr>',
+ '<tr><th><a href="famlist.php?ged=', rawurlencode($gedcom), '">',
+ i18n::translate('Families'), '</a></th><td>', $stats->totalFamilies(),
+ '</td></tr>',
+ '<tr><th><a href="sourlist.php?ged=', rawurlencode($gedcom), '">',
+ i18n::translate('Sources'), '</a></th><td>', $stats->totalSources(),
+ '</td></tr>',
+ '<tr><th><a href="repolist.php?ged=', rawurlencode($gedcom), '">',
+ i18n::translate('Repositories'), '</a></th><td>', $stats->totalRepositories(),
+ '</td></tr>',
+ '<tr><th><a href="medialist.php?ged=', rawurlencode($gedcom), '">',
+ i18n::translate('Media objects'), '</a></th><td>', $stats->totalMedia(),
+ '</td></tr>',
+ '<tr><th><a href="notelist.php?ged=', rawurlencode($gedcom), '">',
+ i18n::translate('Notes'), '</a></th><td>', $stats->totalNotes(),
+ '</td></tr>',
+ '</table>',
+ '</div>';
+}
+echo
+ '</div>', // id=tree_stats
+ WT_JS_START,
+ 'jQuery("#tree_stats").accordion({active:',$accordian_element,', icons:false});',
+ WT_JS_END,
+ '</div>'; // id=block2
+
+echo '</div>'; // id=x
+
+print_footer();
diff --git a/editconfig_gedcom.php b/editconfig_gedcom.php
index 402591b282..c4fa4db30d 100644
--- a/editconfig_gedcom.php
+++ b/editconfig_gedcom.php
@@ -710,7 +710,7 @@ print_header(i18n::translate('GEDCOM configuration'));
explode(',', get_gedcom_setting(WT_GED_ID, 'NOTE_FACTS_ADD')), explode(',', get_gedcom_setting(WT_GED_ID, 'NOTE_FACTS_UNIQUE')),
explode(',', get_gedcom_setting(WT_GED_ID, 'SOUR_FACTS_ADD')), explode(',', get_gedcom_setting(WT_GED_ID, 'SOUR_FACTS_UNIQUE')),
explode(',', get_gedcom_setting(WT_GED_ID, 'REPO_FACTS_ADD')), explode(',', get_gedcom_setting(WT_GED_ID, 'REPO_FACTS_UNIQUE')),
- array('SOUR', 'REPO', 'OBJE', '_PRIM', '_THUM', 'NOTE', 'SUBM', 'SUBN')
+ array('SOUR', 'REPO', 'OBJE', 'NOTE', 'SUBM', 'SUBN')
));
foreach ($tags as $tag) {
@@ -727,7 +727,6 @@ print_header(i18n::translate('GEDCOM configuration'));
print_findfamily_link("xref");
print_findsource_link("xref");
print_findrepository_link("xref");
- print_findnote_link("xref");
print_findmedia_link("xref", "1media");
echo '</td><td class="optionbox" width="*">';
echo select_edit_control('tag_type', $all_tags, '', null, null);
diff --git a/includes/functions/functions.php b/includes/functions/functions.php
index af0d654ddd..7c0d525ea0 100644
--- a/includes/functions/functions.php
+++ b/includes/functions/functions.php
@@ -3277,7 +3277,7 @@ function mediaFileInfo($fileName, $thumbName, $mid, $name='', $notes='', $obeyVi
$url = $fileName . "\" rel='clearbox({$LB_URL_WIDTH}, {$LB_URL_HEIGHT}, click)' rev=\"" . $mid . "::" . $GEDCOM . "::" . PrintReady(htmlspecialchars($name)) . "::" . htmlspecialchars($notes);
break 2;
case 'url_streetview':
- if (WT_SCRIPT_NAME != "media.php") {
+ if (WT_SCRIPT_NAME != "admin_media.php") {
echo '<iframe style="float:left; padding:5px;" width="264" height="176" frameborder="0" scrolling="no" marginheight="0" marginwidth="0" src="'. $fileName. '&amp;output=svembed"></iframe>';
}
break 2;
diff --git a/includes/functions/functions_db.php b/includes/functions/functions_db.php
index da4946032a..3c0db9aa07 100644
--- a/includes/functions/functions_db.php
+++ b/includes/functions/functions_db.php
@@ -2425,3 +2425,31 @@ function update_favorites($xref_from, $xref_to, $ged_id=WT_GED_ID) {
->execute(array($xref_to, $xref_from, $ged_name))
->rowCount();
}
+
+/**
+* count changes by today, this week, or this month for administration dashboard
+*
+* @param string $xref
+* @param string $tree
+*/
+function count_changes_today($xref, $tree) {
+ $sql="SELECT count(change_id) FROM `##change` WHERE status='accepted' AND date(change_time)= date(now()) AND xref LIKE ? '%' AND gedcom_id = ? ";
+ return
+ WT_DB::prepare($sql)
+ ->execute(array($xref, $tree))
+ ->fetchOne();
+}
+function count_changes_week($xref, $tree) {
+ $sql="SELECT count(change_id) FROM `##change` WHERE status='accepted' AND week(change_time,2)= week(now(),2) AND xref LIKE ? '%' AND gedcom_id = ? ";
+ return
+ WT_DB::prepare($sql)
+ ->execute(array($xref, $tree))
+ ->fetchOne();
+}
+function count_changes_month($xref, $tree) {
+ $sql="SELECT count(change_id) FROM `##change` WHERE status='accepted' AND month(change_time)= month(now()) AND xref LIKE ? '%' AND gedcom_id = ? ";
+ return
+ WT_DB::prepare($sql)
+ ->execute(array($xref, $tree))
+ ->fetchOne();
+}
diff --git a/includes/functions/functions_mediadb.php b/includes/functions/functions_mediadb.php
index 8efb912e94..6581ef4b86 100644
--- a/includes/functions/functions_mediadb.php
+++ b/includes/functions/functions_mediadb.php
@@ -1076,7 +1076,7 @@ function process_uploadMedia_form() {
* @param string $URL the URL the input form is to execute when the "Submit" button is pressed
* @param bool $showthumb the setting of the "show thumbnail" option (required by media.php)
*/
-function show_mediaUpload_form($URL='media.php', $showthumb=false) {
+function show_mediaUpload_form($URL, $showthumb=false) {
global $MEDIA_DIRECTORY_LEVELS, $MEDIA_DIRECTORY, $TEXT_DIRECTION;
$AUTO_GENERATE_THUMBS=get_gedcom_setting(WT_GED_ID, 'AUTO_GENERATE_THUMBS');
diff --git a/includes/session.php b/includes/session.php
index 14bb5ac67b..942befa650 100644
--- a/includes/session.php
+++ b/includes/session.php
@@ -460,7 +460,10 @@ if (WT_USER_ID) {
}
// Set the theme
-if (!defined('WT_THEME_DIR')) {
+if (substr(WT_SCRIPT_NAME, 0, 5)=='admin' || WT_SCRIPT_NAME=='module.php' && substr(safe_GET('mod_action'), 0, 5)=='admin') {
+ // Administration scripts begin with 'admin' and use a special administration theme
+ define('WT_THEME_DIR', 'themes/_administration/');
+} else {
if (get_site_setting('ALLOW_USER_THEMES')) {
// Requested change of theme?
$THEME_DIR=safe_GET('theme', get_theme_names());
diff --git a/js/jquery/jquery.dataTables.js b/js/jquery/jquery.dataTables.js
new file mode 100644
index 0000000000..02a54afd41
--- /dev/null
+++ b/js/jquery/jquery.dataTables.js
@@ -0,0 +1,6691 @@
+/*
+ * File: jquery.dataTables.js
+ * Version: 1.7.3
+ * Description: Paginate, search and sort HTML tables
+ * Author: Allan Jardine (www.sprymedia.co.uk)
+ * Created: 28/3/2008
+ * Language: Javascript
+ * License: GPL v2 or BSD 3 point style
+ * Project: Mtaala
+ * Contact: allan.jardine@sprymedia.co.uk
+ *
+ * Copyright 2008-2010 Allan Jardine, all rights reserved.
+ *
+ * This source file is free software, under either the GPL v2 license or a
+ * BSD style license, as supplied with this software.
+ *
+ * This source file is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the license files for details.
+ *
+ * For details please refer to: http://www.datatables.net
+ */
+
+/*
+ * When considering jsLint, we need to allow eval() as it it is used for reading cookies and
+ * building the dynamic multi-column sort functions.
+ */
+/*jslint evil: true, undef: true, browser: true */
+/*globals $, jQuery,_fnExternApiFunc,_fnInitalise,_fnLanguageProcess,_fnAddColumn,_fnColumnOptions,_fnAddData,_fnGatherData,_fnDrawHead,_fnDraw,_fnReDraw,_fnAjaxUpdate,_fnAjaxUpdateDraw,_fnAddOptionsHtml,_fnFeatureHtmlTable,_fnScrollDraw,_fnAjustColumnSizing,_fnFeatureHtmlFilter,_fnFilterComplete,_fnFilterCustom,_fnFilterColumn,_fnFilter,_fnBuildSearchArray,_fnFilterCreateSearch,_fnDataToSearch,_fnSort,_fnSortAttachListener,_fnSortingClasses,_fnFeatureHtmlPaginate,_fnPageChange,_fnFeatureHtmlInfo,_fnUpdateInfo,_fnFeatureHtmlLength,_fnFeatureHtmlProcessing,_fnProcessingDisplay,_fnVisibleToColumnIndex,_fnColumnIndexToVisible,_fnNodeToDataIndex,_fnVisbleColumns,_fnCalculateEnd,_fnConvertToWidth,_fnCalculateColumnWidths,_fnScrollingWidthAdjust,_fnGetWidestNode,_fnGetMaxLenString,_fnStringToCss,_fnArrayCmp,_fnDetectType,_fnSettingsFromNode,_fnGetDataMaster,_fnGetTrNodes,_fnGetTdNodes,_fnEscapeRegex,_fnDeleteIndex,_fnReOrderIndex,_fnColumnOrdering,_fnLog,_fnClearTable,_fnSaveState,_fnLoadState,_fnCreateCookie,_fnReadCookie,_fnGetUniqueThs,_fnScrollBarWidth,_fnApplyToChildren,_fnMap*/
+
+(function($, window, document) {
+ /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+ * Section - DataTables variables
+ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
+
+ /*
+ * Variable: dataTableSettings
+ * Purpose: Store the settings for each dataTables instance
+ * Scope: jQuery.fn
+ */
+ $.fn.dataTableSettings = [];
+ var _aoSettings = $.fn.dataTableSettings; /* Short reference for fast internal lookup */
+
+ /*
+ * Variable: dataTableExt
+ * Purpose: Container for customisable parts of DataTables
+ * Scope: jQuery.fn
+ */
+ $.fn.dataTableExt = {};
+ var _oExt = $.fn.dataTableExt;
+
+
+ /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+ * Section - DataTables extensible objects
+ *
+ * The _oExt object is used to provide an area where user dfined plugins can be
+ * added to DataTables. The following properties of the object are used:
+ * oApi - Plug-in API functions
+ * aTypes - Auto-detection of types
+ * oSort - Sorting functions used by DataTables (based on the type)
+ * oPagination - Pagination functions for different input styles
+ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
+
+ /*
+ * Variable: sVersion
+ * Purpose: Version string for plug-ins to check compatibility
+ * Scope: jQuery.fn.dataTableExt
+ * Notes: Allowed format is a.b.c.d.e where:
+ * a:int, b:int, c:int, d:string(dev|beta), e:int. d and e are optional
+ */
+ _oExt.sVersion = "1.7.3";
+
+ /*
+ * Variable: sErrMode
+ * Purpose: How should DataTables report an error. Can take the value 'alert' or 'throw'
+ * Scope: jQuery.fn.dataTableExt
+ */
+ _oExt.sErrMode = "alert";
+
+ /*
+ * Variable: iApiIndex
+ * Purpose: Index for what 'this' index API functions should use
+ * Scope: jQuery.fn.dataTableExt
+ */
+ _oExt.iApiIndex = 0;
+
+ /*
+ * Variable: oApi
+ * Purpose: Container for plugin API functions
+ * Scope: jQuery.fn.dataTableExt
+ */
+ _oExt.oApi = { };
+
+ /*
+ * Variable: aFiltering
+ * Purpose: Container for plugin filtering functions
+ * Scope: jQuery.fn.dataTableExt
+ */
+ _oExt.afnFiltering = [ ];
+
+ /*
+ * Variable: aoFeatures
+ * Purpose: Container for plugin function functions
+ * Scope: jQuery.fn.dataTableExt
+ * Notes: Array of objects with the following parameters:
+ * fnInit: Function for initialisation of Feature. Takes oSettings and returns node
+ * cFeature: Character that will be matched in sDom - case sensitive
+ * sFeature: Feature name - just for completeness :-)
+ */
+ _oExt.aoFeatures = [ ];
+
+ /*
+ * Variable: ofnSearch
+ * Purpose: Container for custom filtering functions
+ * Scope: jQuery.fn.dataTableExt
+ * Notes: This is an object (the name should match the type) for custom filtering function,
+ * which can be used for live DOM checking or formatted text filtering
+ */
+ _oExt.ofnSearch = { };
+
+ /*
+ * Variable: afnSortData
+ * Purpose: Container for custom sorting data source functions
+ * Scope: jQuery.fn.dataTableExt
+ * Notes: Array (associative) of functions which is run prior to a column of this
+ * 'SortDataType' being sorted upon.
+ * Function input parameters:
+ * object:oSettings- DataTables settings object
+ * int:iColumn - Target column number
+ * Return value: Array of data which exactly matched the full data set size for the column to
+ * be sorted upon
+ */
+ _oExt.afnSortData = [ ];
+
+ /*
+ * Variable: oStdClasses
+ * Purpose: Storage for the various classes that DataTables uses
+ * Scope: jQuery.fn.dataTableExt
+ */
+ _oExt.oStdClasses = {
+ /* Two buttons buttons */
+ "sPagePrevEnabled": "paginate_enabled_previous",
+ "sPagePrevDisabled": "paginate_disabled_previous",
+ "sPageNextEnabled": "paginate_enabled_next",
+ "sPageNextDisabled": "paginate_disabled_next",
+ "sPageJUINext": "",
+ "sPageJUIPrev": "",
+
+ /* Full numbers paging buttons */
+ "sPageButton": "paginate_button",
+ "sPageButtonActive": "paginate_active",
+ "sPageButtonStaticDisabled": "paginate_button",
+ "sPageFirst": "first",
+ "sPagePrevious": "previous",
+ "sPageNext": "next",
+ "sPageLast": "last",
+
+ /* Stripping classes */
+ "sStripOdd": "odd",
+ "sStripEven": "even",
+
+ /* Empty row */
+ "sRowEmpty": "dataTables_empty",
+
+ /* Features */
+ "sWrapper": "dataTables_wrapper",
+ "sFilter": "dataTables_filter",
+ "sInfo": "dataTables_info",
+ "sPaging": "dataTables_paginate paging_", /* Note that the type is postfixed */
+ "sLength": "dataTables_length",
+ "sProcessing": "dataTables_processing",
+
+ /* Sorting */
+ "sSortAsc": "sorting_asc",
+ "sSortDesc": "sorting_desc",
+ "sSortable": "sorting", /* Sortable in both directions */
+ "sSortableAsc": "sorting_asc_disabled",
+ "sSortableDesc": "sorting_desc_disabled",
+ "sSortableNone": "sorting_disabled",
+ "sSortColumn": "sorting_", /* Note that an int is postfixed for the sorting order */
+ "sSortJUIAsc": "",
+ "sSortJUIDesc": "",
+ "sSortJUI": "",
+ "sSortJUIAscAllowed": "",
+ "sSortJUIDescAllowed": "",
+ "sSortJUIWrapper": "",
+
+ /* Scrolling */
+ "sScrollWrapper": "dataTables_scroll",
+ "sScrollHead": "dataTables_scrollHead",
+ "sScrollHeadInner": "dataTables_scrollHeadInner",
+ "sScrollBody": "dataTables_scrollBody",
+ "sScrollFoot": "dataTables_scrollFoot",
+ "sScrollFootInner": "dataTables_scrollFootInner",
+
+ /* Misc */
+ "sFooterTH": ""
+ };
+
+ /*
+ * Variable: oJUIClasses
+ * Purpose: Storage for the various classes that DataTables uses - jQuery UI suitable
+ * Scope: jQuery.fn.dataTableExt
+ */
+ _oExt.oJUIClasses = {
+ /* Two buttons buttons */
+ "sPagePrevEnabled": "fg-button ui-button ui-state-default ui-corner-left",
+ "sPagePrevDisabled": "fg-button ui-button ui-state-default ui-corner-left ui-state-disabled",
+ "sPageNextEnabled": "fg-button ui-button ui-state-default ui-corner-right",
+ "sPageNextDisabled": "fg-button ui-button ui-state-default ui-corner-right ui-state-disabled",
+ "sPageJUINext": "ui-icon ui-icon-circle-arrow-e",
+ "sPageJUIPrev": "ui-icon ui-icon-circle-arrow-w",
+
+ /* Full numbers paging buttons */
+ "sPageButton": "fg-button ui-button ui-state-default",
+ "sPageButtonActive": "fg-button ui-button ui-state-default ui-state-disabled",
+ "sPageButtonStaticDisabled": "fg-button ui-button ui-state-default ui-state-disabled",
+ "sPageFirst": "first ui-corner-tl ui-corner-bl",
+ "sPagePrevious": "previous",
+ "sPageNext": "next",
+ "sPageLast": "last ui-corner-tr ui-corner-br",
+
+ /* Stripping classes */
+ "sStripOdd": "odd",
+ "sStripEven": "even",
+
+ /* Empty row */
+ "sRowEmpty": "dataTables_empty",
+
+ /* Features */
+ "sWrapper": "dataTables_wrapper",
+ "sFilter": "dataTables_filter",
+ "sInfo": "dataTables_info",
+ "sPaging": "dataTables_paginate fg-buttonset ui-buttonset fg-buttonset-multi "+
+ "ui-buttonset-multi paging_", /* Note that the type is postfixed */
+ "sLength": "dataTables_length",
+ "sProcessing": "dataTables_processing",
+
+ /* Sorting */
+ "sSortAsc": "ui-state-default",
+ "sSortDesc": "ui-state-default",
+ "sSortable": "ui-state-default",
+ "sSortableAsc": "ui-state-default",
+ "sSortableDesc": "ui-state-default",
+ "sSortableNone": "ui-state-default",
+ "sSortColumn": "sorting_", /* Note that an int is postfixed for the sorting order */
+ "sSortJUIAsc": "css_right ui-icon ui-icon-triangle-1-n",
+ "sSortJUIDesc": "css_right ui-icon ui-icon-triangle-1-s",
+ "sSortJUI": "css_right ui-icon ui-icon-carat-2-n-s",
+ "sSortJUIAscAllowed": "css_right ui-icon ui-icon-carat-1-n",
+ "sSortJUIDescAllowed": "css_right ui-icon ui-icon-carat-1-s",
+ "sSortJUIWrapper": "DataTables_sort_wrapper",
+
+ /* Scrolling */
+ "sScrollWrapper": "dataTables_scroll",
+ "sScrollHead": "dataTables_scrollHead ui-state-default",
+ "sScrollHeadInner": "dataTables_scrollHeadInner",
+ "sScrollBody": "dataTables_scrollBody",
+ "sScrollFoot": "dataTables_scrollFoot ui-state-default",
+ "sScrollFootInner": "dataTables_scrollFootInner",
+
+ /* Misc */
+ "sFooterTH": "ui-state-default"
+ };
+
+ /*
+ * Variable: oPagination
+ * Purpose: Container for the various type of pagination that dataTables supports
+ * Scope: jQuery.fn.dataTableExt
+ */
+ _oExt.oPagination = {
+ /*
+ * Variable: two_button
+ * Purpose: Standard two button (forward/back) pagination
+ * Scope: jQuery.fn.dataTableExt.oPagination
+ */
+ "two_button": {
+ /*
+ * Function: oPagination.two_button.fnInit
+ * Purpose: Initalise dom elements required for pagination with forward/back buttons only
+ * Returns: -
+ * Inputs: object:oSettings - dataTables settings object
+ * node:nPaging - the DIV which contains this pagination control
+ * function:fnCallbackDraw - draw function which must be called on update
+ */
+ "fnInit": function ( oSettings, nPaging, fnCallbackDraw )
+ {
+ var nPrevious, nNext, nPreviousInner, nNextInner;
+
+ /* Store the next and previous elements in the oSettings object as they can be very
+ * usful for automation - particularly testing
+ */
+ if ( !oSettings.bJUI )
+ {
+ nPrevious = document.createElement( 'div' );
+ nNext = document.createElement( 'div' );
+ }
+ else
+ {
+ nPrevious = document.createElement( 'a' );
+ nNext = document.createElement( 'a' );
+
+ nNextInner = document.createElement('span');
+ nNextInner.className = oSettings.oClasses.sPageJUINext;
+ nNext.appendChild( nNextInner );
+
+ nPreviousInner = document.createElement('span');
+ nPreviousInner.className = oSettings.oClasses.sPageJUIPrev;
+ nPrevious.appendChild( nPreviousInner );
+ }
+
+ nPrevious.className = oSettings.oClasses.sPagePrevDisabled;
+ nNext.className = oSettings.oClasses.sPageNextDisabled;
+
+ nPrevious.title = oSettings.oLanguage.oPaginate.sPrevious;
+ nNext.title = oSettings.oLanguage.oPaginate.sNext;
+
+ nPaging.appendChild( nPrevious );
+ nPaging.appendChild( nNext );
+
+ $(nPrevious).click( function() {
+ if ( oSettings.oApi._fnPageChange( oSettings, "previous" ) )
+ {
+ /* Only draw when the page has actually changed */
+ fnCallbackDraw( oSettings );
+ }
+ } );
+
+ $(nNext).click( function() {
+ if ( oSettings.oApi._fnPageChange( oSettings, "next" ) )
+ {
+ fnCallbackDraw( oSettings );
+ }
+ } );
+
+ /* Take the brutal approach to cancelling text selection */
+ $(nPrevious).bind( 'selectstart', function () { return false; } );
+ $(nNext).bind( 'selectstart', function () { return false; } );
+
+ /* ID the first elements only */
+ if ( oSettings.sTableId !== '' && typeof oSettings.aanFeatures.p == "undefined" )
+ {
+ nPaging.setAttribute( 'id', oSettings.sTableId+'_paginate' );
+ nPrevious.setAttribute( 'id', oSettings.sTableId+'_previous' );
+ nNext.setAttribute( 'id', oSettings.sTableId+'_next' );
+ }
+ },
+
+ /*
+ * Function: oPagination.two_button.fnUpdate
+ * Purpose: Update the two button pagination at the end of the draw
+ * Returns: -
+ * Inputs: object:oSettings - dataTables settings object
+ * function:fnCallbackDraw - draw function to call on page change
+ */
+ "fnUpdate": function ( oSettings, fnCallbackDraw )
+ {
+ if ( !oSettings.aanFeatures.p )
+ {
+ return;
+ }
+
+ /* Loop over each instance of the pager */
+ var an = oSettings.aanFeatures.p;
+ for ( var i=0, iLen=an.length ; i<iLen ; i++ )
+ {
+ if ( an[i].childNodes.length !== 0 )
+ {
+ an[i].childNodes[0].className =
+ ( oSettings._iDisplayStart === 0 ) ?
+ oSettings.oClasses.sPagePrevDisabled : oSettings.oClasses.sPagePrevEnabled;
+
+ an[i].childNodes[1].className =
+ ( oSettings.fnDisplayEnd() == oSettings.fnRecordsDisplay() ) ?
+ oSettings.oClasses.sPageNextDisabled : oSettings.oClasses.sPageNextEnabled;
+ }
+ }
+ }
+ },
+
+
+ /*
+ * Variable: iFullNumbersShowPages
+ * Purpose: Change the number of pages which can be seen
+ * Scope: jQuery.fn.dataTableExt.oPagination
+ */
+ "iFullNumbersShowPages": 5,
+
+ /*
+ * Variable: full_numbers
+ * Purpose: Full numbers pagination
+ * Scope: jQuery.fn.dataTableExt.oPagination
+ */
+ "full_numbers": {
+ /*
+ * Function: oPagination.full_numbers.fnInit
+ * Purpose: Initalise dom elements required for pagination with a list of the pages
+ * Returns: -
+ * Inputs: object:oSettings - dataTables settings object
+ * node:nPaging - the DIV which contains this pagination control
+ * function:fnCallbackDraw - draw function which must be called on update
+ */
+ "fnInit": function ( oSettings, nPaging, fnCallbackDraw )
+ {
+ var nFirst = document.createElement( 'span' );
+ var nPrevious = document.createElement( 'span' );
+ var nList = document.createElement( 'span' );
+ var nNext = document.createElement( 'span' );
+ var nLast = document.createElement( 'span' );
+
+ nFirst.innerHTML = oSettings.oLanguage.oPaginate.sFirst;
+ nPrevious.innerHTML = oSettings.oLanguage.oPaginate.sPrevious;
+ nNext.innerHTML = oSettings.oLanguage.oPaginate.sNext;
+ nLast.innerHTML = oSettings.oLanguage.oPaginate.sLast;
+
+ var oClasses = oSettings.oClasses;
+ nFirst.className = oClasses.sPageButton+" "+oClasses.sPageFirst;
+ nPrevious.className = oClasses.sPageButton+" "+oClasses.sPagePrevious;
+ nNext.className= oClasses.sPageButton+" "+oClasses.sPageNext;
+ nLast.className = oClasses.sPageButton+" "+oClasses.sPageLast;
+
+ nPaging.appendChild( nFirst );
+ nPaging.appendChild( nPrevious );
+ nPaging.appendChild( nList );
+ nPaging.appendChild( nNext );
+ nPaging.appendChild( nLast );
+
+ $(nFirst).click( function () {
+ if ( oSettings.oApi._fnPageChange( oSettings, "first" ) )
+ {
+ fnCallbackDraw( oSettings );
+ }
+ } );
+
+ $(nPrevious).click( function() {
+ if ( oSettings.oApi._fnPageChange( oSettings, "previous" ) )
+ {
+ fnCallbackDraw( oSettings );
+ }
+ } );
+
+ $(nNext).click( function() {
+ if ( oSettings.oApi._fnPageChange( oSettings, "next" ) )
+ {
+ fnCallbackDraw( oSettings );
+ }
+ } );
+
+ $(nLast).click( function() {
+ if ( oSettings.oApi._fnPageChange( oSettings, "last" ) )
+ {
+ fnCallbackDraw( oSettings );
+ }
+ } );
+
+ /* Take the brutal approach to cancelling text selection */
+ $('span', nPaging)
+ .bind( 'mousedown', function () { return false; } )
+ .bind( 'selectstart', function () { return false; } );
+
+ /* ID the first elements only */
+ if ( oSettings.sTableId !== '' && typeof oSettings.aanFeatures.p == "undefined" )
+ {
+ nPaging.setAttribute( 'id', oSettings.sTableId+'_paginate' );
+ nFirst.setAttribute( 'id', oSettings.sTableId+'_first' );
+ nPrevious.setAttribute( 'id', oSettings.sTableId+'_previous' );
+ nNext.setAttribute( 'id', oSettings.sTableId+'_next' );
+ nLast.setAttribute( 'id', oSettings.sTableId+'_last' );
+ }
+ },
+
+ /*
+ * Function: oPagination.full_numbers.fnUpdate
+ * Purpose: Update the list of page buttons shows
+ * Returns: -
+ * Inputs: object:oSettings - dataTables settings object
+ * function:fnCallbackDraw - draw function to call on page change
+ */
+ "fnUpdate": function ( oSettings, fnCallbackDraw )
+ {
+ if ( !oSettings.aanFeatures.p )
+ {
+ return;
+ }
+
+ var iPageCount = _oExt.oPagination.iFullNumbersShowPages;
+ var iPageCountHalf = Math.floor(iPageCount / 2);
+ var iPages = Math.ceil((oSettings.fnRecordsDisplay()) / oSettings._iDisplayLength);
+ var iCurrentPage = Math.ceil(oSettings._iDisplayStart / oSettings._iDisplayLength) + 1;
+ var sList = "";
+ var iStartButton, iEndButton, i, iLen;
+ var oClasses = oSettings.oClasses;
+
+ /* Pages calculation */
+ if (iPages < iPageCount)
+ {
+ iStartButton = 1;
+ iEndButton = iPages;
+ }
+ else
+ {
+ if (iCurrentPage <= iPageCountHalf)
+ {
+ iStartButton = 1;
+ iEndButton = iPageCount;
+ }
+ else
+ {
+ if (iCurrentPage >= (iPages - iPageCountHalf))
+ {
+ iStartButton = iPages - iPageCount + 1;
+ iEndButton = iPages;
+ }
+ else
+ {
+ iStartButton = iCurrentPage - Math.ceil(iPageCount / 2) + 1;
+ iEndButton = iStartButton + iPageCount - 1;
+ }
+ }
+ }
+
+ /* Build the dynamic list */
+ for ( i=iStartButton ; i<=iEndButton ; i++ )
+ {
+ if ( iCurrentPage != i )
+ {
+ sList += '<span class="'+oClasses.sPageButton+'">'+i+'</span>';
+ }
+ else
+ {
+ sList += '<span class="'+oClasses.sPageButtonActive+'">'+i+'</span>';
+ }
+ }
+
+ /* Loop over each instance of the pager */
+ var an = oSettings.aanFeatures.p;
+ var anButtons, anStatic, nPaginateList;
+ var fnClick = function() {
+ /* Use the information in the element to jump to the required page */
+ var iTarget = (this.innerHTML * 1) - 1;
+ oSettings._iDisplayStart = iTarget * oSettings._iDisplayLength;
+ fnCallbackDraw( oSettings );
+ return false;
+ };
+ var fnFalse = function () { return false; };
+
+ for ( i=0, iLen=an.length ; i<iLen ; i++ )
+ {
+ if ( an[i].childNodes.length === 0 )
+ {
+ continue;
+ }
+
+ /* Build up the dynamic list forst - html and listeners */
+ var qjPaginateList = $('span:eq(2)', an[i]);
+ qjPaginateList.html( sList );
+ $('span', qjPaginateList).click( fnClick ).bind( 'mousedown', fnFalse )
+ .bind( 'selectstart', fnFalse );
+
+ /* Update the 'premanent botton's classes */
+ anButtons = an[i].getElementsByTagName('span');
+ anStatic = [
+ anButtons[0], anButtons[1],
+ anButtons[anButtons.length-2], anButtons[anButtons.length-1]
+ ];
+ $(anStatic).removeClass( oClasses.sPageButton+" "+oClasses.sPageButtonActive+" "+oClasses.sPageButtonStaticDisabled );
+ if ( iCurrentPage == 1 )
+ {
+ anStatic[0].className += " "+oClasses.sPageButtonStaticDisabled;
+ anStatic[1].className += " "+oClasses.sPageButtonStaticDisabled;
+ }
+ else
+ {
+ anStatic[0].className += " "+oClasses.sPageButton;
+ anStatic[1].className += " "+oClasses.sPageButton;
+ }
+
+ if ( iPages === 0 || iCurrentPage == iPages || oSettings._iDisplayLength == -1 )
+ {
+ anStatic[2].className += " "+oClasses.sPageButtonStaticDisabled;
+ anStatic[3].className += " "+oClasses.sPageButtonStaticDisabled;
+ }
+ else
+ {
+ anStatic[2].className += " "+oClasses.sPageButton;
+ anStatic[3].className += " "+oClasses.sPageButton;
+ }
+ }
+ }
+ }
+ };
+
+ /*
+ * Variable: oSort
+ * Purpose: Wrapper for the sorting functions that can be used in DataTables
+ * Scope: jQuery.fn.dataTableExt
+ * Notes: The functions provided in this object are basically standard javascript sort
+ * functions - they expect two inputs which they then compare and then return a priority
+ * result. For each sort method added, two functions need to be defined, an ascending sort and
+ * a descending sort.
+ */
+ _oExt.oSort = {
+ /*
+ * text sorting
+ */
+ "string-asc": function ( a, b )
+ {
+ var x = a.toLowerCase();
+ var y = b.toLowerCase();
+ return ((x < y) ? -1 : ((x > y) ? 1 : 0));
+ },
+
+ "string-desc": function ( a, b )
+ {
+ var x = a.toLowerCase();
+ var y = b.toLowerCase();
+ return ((x < y) ? 1 : ((x > y) ? -1 : 0));
+ },
+
+
+ /*
+ * html sorting (ignore html tags)
+ */
+ "html-asc": function ( a, b )
+ {
+ var x = a.replace( /<.*?>/g, "" ).toLowerCase();
+ var y = b.replace( /<.*?>/g, "" ).toLowerCase();
+ return ((x < y) ? -1 : ((x > y) ? 1 : 0));
+ },
+
+ "html-desc": function ( a, b )
+ {
+ var x = a.replace( /<.*?>/g, "" ).toLowerCase();
+ var y = b.replace( /<.*?>/g, "" ).toLowerCase();
+ return ((x < y) ? 1 : ((x > y) ? -1 : 0));
+ },
+
+
+ /*
+ * date sorting
+ */
+ "date-asc": function ( a, b )
+ {
+ var x = Date.parse( a );
+ var y = Date.parse( b );
+
+ if ( isNaN(x) || x==="" )
+ {
+ x = Date.parse( "01/01/1970 00:00:00" );
+ }
+ if ( isNaN(y) || y==="" )
+ {
+ y = Date.parse( "01/01/1970 00:00:00" );
+ }
+
+ return x - y;
+ },
+
+ "date-desc": function ( a, b )
+ {
+ var x = Date.parse( a );
+ var y = Date.parse( b );
+
+ if ( isNaN(x) || x==="" )
+ {
+ x = Date.parse( "01/01/1970 00:00:00" );
+ }
+ if ( isNaN(y) || y==="" )
+ {
+ y = Date.parse( "01/01/1970 00:00:00" );
+ }
+
+ return y - x;
+ },
+
+
+ /*
+ * numerical sorting
+ */
+ "numeric-asc": function ( a, b )
+ {
+ var x = (a=="-" || a==="") ? 0 : a*1;
+ var y = (b=="-" || b==="") ? 0 : b*1;
+ return x - y;
+ },
+
+ "numeric-desc": function ( a, b )
+ {
+ var x = (a=="-" || a==="") ? 0 : a*1;
+ var y = (b=="-" || b==="") ? 0 : b*1;
+ return y - x;
+ }
+ };
+
+
+ /*
+ * Variable: aTypes
+ * Purpose: Container for the various type of type detection that dataTables supports
+ * Scope: jQuery.fn.dataTableExt
+ * Notes: The functions in this array are expected to parse a string to see if it is a data
+ * type that it recognises. If so then the function should return the name of the type (a
+ * corresponding sort function should be defined!), if the type is not recognised then the
+ * function should return null such that the parser and move on to check the next type.
+ * Note that ordering is important in this array - the functions are processed linearly,
+ * starting at index 0.
+ * Note that the input for these functions is always a string! It cannot be any other data
+ * type
+ */
+ _oExt.aTypes = [
+ /*
+ * Function: -
+ * Purpose: Check to see if a string is numeric
+ * Returns: string:'numeric' or null
+ * Inputs: string:sText - string to check
+ */
+ function ( sData )
+ {
+ /* Allow zero length strings as a number */
+ if ( sData.length === 0 )
+ {
+ return 'numeric';
+ }
+
+ var sValidFirstChars = "0123456789-";
+ var sValidChars = "0123456789.";
+ var Char;
+ var bDecimal = false;
+
+ /* Check for a valid first char (no period and allow negatives) */
+ Char = sData.charAt(0);
+ if (sValidFirstChars.indexOf(Char) == -1)
+ {
+ return null;
+ }
+
+ /* Check all the other characters are valid */
+ for ( var i=1 ; i<sData.length ; i++ )
+ {
+ Char = sData.charAt(i);
+ if (sValidChars.indexOf(Char) == -1)
+ {
+ return null;
+ }
+
+ /* Only allowed one decimal place... */
+ if ( Char == "." )
+ {
+ if ( bDecimal )
+ {
+ return null;
+ }
+ bDecimal = true;
+ }
+ }
+
+ return 'numeric';
+ },
+
+ /*
+ * Function: -
+ * Purpose: Check to see if a string is actually a formatted date
+ * Returns: string:'date' or null
+ * Inputs: string:sText - string to check
+ */
+ function ( sData )
+ {
+ var iParse = Date.parse(sData);
+ if ( (iParse !== null && !isNaN(iParse)) || sData.length === 0 )
+ {
+ return 'date';
+ }
+ return null;
+ },
+
+ /*
+ * Function: -
+ * Purpose: Check to see if a string should be treated as an HTML string
+ * Returns: string:'html' or null
+ * Inputs: string:sText - string to check
+ */
+ function ( sData )
+ {
+ if ( sData.indexOf('<') != -1 && sData.indexOf('>') != -1 )
+ {
+ return 'html';
+ }
+ return null;
+ }
+ ];
+
+ /*
+ * Function: fnVersionCheck
+ * Purpose: Check a version string against this version of DataTables. Useful for plug-ins
+ * Returns: bool:true -this version of DataTables is greater or equal to the required version
+ * false -this version of DataTales is not suitable
+ * Inputs: string:sVersion - the version to check against. May be in the following formats:
+ * "a", "a.b" or "a.b.c"
+ * Notes: This function will only check the first three parts of a version string. It is
+ * assumed that beta and dev versions will meet the requirements. This might change in future
+ */
+ _oExt.fnVersionCheck = function( sVersion )
+ {
+ /* This is cheap, but very effective */
+ var fnZPad = function (Zpad, count)
+ {
+ while(Zpad.length < count) {
+ Zpad += '0';
+ }
+ return Zpad;
+ };
+ var aThis = _oExt.sVersion.split('.');
+ var aThat = sVersion.split('.');
+ var sThis = '', sThat = '';
+
+ for ( var i=0, iLen=aThat.length ; i<iLen ; i++ )
+ {
+ sThis += fnZPad( aThis[i], 3 );
+ sThat += fnZPad( aThat[i], 3 );
+ }
+
+ return parseInt(sThis, 10) >= parseInt(sThat, 10);
+ };
+
+ /*
+ * Variable: _oExternConfig
+ * Purpose: Store information for DataTables to access globally about other instances
+ * Scope: jQuery.fn.dataTableExt
+ */
+ _oExt._oExternConfig = {
+ /* int:iNextUnique - next unique number for an instance */
+ "iNextUnique": 0
+ };
+
+
+ /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+ * Section - DataTables prototype
+ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
+
+ /*
+ * Function: dataTable
+ * Purpose: DataTables information
+ * Returns: -
+ * Inputs: object:oInit - initalisation options for the table
+ */
+ $.fn.dataTable = function( oInit )
+ {
+ /*
+ * Function: classSettings
+ * Purpose: Settings container function for all 'class' properties which are required
+ * by dataTables
+ * Returns: -
+ * Inputs: -
+ */
+ function classSettings ()
+ {
+ this.fnRecordsTotal = function ()
+ {
+ if ( this.oFeatures.bServerSide ) {
+ return this._iRecordsTotal;
+ } else {
+ return this.aiDisplayMaster.length;
+ }
+ };
+
+ this.fnRecordsDisplay = function ()
+ {
+ if ( this.oFeatures.bServerSide ) {
+ return this._iRecordsDisplay;
+ } else {
+ return this.aiDisplay.length;
+ }
+ };
+
+ this.fnDisplayEnd = function ()
+ {
+ if ( this.oFeatures.bServerSide ) {
+ if ( this.oFeatures.bPaginate === false || this._iDisplayLength == -1 ) {
+ return this._iDisplayStart+this.aiDisplay.length;
+ } else {
+ return Math.min( this._iDisplayStart+this._iDisplayLength,
+ this._iRecordsDisplay );
+ }
+ } else {
+ return this._iDisplayEnd;
+ }
+ };
+
+ /*
+ * Variable: oInstance
+ * Purpose: The DataTables object for this table
+ * Scope: jQuery.dataTable.classSettings
+ */
+ this.oInstance = null;
+
+ /*
+ * Variable: sInstance
+ * Purpose: Unique idendifier for each instance of the DataTables object
+ * Scope: jQuery.dataTable.classSettings
+ */
+ this.sInstance = null;
+
+ /*
+ * Variable: oFeatures
+ * Purpose: Indicate the enablement of key dataTable features
+ * Scope: jQuery.dataTable.classSettings
+ */
+ this.oFeatures = {
+ "bPaginate": true,
+ "bLengthChange": true,
+ "bFilter": true,
+ "bSort": true,
+ "bInfo": true,
+ "bAutoWidth": true,
+ "bProcessing": false,
+ "bSortClasses": true,
+ "bStateSave": false,
+ "bServerSide": false
+ };
+
+ /*
+ * Variable: oScroll
+ * Purpose: Container for scrolling options
+ * Scope: jQuery.dataTable.classSettings
+ */
+ this.oScroll = {
+ "sX": "",
+ "sXInner": "",
+ "sY": "",
+ "bCollapse": false,
+ "bInfinite": false,
+ "iLoadGap": 100,
+ "iBarWidth": 0
+ };
+
+ /*
+ * Variable: aanFeatures
+ * Purpose: Array referencing the nodes which are used for the features
+ * Scope: jQuery.dataTable.classSettings
+ * Notes: The parameters of this object match what is allowed by sDom - i.e.
+ * 'l' - Length changing
+ * 'f' - Filtering input
+ * 't' - The table!
+ * 'i' - Information
+ * 'p' - Pagination
+ * 'r' - pRocessing
+ */
+ this.aanFeatures = [];
+
+ /*
+ * Variable: oLanguage
+ * Purpose: Store the language strings used by dataTables
+ * Scope: jQuery.dataTable.classSettings
+ * Notes: The words in the format _VAR_ are variables which are dynamically replaced
+ * by javascript
+ */
+ this.oLanguage = {
+ "sProcessing": "Processing...",
+ "sLengthMenu": "Show _MENU_ entries",
+ "sZeroRecords": "No matching records found",
+ "sEmptyTable": "No data available in table",
+ "sInfo": "Showing _START_ to _END_ of _TOTAL_ entries",
+ "sInfoEmpty": "Showing 0 to 0 of 0 entries",
+ "sInfoFiltered": "(filtered from _MAX_ total entries)",
+ "sInfoPostFix": "",
+ "sSearch": "Search:",
+ "sUrl": "",
+ "oPaginate": {
+ "sFirst": "First",
+ "sPrevious": "Previous",
+ "sNext": "Next",
+ "sLast": "Last"
+ },
+ "fnInfoCallback": null
+ };
+
+ /*
+ * Variable: aoData
+ * Purpose: Store data information
+ * Scope: jQuery.dataTable.classSettings
+ * Notes: This is an array of objects with the following parameters:
+ * int: _iId - internal id for tracking
+ * array: _aData - internal data - used for sorting / filtering etc
+ * node: nTr - display node
+ * array node: _anHidden - hidden TD nodes
+ * string: _sRowStripe
+ */
+ this.aoData = [];
+
+ /*
+ * Variable: aiDisplay
+ * Purpose: Array of indexes which are in the current display (after filtering etc)
+ * Scope: jQuery.dataTable.classSettings
+ */
+ this.aiDisplay = [];
+
+ /*
+ * Variable: aiDisplayMaster
+ * Purpose: Array of indexes for display - no filtering
+ * Scope: jQuery.dataTable.classSettings
+ */
+ this.aiDisplayMaster = [];
+
+ /*
+ * Variable: aoColumns
+ * Purpose: Store information about each column that is in use
+ * Scope: jQuery.dataTable.classSettings
+ */
+ this.aoColumns = [];
+
+ /*
+ * Variable: iNextId
+ * Purpose: Store the next unique id to be used for a new row
+ * Scope: jQuery.dataTable.classSettings
+ */
+ this.iNextId = 0;
+
+ /*
+ * Variable: asDataSearch
+ * Purpose: Search data array for regular expression searching
+ * Scope: jQuery.dataTable.classSettings
+ */
+ this.asDataSearch = [];
+
+ /*
+ * Variable: oPreviousSearch
+ * Purpose: Store the previous search incase we want to force a re-search
+ * or compare the old search to a new one
+ * Scope: jQuery.dataTable.classSettings
+ */
+ this.oPreviousSearch = {
+ "sSearch": "",
+ "bRegex": false,
+ "bSmart": true
+ };
+
+ /*
+ * Variable: aoPreSearchCols
+ * Purpose: Store the previous search for each column
+ * Scope: jQuery.dataTable.classSettings
+ */
+ this.aoPreSearchCols = [];
+
+ /*
+ * Variable: aaSorting
+ * Purpose: Sorting information
+ * Scope: jQuery.dataTable.classSettings
+ * Notes: Index 0 - column number
+ * Index 1 - current sorting direction
+ * Index 2 - index of asSorting for this column
+ */
+ this.aaSorting = [ [0, 'asc', 0] ];
+
+ /*
+ * Variable: aaSortingFixed
+ * Purpose: Sorting information that is always applied
+ * Scope: jQuery.dataTable.classSettings
+ */
+ this.aaSortingFixed = null;
+
+ /*
+ * Variable: asStripClasses
+ * Purpose: Classes to use for the striping of a table
+ * Scope: jQuery.dataTable.classSettings
+ */
+ this.asStripClasses = [];
+
+ /*
+ * Variable: asDestoryStrips
+ * Purpose: If restoring a table - we should restore it's striping classes as well
+ * Scope: jQuery.dataTable.classSettings
+ */
+ this.asDestoryStrips = [];
+
+ /*
+ * Variable: sDestroyWidth
+ * Purpose: If restoring a table - we should restore it's width
+ * Scope: jQuery.dataTable.classSettings
+ */
+ this.sDestroyWidth = 0;
+
+ /*
+ * Variable: fnRowCallback
+ * Purpose: Call this function every time a row is inserted (draw)
+ * Scope: jQuery.dataTable.classSettings
+ */
+ this.fnRowCallback = null;
+
+ /*
+ * Variable: fnHeaderCallback
+ * Purpose: Callback function for the header on each draw
+ * Scope: jQuery.dataTable.classSettings
+ */
+ this.fnHeaderCallback = null;
+
+ /*
+ * Variable: fnFooterCallback
+ * Purpose: Callback function for the footer on each draw
+ * Scope: jQuery.dataTable.classSettings
+ */
+ this.fnFooterCallback = null;
+
+ /*
+ * Variable: aoDrawCallback
+ * Purpose: Array of callback functions for draw callback functions
+ * Scope: jQuery.dataTable.classSettings
+ * Notes: Each array element is an object with the following parameters:
+ * function:fn - function to call
+ * string:sName - name callback (feature). useful for arranging array
+ */
+ this.aoDrawCallback = [];
+
+ /*
+ * Variable: fnInitComplete
+ * Purpose: Callback function for when the table has been initalised
+ * Scope: jQuery.dataTable.classSettings
+ */
+ this.fnInitComplete = null;
+
+ /*
+ * Variable: sTableId
+ * Purpose: Cache the table ID for quick access
+ * Scope: jQuery.dataTable.classSettings
+ */
+ this.sTableId = "";
+
+ /*
+ * Variable: nTable
+ * Purpose: Cache the table node for quick access
+ * Scope: jQuery.dataTable.classSettings
+ */
+ this.nTable = null;
+
+ /*
+ * Variable: nTHead
+ * Purpose: Permanent ref to the thead element
+ * Scope: jQuery.dataTable.classSettings
+ */
+ this.nTHead = null;
+
+ /*
+ * Variable: nTFoot
+ * Purpose: Permanent ref to the tfoot element - if it exists
+ * Scope: jQuery.dataTable.classSettings
+ */
+ this.nTFoot = null;
+
+ /*
+ * Variable: nTBody
+ * Purpose: Permanent ref to the tbody element
+ * Scope: jQuery.dataTable.classSettings
+ */
+ this.nTBody = null;
+
+ /*
+ * Variable: nTableWrapper
+ * Purpose: Cache the wrapper node (contains all DataTables controlled elements)
+ * Scope: jQuery.dataTable.classSettings
+ */
+ this.nTableWrapper = null;
+
+ /*
+ * Variable: iDefaultSortIndex
+ * Purpose: Sorting index which will be used by default
+ * Scope: jQuery.dataTable.classSettings
+ */
+ this.iDefaultSortIndex = 0;
+
+ /*
+ * Variable: bInitialised
+ * Purpose: Indicate if all required information has been read in
+ * Scope: jQuery.dataTable.classSettings
+ */
+ this.bInitialised = false;
+
+ /*
+ * Variable: aoOpenRows
+ * Purpose: Information about open rows
+ * Scope: jQuery.dataTable.classSettings
+ * Notes: Has the parameters 'nTr' and 'nParent'
+ */
+ this.aoOpenRows = [];
+
+ /*
+ * Variable: sDom
+ * Purpose: Dictate the positioning that the created elements will take
+ * Scope: jQuery.dataTable.classSettings
+ * Notes:
+ * The following options are allowed:
+ * 'l' - Length changing
+ * 'f' - Filtering input
+ * 't' - The table!
+ * 'i' - Information
+ * 'p' - Pagination
+ * 'r' - pRocessing
+ * The following constants are allowed:
+ * 'H' - jQueryUI theme "header" classes
+ * 'F' - jQueryUI theme "footer" classes
+ * The following syntax is expected:
+ * '<' and '>' - div elements
+ * '<"class" and '>' - div with a class
+ * Examples:
+ * '<"wrapper"flipt>', '<lf<t>ip>'
+ */
+ this.sDom = 'lfrtip';
+
+ /*
+ * Variable: sPaginationType
+ * Purpose: Note which type of sorting should be used
+ * Scope: jQuery.dataTable.classSettings
+ */
+ this.sPaginationType = "two_button";
+
+ /*
+ * Variable: iCookieDuration
+ * Purpose: The cookie duration (for bStateSave) in seconds - default 2 hours
+ * Scope: jQuery.dataTable.classSettings
+ */
+ this.iCookieDuration = 60 * 60 * 2;
+
+ /*
+ * Variable: sCookiePrefix
+ * Purpose: The cookie name prefix
+ * Scope: jQuery.dataTable.classSettings
+ */
+ this.sCookiePrefix = "SpryMedia_DataTables_";
+
+ /*
+ * Variable: fnCookieCallback
+ * Purpose: Callback function for cookie creation
+ * Scope: jQuery.dataTable.classSettings
+ */
+ this.fnCookieCallback = null;
+
+ /*
+ * Variable: sAjaxSource
+ * Purpose: Source url for AJAX data for the table
+ * Scope: jQuery.dataTable.classSettings
+ */
+ this.sAjaxSource = null;
+
+ /*
+ * Variable: bAjaxDataGet
+ * Purpose: Note if draw should be blocked while getting data
+ * Scope: jQuery.dataTable.classSettings
+ */
+ this.bAjaxDataGet = true;
+
+ /*
+ * Variable: fnServerData
+ * Purpose: Function to get the server-side data - can be overruled by the developer
+ * Scope: jQuery.dataTable.classSettings
+ */
+ this.fnServerData = function ( url, data, callback ) {
+ $.ajax( {
+ "url": url,
+ "data": data,
+ "success": callback,
+ "dataType": "json",
+ "cache": false,
+ "error": function () {
+ alert( "DataTables warning: JSON data from server failed to load or be parsed. "+
+ "This is most likely to be caused by a JSON formatting error." );
+ }
+ } );
+ };
+
+ /*
+ * Variable: fnFormatNumber
+ * Purpose: Format numbers for display
+ * Scope: jQuery.dataTable.classSettings
+ */
+ this.fnFormatNumber = function ( iIn )
+ {
+ if ( iIn < 1000 )
+ {
+ /* A small optimisation for what is likely to be the vast majority of use cases */
+ return iIn;
+ }
+ else
+ {
+ var s=(iIn+""), a=s.split(""), out="", iLen=s.length;
+
+ for ( var i=0 ; i<iLen ; i++ )
+ {
+ if ( i%3 === 0 && i !== 0 )
+ {
+ out = ','+out;
+ }
+ out = a[iLen-i-1]+out;
+ }
+ }
+ return out;
+ };
+
+ /*
+ * Variable: aLengthMenu
+ * Purpose: List of options that can be used for the user selectable length menu
+ * Scope: jQuery.dataTable.classSettings
+ * Note: This varaible can take for form of a 1D array, in which case the value and the
+ * displayed value in the menu are the same, or a 2D array in which case the value comes
+ * from the first array, and the displayed value to the end user comes from the second
+ * array. 2D example: [ [ 10, 25, 50, 100, -1 ], [ 10, 25, 50, 100, 'All' ] ];
+ */
+ this.aLengthMenu = [ 10, 25, 50, 100 ];
+
+ /*
+ * Variable: iDraw
+ * Purpose: Counter for the draws that the table does. Also used as a tracker for
+ * server-side processing
+ * Scope: jQuery.dataTable.classSettings
+ */
+ this.iDraw = 0;
+
+ /*
+ * Variable: bDrawing
+ * Purpose: Indicate if a redraw is being done - useful for Ajax
+ * Scope: jQuery.dataTable.classSettings
+ */
+ this.bDrawing = 0;
+
+ /*
+ * Variable: iDrawError
+ * Purpose: Last draw error
+ * Scope: jQuery.dataTable.classSettings
+ */
+ this.iDrawError = -1;
+
+ /*
+ * Variable: _iDisplayLength, _iDisplayStart, _iDisplayEnd
+ * Purpose: Display length variables
+ * Scope: jQuery.dataTable.classSettings
+ * Notes: These variable must NOT be used externally to get the data length. Rather, use
+ * the fnRecordsTotal() (etc) functions.
+ */
+ this._iDisplayLength = 10;
+ this._iDisplayStart = 0;
+ this._iDisplayEnd = 10;
+
+ /*
+ * Variable: _iRecordsTotal, _iRecordsDisplay
+ * Purpose: Display length variables used for server side processing
+ * Scope: jQuery.dataTable.classSettings
+ * Notes: These variable must NOT be used externally to get the data length. Rather, use
+ * the fnRecordsTotal() (etc) functions.
+ */
+ this._iRecordsTotal = 0;
+ this._iRecordsDisplay = 0;
+
+ /*
+ * Variable: bJUI
+ * Purpose: Should we add the markup needed for jQuery UI theming?
+ * Scope: jQuery.dataTable.classSettings
+ */
+ this.bJUI = false;
+
+ /*
+ * Variable: bJUI
+ * Purpose: Should we add the markup needed for jQuery UI theming?
+ * Scope: jQuery.dataTable.classSettings
+ */
+ this.oClasses = _oExt.oStdClasses;
+
+ /*
+ * Variable: bFiltered and bSorted
+ * Purpose: Flags to allow callback functions to see what actions have been performed
+ * Scope: jQuery.dataTable.classSettings
+ */
+ this.bFiltered = false;
+ this.bSorted = false;
+
+ /*
+ * Variable: oInit
+ * Purpose: Initialisation object that is used for the table
+ * Scope: jQuery.dataTable.classSettings
+ */
+ this.oInit = null;
+ }
+
+ /*
+ * Variable: oApi
+ * Purpose: Container for publicly exposed 'private' functions
+ * Scope: jQuery.dataTable
+ */
+ this.oApi = {};
+
+
+ /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+ * Section - API functions
+ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
+
+ /*
+ * Function: fnDraw
+ * Purpose: Redraw the table
+ * Returns: -
+ * Inputs: bool:bComplete - Refilter and resort (if enabled) the table before the draw.
+ * Optional: default - true
+ */
+ this.fnDraw = function( bComplete )
+ {
+ var oSettings = _fnSettingsFromNode( this[_oExt.iApiIndex] );
+ if ( typeof bComplete != 'undefined' && bComplete === false )
+ {
+ _fnCalculateEnd( oSettings );
+ _fnDraw( oSettings );
+ }
+ else
+ {
+ _fnReDraw( oSettings );
+ }
+ };
+
+ /*
+ * Function: fnFilter
+ * Purpose: Filter the input based on data
+ * Returns: -
+ * Inputs: string:sInput - string to filter the table on
+ * int:iColumn - optional - column to limit filtering to
+ * bool:bRegex - optional - treat as regular expression or not - default false
+ * bool:bSmart - optional - perform smart filtering or not - default true
+ * bool:bShowGlobal - optional - show the input global filter in it's input box(es)
+ * - default true
+ */
+ this.fnFilter = function( sInput, iColumn, bRegex, bSmart, bShowGlobal )
+ {
+ var oSettings = _fnSettingsFromNode( this[_oExt.iApiIndex] );
+
+ if ( !oSettings.oFeatures.bFilter )
+ {
+ return;
+ }
+
+ if ( typeof bRegex == 'undefined' )
+ {
+ bRegex = false;
+ }
+
+ if ( typeof bSmart == 'undefined' )
+ {
+ bSmart = true;
+ }
+
+ if ( typeof bShowGlobal == 'undefined' )
+ {
+ bShowGlobal = true;
+ }
+
+ if ( typeof iColumn == "undefined" || iColumn === null )
+ {
+ /* Global filter */
+ _fnFilterComplete( oSettings, {
+ "sSearch":sInput,
+ "bRegex": bRegex,
+ "bSmart": bSmart
+ }, 1 );
+
+ if ( bShowGlobal && typeof oSettings.aanFeatures.f != 'undefined' )
+ {
+ var n = oSettings.aanFeatures.f;
+ for ( var i=0, iLen=n.length ; i<iLen ; i++ )
+ {
+ $('input', n[i]).val( sInput );
+ }
+ }
+ }
+ else
+ {
+ /* Single column filter */
+ oSettings.aoPreSearchCols[ iColumn ].sSearch = sInput;
+ oSettings.aoPreSearchCols[ iColumn ].bRegex = bRegex;
+ oSettings.aoPreSearchCols[ iColumn ].bSmart = bSmart;
+ _fnFilterComplete( oSettings, oSettings.oPreviousSearch, 1 );
+ }
+ };
+
+ /*
+ * Function: fnSettings
+ * Purpose: Get the settings for a particular table for extern. manipulation
+ * Returns: -
+ * Inputs: -
+ */
+ this.fnSettings = function( nNode )
+ {
+ return _fnSettingsFromNode( this[_oExt.iApiIndex] );
+ };
+
+ /*
+ * Function: fnVersionCheck
+ * Notes: The function is the same as the 'static' function provided in the ext variable
+ */
+ this.fnVersionCheck = _oExt.fnVersionCheck;
+
+ /*
+ * Function: fnSort
+ * Purpose: Sort the table by a particular row
+ * Returns: -
+ * Inputs: int:iCol - the data index to sort on. Note that this will
+ * not match the 'display index' if you have hidden data entries
+ */
+ this.fnSort = function( aaSort )
+ {
+ var oSettings = _fnSettingsFromNode( this[_oExt.iApiIndex] );
+ oSettings.aaSorting = aaSort;
+ _fnSort( oSettings );
+ };
+
+ /*
+ * Function: fnSortListener
+ * Purpose: Attach a sort listener to an element for a given column
+ * Returns: -
+ * Inputs: node:nNode - the element to attach the sort listener to
+ * int:iColumn - the column that a click on this node will sort on
+ * function:fnCallback - callback function when sort is run - optional
+ */
+ this.fnSortListener = function( nNode, iColumn, fnCallback )
+ {
+ _fnSortAttachListener( _fnSettingsFromNode( this[_oExt.iApiIndex] ), nNode, iColumn,
+ fnCallback );
+ };
+
+ /*
+ * Function: fnAddData
+ * Purpose: Add new row(s) into the table
+ * Returns: array int: array of indexes (aoData) which have been added (zero length on error)
+ * Inputs: array:mData - the data to be added. The length must match
+ * the original data from the DOM
+ * or
+ * array array:mData - 2D array of data to be added
+ * bool:bRedraw - redraw the table or not - default true
+ * Notes: Warning - the refilter here will cause the table to redraw
+ * starting at zero
+ * Notes: Thanks to Yekimov Denis for contributing the basis for this function!
+ */
+ this.fnAddData = function( mData, bRedraw )
+ {
+ if ( mData.length === 0 )
+ {
+ return [];
+ }
+
+ var aiReturn = [];
+ var iTest;
+
+ /* Find settings from table node */
+ var oSettings = _fnSettingsFromNode( this[_oExt.iApiIndex] );
+
+ /* Check if we want to add multiple rows or not */
+ if ( typeof mData[0] == "object" )
+ {
+ for ( var i=0 ; i<mData.length ; i++ )
+ {
+ iTest = _fnAddData( oSettings, mData[i] );
+ if ( iTest == -1 )
+ {
+ return aiReturn;
+ }
+ aiReturn.push( iTest );
+ }
+ }
+ else
+ {
+ iTest = _fnAddData( oSettings, mData );
+ if ( iTest == -1 )
+ {
+ return aiReturn;
+ }
+ aiReturn.push( iTest );
+ }
+
+ oSettings.aiDisplay = oSettings.aiDisplayMaster.slice();
+
+ /* Rebuild the search */
+ _fnBuildSearchArray( oSettings, 1 );
+
+ if ( typeof bRedraw == 'undefined' || bRedraw )
+ {
+ _fnReDraw( oSettings );
+ }
+ return aiReturn;
+ };
+
+ /*
+ * Function: fnDeleteRow
+ * Purpose: Remove a row for the table
+ * Returns: array:aReturn - the row that was deleted
+ * Inputs: mixed:mTarget -
+ * int: - index of aoData to be deleted, or
+ * node(TR): - TR element you want to delete
+ * function:fnCallBack - callback function - default null
+ * bool:bRedraw - redraw the table or not - default true
+ */
+ this.fnDeleteRow = function( mTarget, fnCallBack, bRedraw )
+ {
+ /* Find settings from table node */
+ var oSettings = _fnSettingsFromNode( this[_oExt.iApiIndex] );
+ var i, iAODataIndex;
+
+ iAODataIndex = (typeof mTarget == 'object') ?
+ _fnNodeToDataIndex(oSettings, mTarget) : mTarget;
+
+ /* Return the data array from this row */
+ var oData = oSettings.aoData.splice( iAODataIndex, 1 );
+
+ /* Delete from the display arrays */
+ _fnDeleteIndex( oSettings.aiDisplayMaster, iAODataIndex );
+ _fnDeleteIndex( oSettings.aiDisplay, iAODataIndex );
+
+ /* Rebuild the search */
+ _fnBuildSearchArray( oSettings, 1 );
+
+ /* If there is a user callback function - call it */
+ if ( typeof fnCallBack == "function" )
+ {
+ fnCallBack.call( this, oSettings, oData );
+ }
+
+ /* Check for an 'overflow' they case for dislaying the table */
+ if ( oSettings._iDisplayStart >= oSettings.aiDisplay.length )
+ {
+ oSettings._iDisplayStart -= oSettings._iDisplayLength;
+ if ( oSettings._iDisplayStart < 0 )
+ {
+ oSettings._iDisplayStart = 0;
+ }
+ }
+
+ if ( typeof bRedraw == 'undefined' || bRedraw )
+ {
+ _fnCalculateEnd( oSettings );
+ _fnDraw( oSettings );
+ }
+
+ return oData;
+ };
+
+ /*
+ * Function: fnClearTable
+ * Purpose: Quickly and simply clear a table
+ * Returns: -
+ * Inputs: bool:bRedraw - redraw the table or not - default true
+ * Notes: Thanks to Yekimov Denis for contributing the basis for this function!
+ */
+ this.fnClearTable = function( bRedraw )
+ {
+ /* Find settings from table node */
+ var oSettings = _fnSettingsFromNode( this[_oExt.iApiIndex] );
+ _fnClearTable( oSettings );
+
+ if ( typeof bRedraw == 'undefined' || bRedraw )
+ {
+ _fnDraw( oSettings );
+ }
+ };
+
+ /*
+ * Function: fnOpen
+ * Purpose: Open a display row (append a row after the row in question)
+ * Returns: node:nNewRow - the row opened
+ * Inputs: node:nTr - the table row to 'open'
+ * string:sHtml - the HTML to put into the row
+ * string:sClass - class to give the new TD cell
+ */
+ this.fnOpen = function( nTr, sHtml, sClass )
+ {
+ /* Find settings from table node */
+ var oSettings = _fnSettingsFromNode( this[_oExt.iApiIndex] );
+
+ /* the old open one if there is one */
+ this.fnClose( nTr );
+
+ var nNewRow = document.createElement("tr");
+ var nNewCell = document.createElement("td");
+ nNewRow.appendChild( nNewCell );
+ nNewCell.className = sClass;
+ nNewCell.colSpan = _fnVisbleColumns( oSettings );
+ nNewCell.innerHTML = sHtml;
+
+ /* If the nTr isn't on the page at the moment - then we don't insert at the moment */
+ var nTrs = $('tr', oSettings.nTBody);
+ if ( $.inArray(nTr, nTrs) != -1 )
+ {
+ $(nNewRow).insertAfter(nTr);
+ }
+
+ oSettings.aoOpenRows.push( {
+ "nTr": nNewRow,
+ "nParent": nTr
+ } );
+
+ return nNewRow;
+ };
+
+ /*
+ * Function: fnClose
+ * Purpose: Close a display row
+ * Returns: int: 0 (success) or 1 (failed)
+ * Inputs: node:nTr - the table row to 'close'
+ */
+ this.fnClose = function( nTr )
+ {
+ /* Find settings from table node */
+ var oSettings = _fnSettingsFromNode( this[_oExt.iApiIndex] );
+
+ for ( var i=0 ; i<oSettings.aoOpenRows.length ; i++ )
+ {
+ if ( oSettings.aoOpenRows[i].nParent == nTr )
+ {
+ var nTrParent = oSettings.aoOpenRows[i].nTr.parentNode;
+ if ( nTrParent )
+ {
+ /* Remove it if it is currently on display */
+ nTrParent.removeChild( oSettings.aoOpenRows[i].nTr );
+ }
+ oSettings.aoOpenRows.splice( i, 1 );
+ return 0;
+ }
+ }
+ return 1;
+ };
+
+ /*
+ * Function: fnGetData
+ * Purpose: Return an array with the data which is used to make up the table
+ * Returns: array array string: 2d data array ([row][column]) or array string: 1d data array
+ * or
+ * array string (if iRow specified)
+ * Inputs: mixed:mRow - optional - if not present, then the full 2D array for the table
+ * if given then:
+ * int: - return 1D array for aoData entry of this index
+ * node(TR): - return 1D array for this TR element
+ * Inputs: int:iRow - optional - if present then the array returned will be the data for
+ * the row with the index 'iRow'
+ */
+ this.fnGetData = function( mRow )
+ {
+ var oSettings = _fnSettingsFromNode( this[_oExt.iApiIndex] );
+
+ if ( typeof mRow != 'undefined' )
+ {
+ var iRow = (typeof mRow == 'object') ?
+ _fnNodeToDataIndex(oSettings, mRow) : mRow;
+ return oSettings.aoData[iRow]._aData;
+ }
+ return _fnGetDataMaster( oSettings );
+ };
+
+ /*
+ * Function: fnGetNodes
+ * Purpose: Return an array with the TR nodes used for drawing the table
+ * Returns: array node: TR elements
+ * or
+ * node (if iRow specified)
+ * Inputs: int:iRow - optional - if present then the array returned will be the node for
+ * the row with the index 'iRow'
+ */
+ this.fnGetNodes = function( iRow )
+ {
+ var oSettings = _fnSettingsFromNode( this[_oExt.iApiIndex] );
+
+ if ( typeof iRow != 'undefined' )
+ {
+ return oSettings.aoData[iRow].nTr;
+ }
+ return _fnGetTrNodes( oSettings );
+ };
+
+ /*
+ * Function: fnGetPosition
+ * Purpose: Get the array indexes of a particular cell from it's DOM element
+ * Returns: int: - row index, or array[ int, int, int ]: - row index, column index (visible)
+ * and column index including hidden columns
+ * Inputs: node:nNode - this can either be a TR or a TD in the table, the return is
+ * dependent on this input
+ */
+ this.fnGetPosition = function( nNode )
+ {
+ var oSettings = _fnSettingsFromNode( this[_oExt.iApiIndex] );
+ var i;
+
+ if ( nNode.nodeName.toUpperCase() == "TR" )
+ {
+ return _fnNodeToDataIndex(oSettings, nNode);
+ }
+ else if ( nNode.nodeName.toUpperCase() == "TD" )
+ {
+ var iDataIndex = _fnNodeToDataIndex(oSettings, nNode.parentNode);
+ var iCorrector = 0;
+ for ( var j=0 ; j<oSettings.aoColumns.length ; j++ )
+ {
+ if ( oSettings.aoColumns[j].bVisible )
+ {
+ if ( oSettings.aoData[iDataIndex].nTr.getElementsByTagName('td')[j-iCorrector] == nNode )
+ {
+ return [ iDataIndex, j-iCorrector, j ];
+ }
+ }
+ else
+ {
+ iCorrector++;
+ }
+ }
+ }
+ return null;
+ };
+
+ /*
+ * Function: fnUpdate
+ * Purpose: Update a table cell or row
+ * Returns: int: 0 okay, 1 error
+ * Inputs: array string 'or' string:mData - data to update the cell/row with
+ * mixed:mRow -
+ * int: - index of aoData to be updated, or
+ * node(TR): - TR element you want to update
+ * int:iColumn - the column to update - optional (not used of mData is 2D)
+ * bool:bRedraw - redraw the table or not - default true
+ * bool:bAction - perform predraw actions or not (you will want this as 'true' if
+ * you have bRedraw as true) - default true
+ */
+ this.fnUpdate = function( mData, mRow, iColumn, bRedraw, bAction )
+ {
+ var oSettings = _fnSettingsFromNode( this[_oExt.iApiIndex] );
+ var iVisibleColumn;
+ var sDisplay;
+ var iRow = (typeof mRow == 'object') ?
+ _fnNodeToDataIndex(oSettings, mRow) : mRow;
+
+ if ( typeof mData != 'object' )
+ {
+ sDisplay = mData;
+ oSettings.aoData[iRow]._aData[iColumn] = sDisplay;
+
+ if ( oSettings.aoColumns[iColumn].fnRender !== null )
+ {
+ sDisplay = oSettings.aoColumns[iColumn].fnRender( {
+ "iDataRow": iRow,
+ "iDataColumn": iColumn,
+ "aData": oSettings.aoData[iRow]._aData,
+ "oSettings": oSettings
+ } );
+
+ if ( oSettings.aoColumns[iColumn].bUseRendered )
+ {
+ oSettings.aoData[iRow]._aData[iColumn] = sDisplay;
+ }
+ }
+
+ iVisibleColumn = _fnColumnIndexToVisible( oSettings, iColumn );
+ if ( iVisibleColumn !== null )
+ {
+ oSettings.aoData[iRow].nTr.getElementsByTagName('td')[iVisibleColumn].innerHTML =
+ sDisplay;
+ }
+ }
+ else
+ {
+ if ( mData.length != oSettings.aoColumns.length )
+ {
+ _fnLog( oSettings, 0, 'An array passed to fnUpdate must have the same number of '+
+ 'columns as the table in question - in this case '+oSettings.aoColumns.length );
+ return 1;
+ }
+
+ for ( var i=0 ; i<mData.length ; i++ )
+ {
+ sDisplay = mData[i];
+ oSettings.aoData[iRow]._aData[i] = sDisplay;
+
+ if ( oSettings.aoColumns[i].fnRender !== null )
+ {
+ sDisplay = oSettings.aoColumns[i].fnRender( {
+ "iDataRow": iRow,
+ "iDataColumn": i,
+ "aData": oSettings.aoData[iRow]._aData,
+ "oSettings": oSettings
+ } );
+
+ if ( oSettings.aoColumns[i].bUseRendered )
+ {
+ oSettings.aoData[iRow]._aData[i] = sDisplay;
+ }
+ }
+
+ iVisibleColumn = _fnColumnIndexToVisible( oSettings, i );
+ if ( iVisibleColumn !== null )
+ {
+ oSettings.aoData[iRow].nTr.getElementsByTagName('td')[iVisibleColumn].innerHTML =
+ sDisplay;
+ }
+ }
+ }
+
+ /* Perform pre-draw actions */
+ if ( typeof bAction == 'undefined' || bAction )
+ {
+ _fnBuildSearchArray( oSettings, 1 );
+ _fnAjustColumnSizing( oSettings );
+ }
+
+ /* Redraw the table */
+ if ( typeof bRedraw == 'undefined' || bRedraw )
+ {
+ _fnReDraw( oSettings );
+ }
+ return 0;
+ };
+
+
+ /*
+ * Function: fnShowColoumn
+ * Purpose: Show a particular column
+ * Returns: -
+ * Inputs: int:iCol - the column whose display should be changed
+ * bool:bShow - show (true) or hide (false) the column
+ */
+ this.fnSetColumnVis = function ( iCol, bShow )
+ {
+ var oSettings = _fnSettingsFromNode( this[_oExt.iApiIndex] );
+ var i, iLen;
+ var iColumns = oSettings.aoColumns.length;
+ var nTd, anTds;
+
+ /* No point in doing anything if we are requesting what is already true */
+ if ( oSettings.aoColumns[iCol].bVisible == bShow )
+ {
+ return;
+ }
+
+ var nTrHead = $('>tr', oSettings.nTHead)[0];
+ var nTrFoot = $('>tr', oSettings.nTFoot)[0];
+ var anTheadTh = [];
+ var anTfootTh = [];
+ for ( i=0 ; i<iColumns ; i++ )
+ {
+ anTheadTh.push( oSettings.aoColumns[i].nTh );
+ anTfootTh.push( oSettings.aoColumns[i].nTf );
+ }
+
+ /* Show the column */
+ if ( bShow )
+ {
+ var iInsert = 0;
+ for ( i=0 ; i<iCol ; i++ )
+ {
+ if ( oSettings.aoColumns[i].bVisible )
+ {
+ iInsert++;
+ }
+ }
+
+ /* Need to decide if we should use appendChild or insertBefore */
+ if ( iInsert >= _fnVisbleColumns( oSettings ) )
+ {
+ nTrHead.appendChild( anTheadTh[iCol] );
+ if ( nTrFoot )
+ {
+ nTrFoot.appendChild( anTfootTh[iCol] );
+ }
+
+ for ( i=0, iLen=oSettings.aoData.length ; i<iLen ; i++ )
+ {
+ nTd = oSettings.aoData[i]._anHidden[iCol];
+ oSettings.aoData[i].nTr.appendChild( nTd );
+ }
+ }
+ else
+ {
+ /* Which coloumn should we be inserting before? */
+ var iBefore;
+ for ( i=iCol ; i<iColumns ; i++ )
+ {
+ iBefore = _fnColumnIndexToVisible( oSettings, i );
+ if ( iBefore !== null )
+ {
+ break;
+ }
+ }
+
+ nTrHead.insertBefore( anTheadTh[iCol], nTrHead.getElementsByTagName('th')[iBefore] );
+ if ( nTrFoot )
+ {
+ nTrFoot.insertBefore( anTfootTh[iCol], nTrFoot.getElementsByTagName('th')[iBefore] );
+ }
+
+ anTds = _fnGetTdNodes( oSettings );
+ for ( i=0, iLen=oSettings.aoData.length ; i<iLen ; i++ )
+ {
+ nTd = oSettings.aoData[i]._anHidden[iCol];
+ oSettings.aoData[i].nTr.insertBefore( nTd, $('>td:eq('+iBefore+')',
+ oSettings.aoData[i].nTr)[0] );
+ }
+ }
+
+ oSettings.aoColumns[iCol].bVisible = true;
+ }
+ else
+ {
+ /* Remove a column from display */
+ nTrHead.removeChild( anTheadTh[iCol] );
+ if ( nTrFoot )
+ {
+ nTrFoot.removeChild( anTfootTh[iCol] );
+ }
+
+ anTds = _fnGetTdNodes( oSettings );
+ for ( i=0, iLen=oSettings.aoData.length ; i<iLen ; i++ )
+ {
+ nTd = anTds[ ( i*oSettings.aoColumns.length) + (iCol*1) ];
+ oSettings.aoData[i]._anHidden[iCol] = nTd;
+ nTd.parentNode.removeChild( nTd );
+ }
+
+ oSettings.aoColumns[iCol].bVisible = false;
+ }
+
+ /* If there are any 'open' rows, then we need to alter the colspan for this col change */
+ for ( i=0, iLen=oSettings.aoOpenRows.length ; i<iLen ; i++ )
+ {
+ oSettings.aoOpenRows[i].nTr.colSpan = _fnVisbleColumns( oSettings );
+ }
+
+ /* Do a redraw incase anything depending on the table columns needs it
+ * (built-in: scrolling)
+ */
+ _fnAjustColumnSizing( oSettings );
+ _fnDraw( oSettings );
+ _fnSaveState( oSettings );
+ };
+
+ /*
+ * Function: fnPageChange
+ * Purpose: Change the pagination
+ * Returns: -
+ * Inputs: string:sAction - paging action to take: "first", "previous", "next" or "last"
+ * bool:bRedraw - redraw the table or not - optional - default true
+ */
+ this.fnPageChange = function ( sAction, bRedraw )
+ {
+ var oSettings = _fnSettingsFromNode( this[_oExt.iApiIndex] );
+ _fnPageChange( oSettings, sAction );
+ _fnCalculateEnd( oSettings );
+
+ if ( typeof bRedraw == 'undefined' || bRedraw )
+ {
+ _fnDraw( oSettings );
+ }
+ };
+
+ /*
+ * Function: fnDestroy
+ * Purpose: Destructor for the DataTable
+ * Returns: -
+ * Inputs: -
+ */
+ this.fnDestroy = function ( )
+ {
+ var oSettings = _fnSettingsFromNode( this[_oExt.iApiIndex] );
+ var nOrig = oSettings.nTableWrapper.parentNode;
+ var nBody = oSettings.nTBody;
+ var i, iLen;
+
+ /* Flag to note that the table is currently being destoryed - no action should be taken */
+ oSettings.bDestroying = true;
+
+ /* Restore hidden columns */
+ for ( i=0, iLen=oSettings.aoColumns.length ; i<iLen ; i++ )
+ {
+ if ( oSettings.aoColumns[i].bVisible === false )
+ {
+ this.fnSetColumnVis( i, true );
+ }
+ }
+
+ /* If there is an 'empty' indicator row, remove it */
+ $('tbody>tr>td.'+oSettings.oClasses.sRowEmpty, oSettings.nTable).parent().remove();
+
+ /* When scrolling we had to break the table up - restore it */
+ if ( oSettings.nTable != oSettings.nTHead.parentNode )
+ {
+ $('>thead', oSettings.nTable).remove();
+ oSettings.nTable.appendChild( oSettings.nTHead );
+ }
+
+ if ( oSettings.nTFoot && oSettings.nTable != oSettings.nTFoot.parentNode )
+ {
+ $('>tfoot', oSettings.nTable).remove();
+ oSettings.nTable.appendChild( oSettings.nTFoot );
+ }
+
+ /* Remove the DataTables generated nodes, events and classes */
+ oSettings.nTable.parentNode.removeChild( oSettings.nTable );
+ $(oSettings.nTableWrapper).remove();
+
+ oSettings.aaSorting = [];
+ oSettings.aaSortingFixed = [];
+ _fnSortingClasses( oSettings );
+
+ $(_fnGetTrNodes( oSettings )).removeClass( oSettings.asStripClasses.join(' ') );
+
+ if ( !oSettings.bJUI )
+ {
+ $('th', oSettings.nTHead).removeClass( [ _oExt.oStdClasses.sSortable,
+ _oExt.oStdClasses.sSortableAsc,
+ _oExt.oStdClasses.sSortableDesc,
+ _oExt.oStdClasses.sSortableNone ].join(' ')
+ );
+ }
+ else
+ {
+ $('th', oSettings.nTHead).removeClass( [ _oExt.oStdClasses.sSortable,
+ _oExt.oJUIClasses.sSortableAsc,
+ _oExt.oJUIClasses.sSortableDesc,
+ _oExt.oJUIClasses.sSortableNone ].join(' ')
+ );
+ $('th span', oSettings.nTHead).remove();
+ }
+
+ /* Add the TR elements back into the table in their original order */
+ nOrig.appendChild( oSettings.nTable );
+ for ( i=0, iLen=oSettings.aoData.length ; i<iLen ; i++ )
+ {
+ nBody.appendChild( oSettings.aoData[i].nTr );
+ }
+
+ /* Restore the width of the original table */
+ oSettings.nTable.style.width = _fnStringToCss(oSettings.sDestroyWidth);
+
+ /* If the were originally odd/even type classes - then we add them back here. Note
+ * this is not fool proof (for example if not all rows as odd/even classes - but
+ * it's a good effort without getting carried away
+ */
+ $('>tr:even', nBody).addClass( oSettings.asDestoryStrips[0] );
+ $('>tr:odd', nBody).addClass( oSettings.asDestoryStrips[1] );
+
+ /* Remove the settings object from the settings array */
+ for ( i=0, iLen=_aoSettings.length ; i<iLen ; i++ )
+ {
+ if ( _aoSettings[i] == oSettings )
+ {
+ _aoSettings.splice( i, 1 );
+ }
+ }
+
+ /* End it all */
+ oSettings = null;
+ };
+
+ /*
+ * Function: _fnAjustColumnSizing
+ * Purpose: Update tale sizing based on content. This would most likely be used for scrolling
+ * and will typically need a redraw after it.
+ * Returns: -
+ * Inputs: bool:bRedraw - redraw the table or not, you will typically want to - default true
+ */
+ this.fnAdjustColumnSizing = function ( bRedraw )
+ {
+ _fnAjustColumnSizing( _fnSettingsFromNode( this[_oExt.iApiIndex] ) );
+
+ if ( typeof bRedraw == 'undefined' || bRedraw )
+ {
+ this.fnDraw( false );
+ }
+ };
+
+ /*
+ * Plugin API functions
+ *
+ * This call will add the functions which are defined in _oExt.oApi to the
+ * DataTables object, providing a rather nice way to allow plug-in API functions. Note that
+ * this is done here, so that API function can actually override the built in API functions if
+ * required for a particular purpose.
+ */
+
+ /*
+ * Function: _fnExternApiFunc
+ * Purpose: Create a wrapper function for exporting an internal func to an external API func
+ * Returns: function: - wrapped function
+ * Inputs: string:sFunc - API function name
+ */
+ function _fnExternApiFunc (sFunc)
+ {
+ return function() {
+ var aArgs = [_fnSettingsFromNode(this[_oExt.iApiIndex])].concat(
+ Array.prototype.slice.call(arguments) );
+ return _oExt.oApi[sFunc].apply( this, aArgs );
+ };
+ }
+
+ for ( var sFunc in _oExt.oApi )
+ {
+ if ( sFunc )
+ {
+ /*
+ * Function: anon
+ * Purpose: Wrap the plug-in API functions in order to provide the settings as 1st arg
+ * and execute in this scope
+ * Returns: -
+ * Inputs: -
+ */
+ this[sFunc] = _fnExternApiFunc(sFunc);
+ }
+ }
+
+
+
+ /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+ * Section - Local functions
+ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
+
+ /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+ * Section - Initalisation
+ */
+
+ /*
+ * Function: _fnInitalise
+ * Purpose: Draw the table for the first time, adding all required features
+ * Returns: -
+ * Inputs: object:oSettings - dataTables settings object
+ */
+ function _fnInitalise ( oSettings )
+ {
+ var i, iLen;
+
+ /* Ensure that the table data is fully initialised */
+ if ( oSettings.bInitialised === false )
+ {
+ setTimeout( function(){ _fnInitalise( oSettings ); }, 200 );
+ return;
+ }
+
+ /* Show the display HTML options */
+ _fnAddOptionsHtml( oSettings );
+
+ /* Draw the headers for the table */
+ _fnDrawHead( oSettings );
+
+ /* Calculate sizes for columns */
+ if ( oSettings.oFeatures.bAutoWidth )
+ {
+ _fnCalculateColumnWidths( oSettings );
+ }
+
+ for ( i=0, iLen=oSettings.aoColumns.length ; i<iLen ; i++ )
+ {
+ if ( oSettings.aoColumns[i].sWidth !== null )
+ {
+ oSettings.aoColumns[i].nTh.style.width = _fnStringToCss( oSettings.aoColumns[i].sWidth );
+ }
+ }
+
+ /* If there is default sorting required - let's do it. The sort function
+ * will do the drawing for us. Otherwise we draw the table
+ */
+ if ( oSettings.oFeatures.bSort )
+ {
+ _fnSort( oSettings );
+ }
+ else
+ {
+ oSettings.aiDisplay = oSettings.aiDisplayMaster.slice();
+ _fnCalculateEnd( oSettings );
+ _fnDraw( oSettings );
+ }
+
+ /* if there is an ajax source */
+ if ( oSettings.sAjaxSource !== null && !oSettings.oFeatures.bServerSide )
+ {
+ _fnProcessingDisplay( oSettings, true );
+
+ oSettings.fnServerData.call( oSettings.oInstance, oSettings.sAjaxSource, [], function(json) {
+ /* Got the data - add it to the table */
+ for ( i=0 ; i<json.aaData.length ; i++ )
+ {
+ _fnAddData( oSettings, json.aaData[i] );
+ }
+
+ /* Reset the init display for cookie saving. We've already done a filter, and
+ * therefore cleared it before. So we need to make it appear 'fresh'
+ */
+ oSettings.iInitDisplayStart = oSettings._iDisplayStart;
+
+ if ( oSettings.oFeatures.bSort )
+ {
+ _fnSort( oSettings );
+ }
+ else
+ {
+ oSettings.aiDisplay = oSettings.aiDisplayMaster.slice();
+ _fnCalculateEnd( oSettings );
+ _fnDraw( oSettings );
+ }
+
+ _fnProcessingDisplay( oSettings, false );
+
+ /* Run the init callback if there is one - done here for ajax source for json obj */
+ if ( typeof oSettings.fnInitComplete == 'function' )
+ {
+ oSettings.fnInitComplete.call( oSettings.oInstance, oSettings, json );
+ }
+ } );
+ return;
+ }
+
+ if ( !oSettings.oFeatures.bServerSide )
+ {
+ _fnProcessingDisplay( oSettings, false );
+ }
+ }
+
+ /*
+ * Function: _fnLanguageProcess
+ * Purpose: Copy language variables from remote object to a local one
+ * Returns: -
+ * Inputs: object:oSettings - dataTables settings object
+ * object:oLanguage - Language information
+ * bool:bInit - init once complete
+ */
+ function _fnLanguageProcess( oSettings, oLanguage, bInit )
+ {
+ _fnMap( oSettings.oLanguage, oLanguage, 'sProcessing' );
+ _fnMap( oSettings.oLanguage, oLanguage, 'sLengthMenu' );
+ _fnMap( oSettings.oLanguage, oLanguage, 'sEmptyTable' );
+ _fnMap( oSettings.oLanguage, oLanguage, 'sZeroRecords' );
+ _fnMap( oSettings.oLanguage, oLanguage, 'sInfo' );
+ _fnMap( oSettings.oLanguage, oLanguage, 'sInfoEmpty' );
+ _fnMap( oSettings.oLanguage, oLanguage, 'sInfoFiltered' );
+ _fnMap( oSettings.oLanguage, oLanguage, 'sInfoPostFix' );
+ _fnMap( oSettings.oLanguage, oLanguage, 'sSearch' );
+
+ if ( typeof oLanguage.oPaginate != 'undefined' )
+ {
+ _fnMap( oSettings.oLanguage.oPaginate, oLanguage.oPaginate, 'sFirst' );
+ _fnMap( oSettings.oLanguage.oPaginate, oLanguage.oPaginate, 'sPrevious' );
+ _fnMap( oSettings.oLanguage.oPaginate, oLanguage.oPaginate, 'sNext' );
+ _fnMap( oSettings.oLanguage.oPaginate, oLanguage.oPaginate, 'sLast' );
+ }
+
+ /* Backwards compatibility - if there is no sEmptyTable given, then use the same as
+ * sZeroRecords - assuming that is given.
+ */
+ if ( typeof oLanguage.sEmptyTable == 'undefined' &&
+ typeof oLanguage.sZeroRecords != 'undefined' )
+ {
+ _fnMap( oSettings.oLanguage, oLanguage, 'sZeroRecords', 'sEmptyTable' );
+ }
+
+ if ( bInit )
+ {
+ _fnInitalise( oSettings );
+ }
+ }
+
+ /*
+ * Function: _fnAddColumn
+ * Purpose: Add a column to the list used for the table with default values
+ * Returns: -
+ * Inputs: object:oSettings - dataTables settings object
+ * node:nTh - the th element for this column
+ */
+ function _fnAddColumn( oSettings, nTh )
+ {
+ oSettings.aoColumns[ oSettings.aoColumns.length++ ] = {
+ "sType": null,
+ "_bAutoType": true,
+ "bVisible": true,
+ "bSearchable": true,
+ "bSortable": true,
+ "asSorting": [ 'asc', 'desc' ],
+ "sSortingClass": oSettings.oClasses.sSortable,
+ "sSortingClassJUI": oSettings.oClasses.sSortJUI,
+ "sTitle": nTh ? nTh.innerHTML : '',
+ "sName": '',
+ "sWidth": null,
+ "sWidthOrig": null,
+ "sClass": null,
+ "fnRender": null,
+ "bUseRendered": true,
+ "iDataSort": oSettings.aoColumns.length-1,
+ "sSortDataType": 'std',
+ "nTh": nTh ? nTh : document.createElement('th'),
+ "nTf": null
+ };
+
+ var iCol = oSettings.aoColumns.length-1;
+ var oCol = oSettings.aoColumns[ iCol ];
+
+ /* Add a column specific filter */
+ if ( typeof oSettings.aoPreSearchCols[ iCol ] == 'undefined' ||
+ oSettings.aoPreSearchCols[ iCol ] === null )
+ {
+ oSettings.aoPreSearchCols[ iCol ] = {
+ "sSearch": "",
+ "bRegex": false,
+ "bSmart": true
+ };
+ }
+ else
+ {
+ /* Don't require that the user must specify bRegex and / or bSmart */
+ if ( typeof oSettings.aoPreSearchCols[ iCol ].bRegex == 'undefined' )
+ {
+ oSettings.aoPreSearchCols[ iCol ].bRegex = true;
+ }
+
+ if ( typeof oSettings.aoPreSearchCols[ iCol ].bSmart == 'undefined' )
+ {
+ oSettings.aoPreSearchCols[ iCol ].bSmart = true;
+ }
+ }
+
+ /* Use the column options function to initialise classes etc */
+ _fnColumnOptions( oSettings, iCol, null );
+ }
+
+ /*
+ * Function: _fnColumnOptions
+ * Purpose: Apply options for a column
+ * Returns: -
+ * Inputs: object:oSettings - dataTables settings object
+ * int:iCol - column index to consider
+ * object:oOptions - object with sType, bVisible and bSearchable
+ */
+ function _fnColumnOptions( oSettings, iCol, oOptions )
+ {
+ var oCol = oSettings.aoColumns[ iCol ];
+
+ /* User specified column options */
+ if ( typeof oOptions != 'undefined' && oOptions !== null )
+ {
+ if ( typeof oOptions.sType != 'undefined' )
+ {
+ oCol.sType = oOptions.sType;
+ oCol._bAutoType = false;
+ }
+
+ _fnMap( oCol, oOptions, "bVisible" );
+ _fnMap( oCol, oOptions, "bSearchable" );
+ _fnMap( oCol, oOptions, "bSortable" );
+ _fnMap( oCol, oOptions, "sTitle" );
+ _fnMap( oCol, oOptions, "sName" );
+ _fnMap( oCol, oOptions, "sWidth" );
+ _fnMap( oCol, oOptions, "sWidth", "sWidthOrig" );
+ _fnMap( oCol, oOptions, "sClass" );
+ _fnMap( oCol, oOptions, "fnRender" );
+ _fnMap( oCol, oOptions, "bUseRendered" );
+ _fnMap( oCol, oOptions, "iDataSort" );
+ _fnMap( oCol, oOptions, "asSorting" );
+ _fnMap( oCol, oOptions, "sSortDataType" );
+ }
+
+ /* Feature sorting overrides column specific when off */
+ if ( !oSettings.oFeatures.bSort )
+ {
+ oCol.bSortable = false;
+ }
+
+ /* Check that the class assignment is correct for sorting */
+ if ( !oCol.bSortable ||
+ ($.inArray('asc', oCol.asSorting) == -1 && $.inArray('desc', oCol.asSorting) == -1) )
+ {
+ oCol.sSortingClass = oSettings.oClasses.sSortableNone;
+ oCol.sSortingClassJUI = "";
+ }
+ else if ( $.inArray('asc', oCol.asSorting) != -1 && $.inArray('desc', oCol.asSorting) == -1 )
+ {
+ oCol.sSortingClass = oSettings.oClasses.sSortableAsc;
+ oCol.sSortingClassJUI = oSettings.oClasses.sSortJUIAscAllowed;
+ }
+ else if ( $.inArray('asc', oCol.asSorting) == -1 && $.inArray('desc', oCol.asSorting) != -1 )
+ {
+ oCol.sSortingClass = oSettings.oClasses.sSortableDesc;
+ oCol.sSortingClassJUI = oSettings.oClasses.sSortJUIDescAllowed;
+ }
+ }
+
+ /*
+ * Function: _fnAddData
+ * Purpose: Add a data array to the table, creating DOM node etc
+ * Returns: int: - >=0 if successful (index of new aoData entry), -1 if failed
+ * Inputs: object:oSettings - dataTables settings object
+ * array:aData - data array to be added
+ * Notes: There are two basic methods for DataTables to get data to display - a JS array
+ * (which is dealt with by this function), and the DOM, which has it's own optimised
+ * function (_fnGatherData). Be careful to make the same changes here as there and vice-versa
+ */
+ function _fnAddData ( oSettings, aDataSupplied )
+ {
+ /* Sanity check the length of the new array */
+ if ( aDataSupplied.length != oSettings.aoColumns.length &&
+ oSettings.iDrawError != oSettings.iDraw )
+ {
+ _fnLog( oSettings, 0, "Added data (size "+aDataSupplied.length+") does not match known "+
+ "number of columns ("+oSettings.aoColumns.length+")" );
+ oSettings.iDrawError = oSettings.iDraw;
+ return -1;
+ }
+
+
+ /* Create the object for storing information about this new row */
+ var aData = aDataSupplied.slice();
+ var iThisIndex = oSettings.aoData.length;
+ oSettings.aoData.push( {
+ "nTr": document.createElement('tr'),
+ "_iId": oSettings.iNextId++,
+ "_aData": aData,
+ "_anHidden": [],
+ "_sRowStripe": ''
+ } );
+
+ /* Create the cells */
+ var nTd, sThisType;
+ for ( var i=0 ; i<aData.length ; i++ )
+ {
+ nTd = document.createElement('td');
+
+ /* Allow null data (from a data array) - simply deal with it as a blank string */
+ if ( aData[i] === null )
+ {
+ aData[i] = '';
+ }
+
+ if ( typeof oSettings.aoColumns[i].fnRender == 'function' )
+ {
+ var sRendered = oSettings.aoColumns[i].fnRender( {
+ "iDataRow": iThisIndex,
+ "iDataColumn": i,
+ "aData": aData,
+ "oSettings": oSettings
+ } );
+ nTd.innerHTML = sRendered;
+ if ( oSettings.aoColumns[i].bUseRendered )
+ {
+ /* Use the rendered data for filtering/sorting */
+ oSettings.aoData[iThisIndex]._aData[i] = sRendered;
+ }
+ }
+ else
+ {
+ nTd.innerHTML = aData[i];
+ }
+
+ /* Cast everything as a string - so we can treat everything equally when sorting */
+ if ( typeof aData[i] != 'string' )
+ {
+ aData[i] += "";
+ }
+ aData[i] = $.trim(aData[i]);
+
+ /* Add user defined class */
+ if ( oSettings.aoColumns[i].sClass !== null )
+ {
+ nTd.className = oSettings.aoColumns[i].sClass;
+ }
+
+ /* See if we should auto-detect the column type */
+ if ( oSettings.aoColumns[i]._bAutoType && oSettings.aoColumns[i].sType != 'string' )
+ {
+ /* Attempt to auto detect the type - same as _fnGatherData() */
+ sThisType = _fnDetectType( oSettings.aoData[iThisIndex]._aData[i] );
+ if ( oSettings.aoColumns[i].sType === null )
+ {
+ oSettings.aoColumns[i].sType = sThisType;
+ }
+ else if ( oSettings.aoColumns[i].sType != sThisType )
+ {
+ /* String is always the 'fallback' option */
+ oSettings.aoColumns[i].sType = 'string';
+ }
+ }
+
+ if ( oSettings.aoColumns[i].bVisible )
+ {
+ oSettings.aoData[iThisIndex].nTr.appendChild( nTd );
+ }
+ else
+ {
+ oSettings.aoData[iThisIndex]._anHidden[i] = nTd;
+ }
+ }
+
+ /* Add to the display array */
+ oSettings.aiDisplayMaster.push( iThisIndex );
+ return iThisIndex;
+ }
+
+ /*
+ * Function: _fnGatherData
+ * Purpose: Read in the data from the target table from the DOM
+ * Returns: -
+ * Inputs: object:oSettings - dataTables settings object
+ * Notes: This is a optimised version of _fnAddData (more or less) for reading information
+ * from the DOM. The basic actions must be identical in the two functions.
+ */
+ function _fnGatherData( oSettings )
+ {
+ var iLoop, i, iLen, j, jLen, jInner,
+ nTds, nTrs, nTd, aLocalData, iThisIndex,
+ iRow, iRows, iColumn, iColumns;
+
+ /*
+ * Process by row first
+ * Add the data object for the whole table - storing the tr node. Note - no point in getting
+ * DOM based data if we are going to go and replace it with Ajax source data.
+ */
+ if ( oSettings.sAjaxSource === null )
+ {
+ nTrs = oSettings.nTBody.childNodes;
+ for ( i=0, iLen=nTrs.length ; i<iLen ; i++ )
+ {
+ if ( nTrs[i].nodeName.toUpperCase() == "TR" )
+ {
+ iThisIndex = oSettings.aoData.length;
+ oSettings.aoData.push( {
+ "nTr": nTrs[i],
+ "_iId": oSettings.iNextId++,
+ "_aData": [],
+ "_anHidden": [],
+ "_sRowStripe": ''
+ } );
+
+ oSettings.aiDisplayMaster.push( iThisIndex );
+
+ aLocalData = oSettings.aoData[iThisIndex]._aData;
+ nTds = nTrs[i].childNodes;
+ jInner = 0;
+
+ for ( j=0, jLen=nTds.length ; j<jLen ; j++ )
+ {
+ if ( nTds[j].nodeName.toUpperCase() == "TD" )
+ {
+ aLocalData[jInner] = $.trim(nTds[j].innerHTML);
+ jInner++;
+ }
+ }
+ }
+ }
+ }
+
+ /* Gather in the TD elements of the Table - note that this is basically the same as
+ * fnGetTdNodes, but that function takes account of hidden columns, which we haven't yet
+ * setup!
+ */
+ nTrs = _fnGetTrNodes( oSettings );
+ nTds = [];
+ for ( i=0, iLen=nTrs.length ; i<iLen ; i++ )
+ {
+ for ( j=0, jLen=nTrs[i].childNodes.length ; j<jLen ; j++ )
+ {
+ nTd = nTrs[i].childNodes[j];
+ if ( nTd.nodeName.toUpperCase() == "TD" )
+ {
+ nTds.push( nTd );
+ }
+ }
+ }
+
+ /* Sanity check */
+ if ( nTds.length != nTrs.length * oSettings.aoColumns.length )
+ {
+ _fnLog( oSettings, 1, "Unexpected number of TD elements. Expected "+
+ (nTrs.length * oSettings.aoColumns.length)+" and got "+nTds.length+". DataTables does "+
+ "not support rowspan / colspan in the table body, and there must be one cell for each "+
+ "row/column combination." );
+ }
+
+ /* Now process by column */
+ for ( iColumn=0, iColumns=oSettings.aoColumns.length ; iColumn<iColumns ; iColumn++ )
+ {
+ /* Get the title of the column - unless there is a user set one */
+ if ( oSettings.aoColumns[iColumn].sTitle === null )
+ {
+ oSettings.aoColumns[iColumn].sTitle = oSettings.aoColumns[iColumn].nTh.innerHTML;
+ }
+
+ var
+ bAutoType = oSettings.aoColumns[iColumn]._bAutoType,
+ bRender = typeof oSettings.aoColumns[iColumn].fnRender == 'function',
+ bClass = oSettings.aoColumns[iColumn].sClass !== null,
+ bVisible = oSettings.aoColumns[iColumn].bVisible,
+ nCell, sThisType, sRendered;
+
+ /* A single loop to rule them all (and be more efficient) */
+ if ( bAutoType || bRender || bClass || !bVisible )
+ {
+ for ( iRow=0, iRows=oSettings.aoData.length ; iRow<iRows ; iRow++ )
+ {
+ nCell = nTds[ (iRow*iColumns) + iColumn ];
+
+ /* Type detection */
+ if ( bAutoType )
+ {
+ if ( oSettings.aoColumns[iColumn].sType != 'string' )
+ {
+ sThisType = _fnDetectType( oSettings.aoData[iRow]._aData[iColumn] );
+ if ( oSettings.aoColumns[iColumn].sType === null )
+ {
+ oSettings.aoColumns[iColumn].sType = sThisType;
+ }
+ else if ( oSettings.aoColumns[iColumn].sType != sThisType )
+ {
+ /* String is always the 'fallback' option */
+ oSettings.aoColumns[iColumn].sType = 'string';
+ }
+ }
+ }
+
+ /* Rendering */
+ if ( bRender )
+ {
+ sRendered = oSettings.aoColumns[iColumn].fnRender( {
+ "iDataRow": iRow,
+ "iDataColumn": iColumn,
+ "aData": oSettings.aoData[iRow]._aData,
+ "oSettings": oSettings
+ } );
+ nCell.innerHTML = sRendered;
+ if ( oSettings.aoColumns[iColumn].bUseRendered )
+ {
+ /* Use the rendered data for filtering/sorting */
+ oSettings.aoData[iRow]._aData[iColumn] = sRendered;
+ }
+ }
+
+ /* Classes */
+ if ( bClass )
+ {
+ nCell.className += ' '+oSettings.aoColumns[iColumn].sClass;
+ }
+
+ /* Column visability */
+ if ( !bVisible )
+ {
+ oSettings.aoData[iRow]._anHidden[iColumn] = nCell;
+ nCell.parentNode.removeChild( nCell );
+ }
+ }
+ }
+ }
+ }
+
+
+
+ /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+ * Section - Drawing functions
+ */
+
+ /*
+ * Function: _fnDrawHead
+ * Purpose: Create the HTML header for the table
+ * Returns: -
+ * Inputs: object:oSettings - dataTables settings object
+ */
+ function _fnDrawHead( oSettings )
+ {
+ var i, nTh, iLen, j, jLen;
+ var iThs = oSettings.nTHead.getElementsByTagName('th').length;
+ var iCorrector = 0;
+
+ /* If there is a header in place - then use it - otherwise it's going to get nuked... */
+ if ( iThs !== 0 )
+ {
+ /* We've got a thead from the DOM, so remove hidden columns and apply width to vis cols */
+ for ( i=0, iLen=oSettings.aoColumns.length ; i<iLen ; i++ )
+ {
+ nTh = oSettings.aoColumns[i].nTh;
+
+ if ( oSettings.aoColumns[i].bVisible )
+ {
+ /* Set the title of the column if it is user defined (not what was auto detected) */
+ if ( oSettings.aoColumns[i].sTitle != nTh.innerHTML )
+ {
+ nTh.innerHTML = oSettings.aoColumns[i].sTitle;
+ }
+ }
+ else
+ {
+ nTh.parentNode.removeChild( nTh );
+ iCorrector++;
+ }
+ }
+ }
+ else
+ {
+ /* We don't have a header in the DOM - so we are going to have to create one */
+ var nTr = document.createElement( "tr" );
+
+ for ( i=0, iLen=oSettings.aoColumns.length ; i<iLen ; i++ )
+ {
+ nTh = oSettings.aoColumns[i].nTh;
+ nTh.innerHTML = oSettings.aoColumns[i].sTitle;
+
+ if ( oSettings.aoColumns[i].bVisible )
+ {
+ if ( oSettings.aoColumns[i].sClass !== null )
+ {
+ nTh.className = oSettings.aoColumns[i].sClass;
+ }
+
+ nTr.appendChild( nTh );
+ }
+ }
+ $(oSettings.nTHead).html( '' )[0].appendChild( nTr );
+ }
+
+ /* Add the extra markup needed by jQuery UI's themes */
+ if ( oSettings.bJUI )
+ {
+ for ( i=0, iLen=oSettings.aoColumns.length ; i<iLen ; i++ )
+ {
+ nTh = oSettings.aoColumns[i].nTh;
+
+ var nDiv = document.createElement('div');
+ nDiv.className = oSettings.oClasses.sSortJUIWrapper;
+ $(nTh).contents().appendTo(nDiv);
+
+ nDiv.appendChild( document.createElement('span') );
+ nTh.appendChild( nDiv );
+ }
+ }
+
+ /* Add sort listener */
+ var fnNoSelect = function (e) {
+ this.onselectstart = function() { return false; };
+ return false;
+ };
+
+ if ( oSettings.oFeatures.bSort )
+ {
+ for ( i=0 ; i<oSettings.aoColumns.length ; i++ )
+ {
+ if ( oSettings.aoColumns[i].bSortable !== false )
+ {
+ _fnSortAttachListener( oSettings, oSettings.aoColumns[i].nTh, i );
+
+ /* Take the brutal approach to cancelling text selection in header */
+ $(oSettings.aoColumns[i].nTh).mousedown( fnNoSelect );
+ }
+ else
+ {
+ $(oSettings.aoColumns[i].nTh).addClass( oSettings.oClasses.sSortableNone );
+ }
+ }
+ }
+
+ /* Cache the footer elements */
+ if ( oSettings.nTFoot !== null )
+ {
+ iCorrector = 0;
+ var nTfs = oSettings.nTFoot.getElementsByTagName('th');
+ for ( i=0, iLen=nTfs.length ; i<iLen ; i++ )
+ {
+ if ( typeof oSettings.aoColumns[i] != 'undefined' )
+ {
+ oSettings.aoColumns[i].nTf = nTfs[i-iCorrector];
+
+ if ( oSettings.oClasses.sFooterTH !== "" )
+ {
+ oSettings.aoColumns[i].nTf.className += " "+oSettings.oClasses.sFooterTH;
+ }
+
+ if ( !oSettings.aoColumns[i].bVisible )
+ {
+ nTfs[i-iCorrector].parentNode.removeChild( nTfs[i-iCorrector] );
+ iCorrector++;
+ }
+ }
+ }
+ }
+ }
+
+ /*
+ * Function: _fnDraw
+ * Purpose: Insert the required TR nodes into the table for display
+ * Returns: -
+ * Inputs: object:oSettings - dataTables settings object
+ */
+ function _fnDraw( oSettings )
+ {
+ var i, iLen;
+ var anRows = [];
+ var iRowCount = 0;
+ var bRowError = false;
+ var iStrips = oSettings.asStripClasses.length;
+ var iOpenRows = oSettings.aoOpenRows.length;
+
+ oSettings.bDrawing = true;
+
+ /* Check and see if we have an initial draw position from state saving */
+ if ( typeof oSettings.iInitDisplayStart != 'undefined' && oSettings.iInitDisplayStart != -1 )
+ {
+ if ( oSettings.oFeatures.bServerSide )
+ {
+ oSettings._iDisplayStart = oSettings.iInitDisplayStart;
+ }
+ else
+ {
+ oSettings._iDisplayStart = (oSettings.iInitDisplayStart >= oSettings.fnRecordsDisplay()) ?
+ 0 : oSettings.iInitDisplayStart;
+ }
+ oSettings.iInitDisplayStart = -1;
+ _fnCalculateEnd( oSettings );
+ }
+
+ /* If we are dealing with Ajax - do it here */
+ if ( oSettings.oFeatures.bServerSide &&
+ !_fnAjaxUpdate( oSettings ) )
+ {
+ return;
+ }
+ else if ( !oSettings.oFeatures.bServerSide )
+ {
+ oSettings.iDraw++;
+ }
+
+ if ( oSettings.aiDisplay.length !== 0 )
+ {
+ var iStart = oSettings._iDisplayStart;
+ var iEnd = oSettings._iDisplayEnd;
+
+ if ( oSettings.oFeatures.bServerSide )
+ {
+ iStart = 0;
+ iEnd = oSettings.aoData.length;
+ }
+
+ for ( var j=iStart ; j<iEnd ; j++ )
+ {
+ var aoData = oSettings.aoData[ oSettings.aiDisplay[j] ];
+ var nRow = aoData.nTr;
+
+ /* Remove the old stripping classes and then add the new one */
+ if ( iStrips !== 0 )
+ {
+ var sStrip = oSettings.asStripClasses[ iRowCount % iStrips ];
+ if ( aoData._sRowStripe != sStrip )
+ {
+ $(nRow).removeClass( aoData._sRowStripe ).addClass( sStrip );
+ aoData._sRowStripe = sStrip;
+ }
+ }
+
+ /* Custom row callback function - might want to manipule the row */
+ if ( typeof oSettings.fnRowCallback == "function" )
+ {
+ nRow = oSettings.fnRowCallback.call( oSettings.oInstance, nRow,
+ oSettings.aoData[ oSettings.aiDisplay[j] ]._aData, iRowCount, j );
+ if ( !nRow && !bRowError )
+ {
+ _fnLog( oSettings, 0, "A node was not returned by fnRowCallback" );
+ bRowError = true;
+ }
+ }
+
+ anRows.push( nRow );
+ iRowCount++;
+
+ /* If there is an open row - and it is attached to this parent - attach it on redraw */
+ if ( iOpenRows !== 0 )
+ {
+ for ( var k=0 ; k<iOpenRows ; k++ )
+ {
+ if ( nRow == oSettings.aoOpenRows[k].nParent )
+ {
+ anRows.push( oSettings.aoOpenRows[k].nTr );
+ }
+ }
+ }
+ }
+ }
+ else
+ {
+ /* Table is empty - create a row with an empty message in it */
+ anRows[ 0 ] = document.createElement( 'tr' );
+
+ if ( typeof oSettings.asStripClasses[0] != 'undefined' )
+ {
+ anRows[ 0 ].className = oSettings.asStripClasses[0];
+ }
+
+ var nTd = document.createElement( 'td' );
+ nTd.setAttribute( 'valign', "top" );
+ nTd.colSpan = _fnVisbleColumns( oSettings );
+ nTd.className = oSettings.oClasses.sRowEmpty;
+ if ( typeof oSettings.oLanguage.sEmptyTable != 'undefined' &&
+ oSettings.fnRecordsTotal() === 0 )
+ {
+ nTd.innerHTML = oSettings.oLanguage.sEmptyTable;
+ }
+ else
+ {
+ nTd.innerHTML = oSettings.oLanguage.sZeroRecords.replace(
+ '_MAX_', oSettings.fnFormatNumber(oSettings.fnRecordsTotal()) );
+ }
+
+ anRows[ iRowCount ].appendChild( nTd );
+ }
+
+ /* Callback the header and footer custom funcation if there is one */
+ if ( typeof oSettings.fnHeaderCallback == 'function' )
+ {
+ oSettings.fnHeaderCallback.call( oSettings.oInstance, $('>tr', oSettings.nTHead)[0],
+ _fnGetDataMaster( oSettings ), oSettings._iDisplayStart, oSettings.fnDisplayEnd(),
+ oSettings.aiDisplay );
+ }
+
+ if ( typeof oSettings.fnFooterCallback == 'function' )
+ {
+ oSettings.fnFooterCallback.call( oSettings.oInstance, $('>tr', oSettings.nTFoot)[0],
+ _fnGetDataMaster( oSettings ), oSettings._iDisplayStart, oSettings.fnDisplayEnd(),
+ oSettings.aiDisplay );
+ }
+
+ /*
+ * Need to remove any old row from the display - note we can't just empty the tbody using
+ * $().html('') since this will unbind the jQuery event handlers (even although the node
+ * still exists!) - equally we can't use innerHTML, since IE throws an exception.
+ */
+ var
+ nAddFrag = document.createDocumentFragment(),
+ nRemoveFrag = document.createDocumentFragment(),
+ nBodyPar, nTrs;
+
+ if ( oSettings.nTBody )
+ {
+ nBodyPar = oSettings.nTBody.parentNode;
+ nRemoveFrag.appendChild( oSettings.nTBody );
+
+ /* When doing infinite scrolling, only remove child rows when sorting, filtering or start
+ * up. When not infinite scroll, always do it.
+ */
+ if ( !oSettings.oScroll.bInfinite || !oSettings._bInitComplete ||
+ oSettings.bSorted || oSettings.bFiltered )
+ {
+ nTrs = oSettings.nTBody.childNodes;
+ for ( i=nTrs.length-1 ; i>=0 ; i-- )
+ {
+ nTrs[i].parentNode.removeChild( nTrs[i] );
+ }
+ }
+
+ /* Put the draw table into the dom */
+ for ( i=0, iLen=anRows.length ; i<iLen ; i++ )
+ {
+ nAddFrag.appendChild( anRows[i] );
+ }
+
+ oSettings.nTBody.appendChild( nAddFrag );
+ if ( nBodyPar !== null )
+ {
+ nBodyPar.appendChild( oSettings.nTBody );
+ }
+ }
+
+ /* Call all required callback functions for the end of a draw */
+ for ( i=0, iLen=oSettings.aoDrawCallback.length ; i<iLen ; i++ )
+ {
+ oSettings.aoDrawCallback[i].fn.call( oSettings.oInstance, oSettings );
+ }
+
+ /* Draw is complete, sorting and filtering must be as well */
+ oSettings.bSorted = false;
+ oSettings.bFiltered = false;
+ oSettings.bDrawing = false;
+
+ /* On first draw, initilaisation is now complete */
+ if ( typeof oSettings._bInitComplete == "undefined" )
+ {
+ oSettings._bInitComplete = true;
+
+ if ( typeof oSettings.fnInitComplete == 'function' &&
+ (oSettings.oFeatures.bServerSide || oSettings.sAjaxSource === null) )
+ {
+ oSettings.fnInitComplete.call( oSettings.oInstance, oSettings );
+ }
+ }
+ }
+
+ /*
+ * Function: _fnReDraw
+ * Purpose: Redraw the table - taking account of the various features which are enabled
+ * Returns: -
+ * Inputs: object:oSettings - dataTables settings object
+ */
+ function _fnReDraw( oSettings )
+ {
+ if ( oSettings.oFeatures.bSort )
+ {
+ /* Sorting will refilter and draw for us */
+ _fnSort( oSettings, oSettings.oPreviousSearch );
+ }
+ else if ( oSettings.oFeatures.bFilter )
+ {
+ /* Filtering will redraw for us */
+ _fnFilterComplete( oSettings, oSettings.oPreviousSearch );
+ }
+ else
+ {
+ _fnCalculateEnd( oSettings );
+ _fnDraw( oSettings );
+ }
+ }
+
+ /*
+ * Function: _fnAjaxUpdate
+ * Purpose: Update the table using an Ajax call
+ * Returns: bool: block the table drawing or not
+ * Inputs: object:oSettings - dataTables settings object
+ */
+ function _fnAjaxUpdate( oSettings )
+ {
+ if ( oSettings.bAjaxDataGet )
+ {
+ _fnProcessingDisplay( oSettings, true );
+ var iColumns = oSettings.aoColumns.length;
+ var aoData = [];
+ var i;
+
+ /* Paging and general */
+ oSettings.iDraw++;
+ aoData.push( { "name": "sEcho", "value": oSettings.iDraw } );
+ aoData.push( { "name": "iColumns", "value": iColumns } );
+ aoData.push( { "name": "sColumns", "value": _fnColumnOrdering(oSettings) } );
+ aoData.push( { "name": "iDisplayStart", "value": oSettings._iDisplayStart } );
+ aoData.push( { "name": "iDisplayLength", "value": oSettings.oFeatures.bPaginate !== false ?
+ oSettings._iDisplayLength : -1 } );
+
+ /* Filtering */
+ if ( oSettings.oFeatures.bFilter !== false )
+ {
+ aoData.push( { "name": "sSearch", "value": oSettings.oPreviousSearch.sSearch } );
+ aoData.push( { "name": "bRegex", "value": oSettings.oPreviousSearch.bRegex } );
+ for ( i=0 ; i<iColumns ; i++ )
+ {
+ aoData.push( { "name": "sSearch_"+i, "value": oSettings.aoPreSearchCols[i].sSearch } );
+ aoData.push( { "name": "bRegex_"+i, "value": oSettings.aoPreSearchCols[i].bRegex } );
+ aoData.push( { "name": "bSearchable_"+i, "value": oSettings.aoColumns[i].bSearchable } );
+ }
+ }
+
+ /* Sorting */
+ if ( oSettings.oFeatures.bSort !== false )
+ {
+ var iFixed = oSettings.aaSortingFixed !== null ? oSettings.aaSortingFixed.length : 0;
+ var iUser = oSettings.aaSorting.length;
+ aoData.push( { "name": "iSortingCols", "value": iFixed+iUser } );
+ for ( i=0 ; i<iFixed ; i++ )
+ {
+ aoData.push( { "name": "iSortCol_"+i, "value": oSettings.aaSortingFixed[i][0] } );
+ aoData.push( { "name": "sSortDir_"+i, "value": oSettings.aaSortingFixed[i][1] } );
+ }
+
+ for ( i=0 ; i<iUser ; i++ )
+ {
+ aoData.push( { "name": "iSortCol_"+(i+iFixed), "value": oSettings.aaSorting[i][0] } );
+ aoData.push( { "name": "sSortDir_"+(i+iFixed), "value": oSettings.aaSorting[i][1] } );
+ }
+
+ for ( i=0 ; i<iColumns ; i++ )
+ {
+ aoData.push( { "name": "bSortable_"+i, "value": oSettings.aoColumns[i].bSortable } );
+ }
+ }
+
+ oSettings.fnServerData.call( oSettings.oInstance, oSettings.sAjaxSource, aoData,
+ function(json) {
+ _fnAjaxUpdateDraw( oSettings, json );
+ } );
+ return false;
+ }
+ else
+ {
+ return true;
+ }
+ }
+
+ /*
+ * Function: _fnAjaxUpdateDraw
+ * Purpose: Data the data from the server (nuking the old) and redraw the table
+ * Returns: -
+ * Inputs: object:oSettings - dataTables settings object
+ * object:json - json data return from the server.
+ * The following must be defined:
+ * iTotalRecords, iTotalDisplayRecords, aaData
+ * The following may be defined:
+ * sColumns
+ */
+ function _fnAjaxUpdateDraw ( oSettings, json )
+ {
+ if ( typeof json.sEcho != 'undefined' )
+ {
+ /* Protect against old returns over-writing a new one. Possible when you get
+ * very fast interaction, and later queires are completed much faster
+ */
+ if ( json.sEcho*1 < oSettings.iDraw )
+ {
+ return;
+ }
+ else
+ {
+ oSettings.iDraw = json.sEcho * 1;
+ }
+ }
+
+ if ( !oSettings.oScroll.bInfinite ||
+ (oSettings.oScroll.bInfinite && (oSettings.bSorted || oSettings.bFiltered)) )
+ {
+ _fnClearTable( oSettings );
+ }
+ oSettings._iRecordsTotal = json.iTotalRecords;
+ oSettings._iRecordsDisplay = json.iTotalDisplayRecords;
+
+ /* Determine if reordering is required */
+ var sOrdering = _fnColumnOrdering(oSettings);
+ var bReOrder = (typeof json.sColumns != 'undefined' && sOrdering !== "" && json.sColumns != sOrdering );
+ if ( bReOrder )
+ {
+ var aiIndex = _fnReOrderIndex( oSettings, json.sColumns );
+ }
+
+ for ( var i=0, iLen=json.aaData.length ; i<iLen ; i++ )
+ {
+ if ( bReOrder )
+ {
+ /* If we need to re-order, then create a new array with the correct order and add it */
+ var aData = [];
+ for ( var j=0, jLen=oSettings.aoColumns.length ; j<jLen ; j++ )
+ {
+ aData.push( json.aaData[i][ aiIndex[j] ] );
+ }
+ _fnAddData( oSettings, aData );
+ }
+ else
+ {
+ /* No re-order required, sever got it "right" - just straight add */
+ _fnAddData( oSettings, json.aaData[i] );
+ }
+ }
+ oSettings.aiDisplay = oSettings.aiDisplayMaster.slice();
+
+ oSettings.bAjaxDataGet = false;
+ _fnDraw( oSettings );
+ oSettings.bAjaxDataGet = true;
+ _fnProcessingDisplay( oSettings, false );
+ }
+
+
+ /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+ * Section - Options (features) HTML
+ */
+
+ /*
+ * Function: _fnAddOptionsHtml
+ * Purpose: Add the options to the page HTML for the table
+ * Returns: -
+ * Inputs: object:oSettings - dataTables settings object
+ */
+ function _fnAddOptionsHtml ( oSettings )
+ {
+ /*
+ * Create a temporary, empty, div which we can later on replace with what we have generated
+ * we do it this way to rendering the 'options' html offline - speed :-)
+ */
+ var nHolding = document.createElement( 'div' );
+ oSettings.nTable.parentNode.insertBefore( nHolding, oSettings.nTable );
+
+ /*
+ * All DataTables are wrapped in a div - this is not currently optional - backwards
+ * compatability. It can be removed if you don't want it.
+ */
+ oSettings.nTableWrapper = document.createElement( 'div' );
+ oSettings.nTableWrapper.className = oSettings.oClasses.sWrapper;
+ if ( oSettings.sTableId !== '' )
+ {
+ oSettings.nTableWrapper.setAttribute( 'id', oSettings.sTableId+'_wrapper' );
+ }
+
+ /* Track where we want to insert the option */
+ var nInsertNode = oSettings.nTableWrapper;
+
+ /* Loop over the user set positioning and place the elements as needed */
+ var aDom = oSettings.sDom.split('');
+ var nTmp, iPushFeature, cOption, nNewNode, cNext, sAttr, j;
+ for ( var i=0 ; i<aDom.length ; i++ )
+ {
+ iPushFeature = 0;
+ cOption = aDom[i];
+
+ if ( cOption == '<' )
+ {
+ /* New container div */
+ nNewNode = document.createElement( 'div' );
+
+ /* Check to see if we should append an id and/or a class name to the container */
+ cNext = aDom[i+1];
+ if ( cNext == "'" || cNext == '"' )
+ {
+ sAttr = "";
+ j = 2;
+ while ( aDom[i+j] != cNext )
+ {
+ sAttr += aDom[i+j];
+ j++;
+ }
+
+ /* Replace jQuery UI constants */
+ if ( sAttr == "H" )
+ {
+ sAttr = "fg-toolbar ui-toolbar ui-widget-header ui-corner-tl ui-corner-tr ui-helper-clearfix";
+ }
+ else if ( sAttr == "F" )
+ {
+ sAttr = "fg-toolbar ui-toolbar ui-widget-header ui-corner-bl ui-corner-br ui-helper-clearfix";
+ }
+
+ /* The attribute can be in the format of "#id.class", "#id" or "class" This logic
+ * breaks the string into parts and applies them as needed
+ */
+ if ( sAttr.indexOf('.') != -1 )
+ {
+ var aSplit = sAttr.split('.');
+ nNewNode.setAttribute('id', aSplit[0].substr(1, aSplit[0].length-1) );
+ nNewNode.className = aSplit[1];
+ }
+ else if ( sAttr.charAt(0) == "#" )
+ {
+ nNewNode.setAttribute('id', sAttr.substr(1, sAttr.length-1) );
+ }
+ else
+ {
+ nNewNode.className = sAttr;
+ }
+
+ i += j; /* Move along the position array */
+ }
+
+ nInsertNode.appendChild( nNewNode );
+ nInsertNode = nNewNode;
+ }
+ else if ( cOption == '>' )
+ {
+ /* End container div */
+ nInsertNode = nInsertNode.parentNode;
+ }
+ else if ( cOption == 'l' && oSettings.oFeatures.bPaginate && oSettings.oFeatures.bLengthChange )
+ {
+ /* Length */
+ nTmp = _fnFeatureHtmlLength( oSettings );
+ iPushFeature = 1;
+ }
+ else if ( cOption == 'f' && oSettings.oFeatures.bFilter )
+ {
+ /* Filter */
+ nTmp = _fnFeatureHtmlFilter( oSettings );
+ iPushFeature = 1;
+ }
+ else if ( cOption == 'r' && oSettings.oFeatures.bProcessing )
+ {
+ /* pRocessing */
+ nTmp = _fnFeatureHtmlProcessing( oSettings );
+ iPushFeature = 1;
+ }
+ else if ( cOption == 't' )
+ {
+ /* Table */
+ nTmp = _fnFeatureHtmlTable( oSettings );
+ iPushFeature = 1;
+ }
+ else if ( cOption == 'i' && oSettings.oFeatures.bInfo )
+ {
+ /* Info */
+ nTmp = _fnFeatureHtmlInfo( oSettings );
+ iPushFeature = 1;
+ }
+ else if ( cOption == 'p' && oSettings.oFeatures.bPaginate )
+ {
+ /* Pagination */
+ nTmp = _fnFeatureHtmlPaginate( oSettings );
+ iPushFeature = 1;
+ }
+ else if ( _oExt.aoFeatures.length !== 0 )
+ {
+ /* Plug-in features */
+ var aoFeatures = _oExt.aoFeatures;
+ for ( var k=0, kLen=aoFeatures.length ; k<kLen ; k++ )
+ {
+ if ( cOption == aoFeatures[k].cFeature )
+ {
+ nTmp = aoFeatures[k].fnInit( oSettings );
+ if ( nTmp )
+ {
+ iPushFeature = 1;
+ }
+ break;
+ }
+ }
+ }
+
+ /* Add to the 2D features array */
+ if ( iPushFeature == 1 && nTmp !== null )
+ {
+ if ( typeof oSettings.aanFeatures[cOption] != 'object' )
+ {
+ oSettings.aanFeatures[cOption] = [];
+ }
+ oSettings.aanFeatures[cOption].push( nTmp );
+ nInsertNode.appendChild( nTmp );
+ }
+ }
+
+ /* Built our DOM structure - replace the holding div with what we want */
+ nHolding.parentNode.replaceChild( oSettings.nTableWrapper, nHolding );
+ }
+
+
+ /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+ * Section - Feature: Filtering
+ */
+
+ /*
+ * Function: _fnFeatureHtmlTable
+ * Purpose: Add any control elements for the table - specifically scrolling
+ * Returns: node: - Node to add to the DOM
+ * Inputs: object:oSettings - dataTables settings object
+ */
+ function _fnFeatureHtmlTable ( oSettings )
+ {
+ /* Chack if scrolling is enabled or not - if not then leave the DOM unaltered */
+ if ( oSettings.oScroll.sX === "" && oSettings.oScroll.sY === "" )
+ {
+ return oSettings.nTable;
+ }
+
+ /*
+ * The HTML structure that we want to generate in this function is:
+ * div - nScroller
+ * div - nScrollHead
+ * div - nScrollHeadInner
+ * table - nScrollHeadTable
+ * thead - nThead
+ * div - nScrollBody
+ * table - oSettings.nTable
+ * thead - nTheadSize
+ * tbody - nTbody
+ * div - nScrollFoot
+ * div - nScrollFootInner
+ * table - nScrollFootTable
+ * tfoot - nTfoot
+ */
+ var
+ nScroller = document.createElement('div'),
+ nScrollHead = document.createElement('div'),
+ nScrollHeadInner = document.createElement('div'),
+ nScrollBody = document.createElement('div'),
+ nScrollFoot = document.createElement('div'),
+ nScrollFootInner = document.createElement('div'),
+ nScrollHeadTable = oSettings.nTable.cloneNode(false),
+ nScrollFootTable = oSettings.nTable.cloneNode(false),
+ nThead = oSettings.nTable.getElementsByTagName('thead')[0],
+ nTfoot = oSettings.nTable.getElementsByTagName('tfoot').length === 0 ? null :
+ oSettings.nTable.getElementsByTagName('tfoot')[0],
+ oClasses = (typeof oInit.bJQueryUI != 'undefined' && oInit.bJQueryUI) ?
+ _oExt.oJUIClasses : _oExt.oStdClasses;
+
+ nScrollHead.appendChild( nScrollHeadInner );
+ nScrollFoot.appendChild( nScrollFootInner );
+ nScrollBody.appendChild( oSettings.nTable );
+ nScroller.appendChild( nScrollHead );
+ nScroller.appendChild( nScrollBody );
+ nScrollHeadInner.appendChild( nScrollHeadTable );
+ nScrollHeadTable.appendChild( nThead );
+ if ( nTfoot !== null )
+ {
+ nScroller.appendChild( nScrollFoot );
+ nScrollFootInner.appendChild( nScrollFootTable );
+ nScrollFootTable.appendChild( nTfoot );
+ }
+
+ nScroller.className = oClasses.sScrollWrapper;
+ nScrollHead.className = oClasses.sScrollHead;
+ nScrollHeadInner.className = oClasses.sScrollHeadInner;
+ nScrollBody.className = oClasses.sScrollBody;
+ nScrollFoot.className = oClasses.sScrollFoot;
+ nScrollFootInner.className = oClasses.sScrollFootInner;
+
+ nScrollHead.style.overflow = "hidden";
+ nScrollFoot.style.overflow = "hidden";
+ nScrollBody.style.overflow = "auto";
+ nScrollHead.style.border = "0";
+ nScrollFoot.style.border = "0";
+ nScrollHeadInner.style.width = "150%"; /* will be overwritten */
+
+ /* Modify attributes to respect the clones */
+ nScrollHeadTable.removeAttribute('id');
+ nScrollHeadTable.style.marginLeft = "0";
+ oSettings.nTable.style.marginLeft = "0";
+ if ( nTfoot !== null )
+ {
+ nScrollFootTable.removeAttribute('id');
+ nScrollFootTable.style.marginLeft = "0";
+ }
+
+ /* Move any caption elements from the body to the header */
+ var nCaptions = $('>caption', oSettings.nTable);
+ for ( var i=0, iLen=nCaptions.length ; i<iLen ; i++ )
+ {
+ nScrollHeadTable.appendChild( nCaptions[i] );
+ }
+
+ /*
+ * Sizing
+ */
+ /* When xscrolling add the width and a scroller to move the header with the body */
+ if ( oSettings.oScroll.sX !== "" )
+ {
+ nScrollHead.style.width = _fnStringToCss( oSettings.oScroll.sX );
+ nScrollBody.style.width = _fnStringToCss( oSettings.oScroll.sX );
+
+ if ( nTfoot !== null )
+ {
+ nScrollFoot.style.width = _fnStringToCss( oSettings.oScroll.sX );
+ }
+
+ /* When the body is scrolled, then we also want to scroll the headers */
+ $(nScrollBody).scroll( function (e) {
+ nScrollHead.scrollLeft = this.scrollLeft;
+
+ if ( nTfoot !== null )
+ {
+ nScrollFoot.scrollLeft = this.scrollLeft;
+ }
+ } );
+ }
+
+ /* When yscrolling, add the height */
+ if ( oSettings.oScroll.sY !== "" )
+ {
+ nScrollBody.style.height = _fnStringToCss( oSettings.oScroll.sY );
+ }
+
+ /* Redraw - align columns across the tables */
+ oSettings.aoDrawCallback.push( {
+ "fn": _fnScrollDraw,
+ "sName": "scrolling"
+ } );
+
+ /* Infinite scrolling event handlers */
+ if ( oSettings.oScroll.bInfinite )
+ {
+ $(nScrollBody).scroll( function() {
+ /* Use a blocker to stop scrolling from loading more data while other data is still loading */
+ if ( !oSettings.bDrawing )
+ {
+ /* Check if we should load the next data set */
+ if ( $(this).scrollTop() + $(this).height() >
+ $(oSettings.nTable).height() - oSettings.oScroll.iLoadGap )
+ {
+ /* Only do the redraw if we have to - we might be at the end of the data */
+ if ( oSettings.fnDisplayEnd() < oSettings.fnRecordsDisplay() )
+ {
+ _fnPageChange( oSettings, 'next' );
+ _fnCalculateEnd( oSettings );
+ _fnDraw( oSettings );
+ }
+ }
+ }
+ } );
+ }
+
+ oSettings.nScrollHead = nScrollHead;
+ oSettings.nScrollFoot = nScrollFoot;
+
+ return nScroller;
+ }
+
+ /*
+ * Function: _fnScrollDraw
+ * Purpose: Update the various tables for resizing
+ * Returns: node: - Node to add to the DOM
+ * Inputs: object:o - dataTables settings object
+ * Notes: It's a bit of a pig this function, but basically the idea to:
+ * 1. Re-create the table inside the scrolling div
+ * 2. Take live measurements from the DOM
+ * 3. Apply the measurements
+ * 4. Clean up
+ */
+ function _fnScrollDraw ( o )
+ {
+ var
+ nScrollHeadInner = o.nScrollHead.getElementsByTagName('div')[0],
+ nScrollHeadTable = nScrollHeadInner.getElementsByTagName('table')[0],
+ nScrollBody = o.nTable.parentNode,
+ i, iLen, j, jLen, anHeadToSize, anHeadSizers, anFootSizers, anFootToSize, oStyle, iVis,
+ iWidth, aApplied=[], iSanityWidth;
+
+ /*
+ * 1. Re-create the table inside the scrolling div
+ */
+
+ /* Remove the old minimised thead and tfoot elements in the inner table */
+ var nTheadSize = o.nTable.getElementsByTagName('thead');
+ if ( nTheadSize.length > 0 )
+ {
+ o.nTable.removeChild( nTheadSize[0] );
+ }
+
+ if ( o.nTFoot !== null )
+ {
+ /* Remove the old minimised footer element in the cloned header */
+ var nTfootSize = o.nTable.getElementsByTagName('tfoot');
+ if ( nTfootSize.length > 0 )
+ {
+ o.nTable.removeChild( nTfootSize[0] );
+ }
+ }
+
+ /* Clone the current header and footer elements and then place it into the inner table */
+ nTheadSize = o.nTHead.cloneNode(true);
+ o.nTable.insertBefore( nTheadSize, o.nTable.childNodes[0] );
+
+ if ( o.nTFoot !== null )
+ {
+ nTfootSize = o.nTFoot.cloneNode(true);
+ o.nTable.insertBefore( nTfootSize, o.nTable.childNodes[1] );
+ }
+
+ /*
+ * 2. Take live measurements from the DOM - do not alter the DOM itself!
+ */
+
+ /* Remove old sizing and apply the calculated column widths
+ * Get the unique column headers in the newly created (cloned) header. We want to apply the
+ * calclated sizes to this header
+ */
+ var nThs = _fnGetUniqueThs( nTheadSize );
+ for ( i=0, iLen=nThs.length ; i<iLen ; i++ )
+ {
+ iVis = _fnVisibleToColumnIndex( o, i );
+ nThs[i].style.width = o.aoColumns[iVis].sWidth;
+ }
+
+ if ( o.nTFoot !== null )
+ {
+ _fnApplyToChildren( function(n) {
+ n.style.width = "";
+ }, nTfootSize.getElementsByTagName('tr') );
+ }
+
+ /* Size the table as a whole */
+ iSanityWidth = $(o.nTable).outerWidth();
+ if ( o.oScroll.sX === "" )
+ {
+ /* No x scrolling */
+ o.nTable.style.width = "100%";
+
+ /* I know this is rubbish - but IE7 will make the width of the table when 100% include
+ * the scrollbar - which is shouldn't. This needs feature detection in future - to do
+ */
+ if ( $.browser.msie && $.browser.version <= 7 )
+ {
+ o.nTable.style.width = _fnStringToCss( $(o.nTable).outerWidth()-o.oScroll.iBarWidth );
+ }
+ }
+ else
+ {
+ if ( o.oScroll.sXInner !== "" )
+ {
+ /* x scroll inner has been given - use it */
+ o.nTable.style.width = _fnStringToCss(o.oScroll.sXInner);
+ }
+ else if ( iSanityWidth == $(nScrollBody).width() &&
+ $(nScrollBody).height() < $(o.nTable).height() )
+ {
+ /* There is y-scrolling - try to take account of the y scroll bar */
+ o.nTable.style.width = _fnStringToCss( iSanityWidth-o.oScroll.iBarWidth );
+ if ( $(o.nTable).outerWidth() > iSanityWidth-o.oScroll.iBarWidth )
+ {
+ /* Not possible to take account of it */
+ o.nTable.style.width = _fnStringToCss( iSanityWidth );
+ }
+ }
+ else
+ {
+ /* All else fails */
+ o.nTable.style.width = _fnStringToCss( iSanityWidth );
+ }
+ }
+
+ /* Recalculate the sanity width - now that we've applied the required width, before it was
+ * a temporary variable. This is required because the column width calculation is done
+ * before this table DOM is created.
+ */
+ iSanityWidth = $(o.nTable).outerWidth();
+
+ /* We want the hidden header to have zero height, so remove padding and borders. Then
+ * set the width based on the real headers
+ */
+ anHeadToSize = o.nTHead.getElementsByTagName('tr');
+ anHeadSizers = nTheadSize.getElementsByTagName('tr');
+
+ _fnApplyToChildren( function(nSizer, nToSize) {
+ oStyle = nSizer.style;
+ oStyle.paddingTop = "0";
+ oStyle.paddingBottom = "0";
+ oStyle.borderTopWidth = "0";
+ oStyle.borderBottomWidth = "0";
+ oStyle.height = 0;
+
+ iWidth = $(nSizer).width();
+ nToSize.style.width = _fnStringToCss( iWidth );
+ aApplied.push( iWidth );
+ }, anHeadSizers, anHeadToSize );
+ $(anHeadSizers).height(0);
+
+ if ( o.nTFoot !== null )
+ {
+ /* Clone the current footer and then place it into the body table as a "hidden header" */
+ anFootSizers = nTfootSize.getElementsByTagName('tr');
+ anFootToSize = o.nTFoot.getElementsByTagName('tr');
+
+ _fnApplyToChildren( function(nSizer, nToSize) {
+ oStyle = nSizer.style;
+ oStyle.paddingTop = "0";
+ oStyle.paddingBottom = "0";
+ oStyle.borderTopWidth = "0";
+ oStyle.borderBottomWidth = "0";
+
+ iWidth = $(nSizer).width();
+ nToSize.style.width = _fnStringToCss( iWidth );
+ aApplied.push( iWidth );
+ }, anFootSizers, anFootToSize );
+ $(anFootSizers).height(0);
+ }
+
+ /*
+ * 3. Apply the measurements
+ */
+
+ /* "Hide" the header and footer that we used for the sizing. We want to also fix their width
+ * to what they currently are
+ */
+ _fnApplyToChildren( function(nSizer) {
+ nSizer.innerHTML = "";
+ nSizer.style.width = _fnStringToCss( aApplied.shift() );
+ }, anHeadSizers );
+
+ if ( o.nTFoot !== null )
+ {
+ _fnApplyToChildren( function(nSizer) {
+ nSizer.innerHTML = "";
+ nSizer.style.width = _fnStringToCss( aApplied.shift() );
+ }, anFootSizers );
+ }
+
+ /* Sanity check that the table is of a sensible width. If not then we are going to get
+ * misalignment
+ */
+ if ( $(o.nTable).outerWidth() < iSanityWidth )
+ {
+ if ( o.oScroll.sX === "" )
+ {
+ _fnLog( o, 1, "The table cannot fit into the current element which will cause column"+
+ " misalignment. It is suggested that you enable x-scrolling or increase the width"+
+ " the table has in which to be drawn" );
+ }
+ else if ( o.oScroll.sXInner !== "" )
+ {
+ _fnLog( o, 1, "The table cannot fit into the current element which will cause column"+
+ " misalignment. It is suggested that you increase the sScrollXInner property to"+
+ " allow it to draw in a larger area, or simply remove that parameter to allow"+
+ " automatic calculation" );
+ }
+ }
+
+
+ /*
+ * 4. Clean up
+ */
+
+ if ( o.oScroll.sY === "" )
+ {
+ /* IE7< puts a vertical scrollbar in place (when it shouldn't be) due to subtracting
+ * the scrollbar height from the visible display, rather than adding it on. We need to
+ * set the height in order to sort this. Don't want to do it in any other browsers.
+ */
+ if ( $.browser.msie && $.browser.version <= 7 )
+ {
+ nScrollBody.style.height = _fnStringToCss( o.nTable.offsetHeight+o.oScroll.iBarWidth );
+ }
+ }
+
+ if ( o.oScroll.sY !== "" && o.oScroll.bCollapse )
+ {
+ nScrollBody.style.height = _fnStringToCss( o.oScroll.sY );
+
+ var iExtra = (o.oScroll.sX !== "" && o.nTable.offsetWidth > nScrollBody.offsetWidth) ?
+ o.oScroll.iBarWidth : 0;
+ if ( o.nTable.offsetHeight < nScrollBody.offsetHeight )
+ {
+ nScrollBody.style.height = _fnStringToCss( $(o.nTable).height()+iExtra );
+ }
+ }
+
+ /* Finally set the width's of the header and footer tables */
+ nScrollHeadTable.style.width = _fnStringToCss( $(o.nTable).outerWidth() );
+ nScrollHeadInner.style.width = _fnStringToCss( $(o.nTable).outerWidth()+o.oScroll.iBarWidth );
+
+ if ( o.nTFoot !== null )
+ {
+ var
+ nScrollFootInner = o.nScrollFoot.getElementsByTagName('div')[0],
+ nScrollFootTable = nScrollFootInner.getElementsByTagName('table')[0];
+
+ nScrollFootInner.style.width = _fnStringToCss( o.nTable.offsetWidth+o.oScroll.iBarWidth );
+ nScrollFootTable.style.width = _fnStringToCss( o.nTable.offsetWidth );
+ }
+
+ /* If sorting or filtering has occured, jump the scrolling back to the top */
+ if ( o.bSorted || o.bFiltered )
+ {
+ nScrollBody.scrollTop = 0;
+ }
+ }
+
+ /*
+ * Function: _fnAjustColumnSizing
+ * Purpose: Ajust the table column widths for new data
+ * Returns: -
+ * Inputs: object:oSettings - dataTables settings object
+ * Notes: You would probably want to do a redraw after calling this function!
+ */
+ function _fnAjustColumnSizing ( oSettings )
+ {
+ /* Not interested in doing column width calculation if autowidth is disabled */
+ if ( oSettings.oFeatures.bAutoWidth === false )
+ {
+ return false;
+ }
+
+ _fnCalculateColumnWidths( oSettings );
+ for ( var i=0 , iLen=oSettings.aoColumns.length ; i<iLen ; i++ )
+ {
+ oSettings.aoColumns[i].nTh.style.width = oSettings.aoColumns[i].sWidth;
+ }
+ }
+
+
+ /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+ * Section - Feature: Filtering
+ */
+
+ /*
+ * Function: _fnFeatureHtmlFilter
+ * Purpose: Generate the node required for filtering text
+ * Returns: node
+ * Inputs: object:oSettings - dataTables settings object
+ */
+ function _fnFeatureHtmlFilter ( oSettings )
+ {
+ var nFilter = document.createElement( 'div' );
+ if ( oSettings.sTableId !== '' && typeof oSettings.aanFeatures.f == "undefined" )
+ {
+ nFilter.setAttribute( 'id', oSettings.sTableId+'_filter' );
+ }
+ nFilter.className = oSettings.oClasses.sFilter;
+ var sSpace = oSettings.oLanguage.sSearch==="" ? "" : " ";
+ nFilter.innerHTML = oSettings.oLanguage.sSearch+sSpace+'<input type="text" />';
+
+ var jqFilter = $("input", nFilter);
+ jqFilter.val( oSettings.oPreviousSearch.sSearch.replace('"','&quot;') );
+ jqFilter.keyup( function(e) {
+ /* Update all other filter input elements for the new display */
+ var n = oSettings.aanFeatures.f;
+ for ( var i=0, iLen=n.length ; i<iLen ; i++ )
+ {
+ if ( n[i] != this.parentNode )
+ {
+ $('input', n[i]).val( this.value );
+ }
+ }
+
+ /* Now do the filter */
+ _fnFilterComplete( oSettings, {
+ "sSearch": this.value,
+ "bRegex": oSettings.oPreviousSearch.bRegex,
+ "bSmart": oSettings.oPreviousSearch.bSmart
+ } );
+ } );
+
+ jqFilter.keypress( function(e) {
+ /* Prevent default */
+ if ( e.keyCode == 13 )
+ {
+ return false;
+ }
+ } );
+
+ return nFilter;
+ }
+
+ /*
+ * Function: _fnFilterComplete
+ * Purpose: Filter the table using both the global filter and column based filtering
+ * Returns: -
+ * Inputs: object:oSettings - dataTables settings object
+ * object:oSearch: search information
+ * int:iForce - optional - force a research of the master array (1) or not (undefined or 0)
+ */
+ function _fnFilterComplete ( oSettings, oInput, iForce )
+ {
+ /* Filter on everything */
+ _fnFilter( oSettings, oInput.sSearch, iForce, oInput.bRegex, oInput.bSmart );
+
+ /* Now do the individual column filter */
+ for ( var i=0 ; i<oSettings.aoPreSearchCols.length ; i++ )
+ {
+ _fnFilterColumn( oSettings, oSettings.aoPreSearchCols[i].sSearch, i,
+ oSettings.aoPreSearchCols[i].bRegex, oSettings.aoPreSearchCols[i].bSmart );
+ }
+
+ /* Custom filtering */
+ if ( _oExt.afnFiltering.length !== 0 )
+ {
+ _fnFilterCustom( oSettings );
+ }
+
+ /* Tell the draw function we have been filtering */
+ oSettings.bFiltered = true;
+
+ /* Redraw the table */
+ oSettings._iDisplayStart = 0;
+ _fnCalculateEnd( oSettings );
+ _fnDraw( oSettings );
+
+ /* Rebuild search array 'offline' */
+ _fnBuildSearchArray( oSettings, 0 );
+ }
+
+ /*
+ * Function: _fnFilterCustom
+ * Purpose: Apply custom filtering functions
+ * Returns: -
+ * Inputs: object:oSettings - dataTables settings object
+ */
+ function _fnFilterCustom( oSettings )
+ {
+ var afnFilters = _oExt.afnFiltering;
+ for ( var i=0, iLen=afnFilters.length ; i<iLen ; i++ )
+ {
+ var iCorrector = 0;
+ for ( var j=0, jLen=oSettings.aiDisplay.length ; j<jLen ; j++ )
+ {
+ var iDisIndex = oSettings.aiDisplay[j-iCorrector];
+
+ /* Check if we should use this row based on the filtering function */
+ if ( !afnFilters[i]( oSettings, oSettings.aoData[iDisIndex]._aData, iDisIndex ) )
+ {
+ oSettings.aiDisplay.splice( j-iCorrector, 1 );
+ iCorrector++;
+ }
+ }
+ }
+ }
+
+ /*
+ * Function: _fnFilterColumn
+ * Purpose: Filter the table on a per-column basis
+ * Returns: -
+ * Inputs: object:oSettings - dataTables settings object
+ * string:sInput - string to filter on
+ * int:iColumn - column to filter
+ * bool:bRegex - treat search string as a regular expression or not
+ * bool:bSmart - use smart filtering or not
+ */
+ function _fnFilterColumn ( oSettings, sInput, iColumn, bRegex, bSmart )
+ {
+ if ( sInput === "" )
+ {
+ return;
+ }
+
+ var iIndexCorrector = 0;
+ var rpSearch = _fnFilterCreateSearch( sInput, bRegex, bSmart );
+
+ for ( var i=oSettings.aiDisplay.length-1 ; i>=0 ; i-- )
+ {
+ var sData = _fnDataToSearch( oSettings.aoData[ oSettings.aiDisplay[i] ]._aData[iColumn],
+ oSettings.aoColumns[iColumn].sType );
+ if ( ! rpSearch.test( sData ) )
+ {
+ oSettings.aiDisplay.splice( i, 1 );
+ iIndexCorrector++;
+ }
+ }
+ }
+
+ /*
+ * Function: _fnFilter
+ * Purpose: Filter the data table based on user input and draw the table
+ * Returns: -
+ * Inputs: object:oSettings - dataTables settings object
+ * string:sInput - string to filter on
+ * int:iForce - optional - force a research of the master array (1) or not (undefined or 0)
+ * bool:bRegex - treat as a regular expression or not
+ * bool:bSmart - perform smart filtering or not
+ */
+ function _fnFilter( oSettings, sInput, iForce, bRegex, bSmart )
+ {
+ var i;
+ var rpSearch = _fnFilterCreateSearch( sInput, bRegex, bSmart );
+
+ /* Check if we are forcing or not - optional parameter */
+ if ( typeof iForce == 'undefined' || iForce === null )
+ {
+ iForce = 0;
+ }
+
+ /* Need to take account of custom filtering functions - always filter */
+ if ( _oExt.afnFiltering.length !== 0 )
+ {
+ iForce = 1;
+ }
+
+ /*
+ * If the input is blank - we want the full data set
+ */
+ if ( sInput.length <= 0 )
+ {
+ oSettings.aiDisplay.splice( 0, oSettings.aiDisplay.length);
+ oSettings.aiDisplay = oSettings.aiDisplayMaster.slice();
+ }
+ else
+ {
+ /*
+ * We are starting a new search or the new search string is smaller
+ * then the old one (i.e. delete). Search from the master array
+ */
+ if ( oSettings.aiDisplay.length == oSettings.aiDisplayMaster.length ||
+ oSettings.oPreviousSearch.sSearch.length > sInput.length || iForce == 1 ||
+ sInput.indexOf(oSettings.oPreviousSearch.sSearch) !== 0 )
+ {
+ /* Nuke the old display array - we are going to rebuild it */
+ oSettings.aiDisplay.splice( 0, oSettings.aiDisplay.length);
+
+ /* Force a rebuild of the search array */
+ _fnBuildSearchArray( oSettings, 1 );
+
+ /* Search through all records to populate the search array
+ * The the oSettings.aiDisplayMaster and asDataSearch arrays have 1 to 1
+ * mapping
+ */
+ for ( i=0 ; i<oSettings.aiDisplayMaster.length ; i++ )
+ {
+ if ( rpSearch.test(oSettings.asDataSearch[i]) )
+ {
+ oSettings.aiDisplay.push( oSettings.aiDisplayMaster[i] );
+ }
+ }
+ }
+ else
+ {
+ /* Using old search array - refine it - do it this way for speed
+ * Don't have to search the whole master array again
+ */
+ var iIndexCorrector = 0;
+
+ /* Search the current results */
+ for ( i=0 ; i<oSettings.asDataSearch.length ; i++ )
+ {
+ if ( ! rpSearch.test(oSettings.asDataSearch[i]) )
+ {
+ oSettings.aiDisplay.splice( i-iIndexCorrector, 1 );
+ iIndexCorrector++;
+ }
+ }
+ }
+ }
+ oSettings.oPreviousSearch.sSearch = sInput;
+ oSettings.oPreviousSearch.bRegex = bRegex;
+ oSettings.oPreviousSearch.bSmart = bSmart;
+ }
+
+ /*
+ * Function: _fnBuildSearchArray
+ * Purpose: Create an array which can be quickly search through
+ * Returns: -
+ * Inputs: object:oSettings - dataTables settings object
+ * int:iMaster - use the master data array - optional
+ */
+ function _fnBuildSearchArray ( oSettings, iMaster )
+ {
+ /* Clear out the old data */
+ oSettings.asDataSearch.splice( 0, oSettings.asDataSearch.length );
+
+ var nTmp = document.createElement('div');
+ var aArray = (typeof iMaster != 'undefined' && iMaster == 1) ?
+ oSettings.aiDisplayMaster : oSettings.aiDisplay;
+
+ for ( var i=0, iLen=aArray.length ; i<iLen ; i++ )
+ {
+ oSettings.asDataSearch[i] = '';
+ for ( var j=0, jLen=oSettings.aoColumns.length ; j<jLen ; j++ )
+ {
+ if ( oSettings.aoColumns[j].bSearchable )
+ {
+ var sData = oSettings.aoData[ aArray[i] ]._aData[j];
+ oSettings.asDataSearch[i] += _fnDataToSearch( sData, oSettings.aoColumns[j].sType )+' ';
+ }
+ }
+
+ /* If it looks like there is an HTML entity in the string, attempt to decode it */
+ if ( oSettings.asDataSearch[i].indexOf('&') !== -1 )
+ {
+ nTmp.innerHTML = oSettings.asDataSearch[i];
+ oSettings.asDataSearch[i] = nTmp.textContent ? nTmp.textContent : nTmp.innerText;
+
+ /* IE and Opera appear to put an newline where there is a <br> tag - remove it */
+ oSettings.asDataSearch[i] = oSettings.asDataSearch[i].replace(/\n/g," ").replace(/\r/g,"");
+ }
+ }
+ }
+
+ /*
+ * Function: _fnFilterCreateSearch
+ * Purpose: Build a regular expression object suitable for searching a table
+ * Returns: RegExp: - constructed object
+ * Inputs: string:sSearch - string to search for
+ * bool:bRegex - treat as a regular expression or not
+ * bool:bSmart - perform smart filtering or not
+ */
+ function _fnFilterCreateSearch( sSearch, bRegex, bSmart )
+ {
+ var asSearch, sRegExpString;
+
+ if ( bSmart )
+ {
+ /* Generate the regular expression to use. Something along the lines of:
+ * ^(?=.*?\bone\b)(?=.*?\btwo\b)(?=.*?\bthree\b).*$
+ */
+ asSearch = bRegex ? sSearch.split( ' ' ) : _fnEscapeRegex( sSearch ).split( ' ' );
+ sRegExpString = '^(?=.*?'+asSearch.join( ')(?=.*?' )+').*$';
+ return new RegExp( sRegExpString, "i" );
+ }
+ else
+ {
+ sSearch = bRegex ? sSearch : _fnEscapeRegex( sSearch );
+ return new RegExp( sSearch, "i" );
+ }
+ }
+
+ /*
+ * Function: _fnDataToSearch
+ * Purpose: Convert raw data into something that the user can search on
+ * Returns: string: - search string
+ * Inputs: string:sData - data to be modified
+ * string:sType - data type
+ */
+ function _fnDataToSearch ( sData, sType )
+ {
+ if ( typeof _oExt.ofnSearch[sType] == "function" )
+ {
+ return _oExt.ofnSearch[sType]( sData );
+ }
+ else if ( sType == "html" )
+ {
+ return sData.replace(/\n/g," ").replace( /<.*?>/g, "" );
+ }
+ else if ( typeof sData == "string" )
+ {
+ return sData.replace(/\n/g," ");
+ }
+ return sData;
+ }
+
+
+ /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+ * Section - Feature: Sorting
+ */
+
+ /*
+ * Function: _fnSort
+ * Purpose: Change the order of the table
+ * Returns: -
+ * Inputs: object:oSettings - dataTables settings object
+ * bool:bApplyClasses - optional - should we apply classes or not
+ * Notes: We always sort the master array and then apply a filter again
+ * if it is needed. This probably isn't optimal - but atm I can't think
+ * of any other way which is (each has disadvantages). we want to sort aiDisplayMaster -
+ * but according to aoData[]._aData
+ */
+ function _fnSort ( oSettings, bApplyClasses )
+ {
+ var aaSort = [];
+ var oSort = _oExt.oSort;
+ var aoData = oSettings.aoData;
+ var iDataSort;
+ var iDataType;
+ var i, j, jLen;
+
+ /* No sorting required if server-side or no sorting array */
+ if ( !oSettings.oFeatures.bServerSide &&
+ (oSettings.aaSorting.length !== 0 || oSettings.aaSortingFixed !== null) )
+ {
+ if ( oSettings.aaSortingFixed !== null )
+ {
+ aaSort = oSettings.aaSortingFixed.concat( oSettings.aaSorting );
+ }
+ else
+ {
+ aaSort = oSettings.aaSorting.slice();
+ }
+
+ /* If there is a sorting data type, and a fuction belonging to it, then we need to
+ * get the data from the developer's function and apply it for this column
+ */
+ for ( i=0 ; i<aaSort.length ; i++ )
+ {
+ var iColumn = aaSort[i][0];
+ var iVisColumn = _fnColumnIndexToVisible( oSettings, iColumn );
+ var sDataType = oSettings.aoColumns[ iColumn ].sSortDataType;
+ if ( typeof _oExt.afnSortData[sDataType] != 'undefined' )
+ {
+ var aData = _oExt.afnSortData[sDataType]( oSettings, iColumn, iVisColumn );
+ for ( j=0, jLen=aoData.length ; j<jLen ; j++ )
+ {
+ aoData[j]._aData[iColumn] = aData[j];
+ }
+ }
+ }
+
+ /* DataTables offers two different methods for doing the 2D array sorting over multiple
+ * columns. The first is to construct a function dynamically, and then evaluate and run
+ * the function, while the second has no need for evalulation, but is a little bit slower.
+ * This is used for environments which do not allow eval() for code execuation such as AIR
+ */
+ if ( !window.runtime )
+ {
+ /* Dynamically created sorting function. Based on the information that we have, we can
+ * create a sorting function as if it were specifically written for this sort. Here we
+ * want to build a function something like (for two column sorting):
+ * fnLocalSorting = function(a,b){
+ * var iTest;
+ * iTest = oSort['string-asc']('data11', 'data12');
+ * if (iTest === 0)
+ * iTest = oSort['numeric-desc']('data21', 'data22');
+ * if (iTest === 0)
+ * return oSort['numeric-desc'](1,2);
+ * return iTest;
+ * }
+ * So basically we have a test for each column, and if that column matches, test the
+ * next one. If all columns match, then we use a numeric sort on the position the two
+ * row have in the original data array in order to provide a stable sort. In order to
+ * get the position for the numeric stablisation, we need to take a clone of the current
+ * display array and then get the position of the sorting value from that during the
+ * sort.
+ *
+ * Note that for use with the Closure compiler, we need to be very careful how we deal
+ * with this eval. Closure will rename all of our local variables, resutling in breakage
+ * if the variables in the eval don't also reflect this. For this reason, we need to use
+ * 'this' to store the variables we need in the eval, so we can control them. A little
+ * nasty, but well worth it for using Closure.
+ */
+ this.ClosureDataTables = {
+ "fn": function(){},
+ "data": aoData,
+ "sort": _oExt.oSort,
+ "master": oSettings.aiDisplayMaster.slice()
+ };
+ var sDynamicSort = "this.ClosureDataTables.fn = function(a,b){"+
+ "var iTest, oSort=this.ClosureDataTables.sort, "+
+ "aoData=this.ClosureDataTables.data, "+
+ "aiOrig=this.ClosureDataTables.master;";
+
+ for ( i=0 ; i<aaSort.length-1 ; i++ )
+ {
+ iDataSort = oSettings.aoColumns[ aaSort[i][0] ].iDataSort;
+ iDataType = oSettings.aoColumns[ iDataSort ].sType;
+ sDynamicSort += "iTest = oSort['"+iDataType+"-"+aaSort[i][1]+"']"+
+ "( aoData[a]._aData["+iDataSort+"], aoData[b]._aData["+iDataSort+"] ); if ( iTest === 0 )";
+ }
+
+ if ( aaSort.length > 0 )
+ {
+ iDataSort = oSettings.aoColumns[ aaSort[aaSort.length-1][0] ].iDataSort;
+ iDataType = oSettings.aoColumns[ iDataSort ].sType;
+ sDynamicSort += "iTest = oSort['"+iDataType+"-"+aaSort[aaSort.length-1][1]+"']"+
+ "( aoData[a]._aData["+iDataSort+"], aoData[b]._aData["+iDataSort+"] );"+
+ "if (iTest===0) "+
+ "return oSort['numeric-asc'](jQuery.inArray(a,aiOrig), jQuery.inArray(b,aiOrig)); "+
+ "return iTest;}";
+
+ /* The eval has to be done to a variable for IE */
+ eval( sDynamicSort );
+ oSettings.aiDisplayMaster.sort( this.ClosureDataTables.fn );
+ }
+ this.ClosureDataTables = undefined;
+ }
+ else
+ {
+ /*
+ * Non-eval() sorting (AIR and other environments which doesn't allow code in eval()
+ * Note that for reasonable sized data sets this method is around 1.5 times slower than
+ * the eval above (hence why it is not used all the time). Oddly enough, it is ever so
+ * slightly faster for very small sets (presumably the eval has overhead).
+ * Single column (1083 records) - eval: 32mS AIR: 38mS
+ * Two columns (1083 records) - eval: 55mS AIR: 66mS
+ */
+
+ /* Build a cached array so the sort doesn't have to process this stuff on every call */
+ var aAirSort = [];
+ var iLen = aaSort.length;
+ for ( i=0 ; i<iLen ; i++ )
+ {
+ iDataSort = oSettings.aoColumns[ aaSort[i][0] ].iDataSort;
+ aAirSort.push( [
+ iDataSort,
+ oSettings.aoColumns[ iDataSort ].sType+'-'+aaSort[i][1]
+ ] );
+ }
+
+ oSettings.aiDisplayMaster.sort( function (a,b) {
+ var iTest;
+ for ( var i=0 ; i<iLen ; i++ )
+ {
+ iTest = oSort[ aAirSort[i][1] ]( aoData[a]._aData[aAirSort[i][0]], aoData[b]._aData[aAirSort[i][0]] );
+ if ( iTest !== 0 )
+ {
+ return iTest;
+ }
+ }
+ return 0;
+ } );
+ }
+ }
+
+ /* Alter the sorting classes to take account of the changes */
+ if ( typeof bApplyClasses == 'undefined' || bApplyClasses )
+ {
+ _fnSortingClasses( oSettings );
+ }
+
+ /* Tell the draw function that we have sorted the data */
+ oSettings.bSorted = true;
+
+ /* Copy the master data into the draw array and re-draw */
+ if ( oSettings.oFeatures.bFilter )
+ {
+ /* _fnFilter() will redraw the table for us */
+ _fnFilterComplete( oSettings, oSettings.oPreviousSearch, 1 );
+ }
+ else
+ {
+ oSettings.aiDisplay = oSettings.aiDisplayMaster.slice();
+ oSettings._iDisplayStart = 0; /* reset display back to page 0 */
+ _fnCalculateEnd( oSettings );
+ _fnDraw( oSettings );
+ }
+ }
+
+ /*
+ * Function: _fnSortAttachListener
+ * Purpose: Attach a sort handler (click) to a node
+ * Returns: -
+ * Inputs: object:oSettings - dataTables settings object
+ * node:nNode - node to attach the handler to
+ * int:iDataIndex - column sorting index
+ * function:fnCallback - callback function - optional
+ */
+ function _fnSortAttachListener ( oSettings, nNode, iDataIndex, fnCallback )
+ {
+ $(nNode).click( function (e) {
+ /* If the column is not sortable - don't to anything */
+ if ( oSettings.aoColumns[iDataIndex].bSortable === false )
+ {
+ return;
+ }
+
+ /*
+ * This is a little bit odd I admit... I declare a temporary function inside the scope of
+ * _fnDrawHead and the click handler in order that the code presented here can be used
+ * twice - once for when bProcessing is enabled, and another time for when it is
+ * disabled, as we need to perform slightly different actions.
+ * Basically the issue here is that the Javascript engine in modern browsers don't
+ * appear to allow the rendering engine to update the display while it is still excuting
+ * it's thread (well - it does but only after long intervals). This means that the
+ * 'processing' display doesn't appear for a table sort. To break the js thread up a bit
+ * I force an execution break by using setTimeout - but this breaks the expected
+ * thread continuation for the end-developer's point of view (their code would execute
+ * too early), so we on;y do it when we absolutely have to.
+ */
+ var fnInnerSorting = function () {
+ var iColumn, iNextSort;
+
+ /* If the shift key is pressed then we are multipe column sorting */
+ if ( e.shiftKey )
+ {
+ /* Are we already doing some kind of sort on this column? */
+ var bFound = false;
+ for ( var i=0 ; i<oSettings.aaSorting.length ; i++ )
+ {
+ if ( oSettings.aaSorting[i][0] == iDataIndex )
+ {
+ bFound = true;
+ iColumn = oSettings.aaSorting[i][0];
+ iNextSort = oSettings.aaSorting[i][2]+1;
+
+ if ( typeof oSettings.aoColumns[iColumn].asSorting[iNextSort] == 'undefined' )
+ {
+ /* Reached the end of the sorting options, remove from multi-col sort */
+ oSettings.aaSorting.splice( i, 1 );
+ }
+ else
+ {
+ /* Move onto next sorting direction */
+ oSettings.aaSorting[i][1] = oSettings.aoColumns[iColumn].asSorting[iNextSort];
+ oSettings.aaSorting[i][2] = iNextSort;
+ }
+ break;
+ }
+ }
+
+ /* No sort yet - add it in */
+ if ( bFound === false )
+ {
+ oSettings.aaSorting.push( [ iDataIndex,
+ oSettings.aoColumns[iDataIndex].asSorting[0], 0 ] );
+ }
+ }
+ else
+ {
+ /* If no shift key then single column sort */
+ if ( oSettings.aaSorting.length == 1 && oSettings.aaSorting[0][0] == iDataIndex )
+ {
+ iColumn = oSettings.aaSorting[0][0];
+ iNextSort = oSettings.aaSorting[0][2]+1;
+ if ( typeof oSettings.aoColumns[iColumn].asSorting[iNextSort] == 'undefined' )
+ {
+ iNextSort = 0;
+ }
+ oSettings.aaSorting[0][1] = oSettings.aoColumns[iColumn].asSorting[iNextSort];
+ oSettings.aaSorting[0][2] = iNextSort;
+ }
+ else
+ {
+ oSettings.aaSorting.splice( 0, oSettings.aaSorting.length );
+ oSettings.aaSorting.push( [ iDataIndex,
+ oSettings.aoColumns[iDataIndex].asSorting[0], 0 ] );
+ }
+ }
+
+ /* Run the sort */
+ _fnSort( oSettings );
+ }; /* /fnInnerSorting */
+
+ if ( !oSettings.oFeatures.bProcessing )
+ {
+ fnInnerSorting();
+ }
+ else
+ {
+ _fnProcessingDisplay( oSettings, true );
+ setTimeout( function() {
+ fnInnerSorting();
+ if ( !oSettings.oFeatures.bServerSide )
+ {
+ _fnProcessingDisplay( oSettings, false );
+ }
+ }, 0 );
+ }
+
+ /* Call the user specified callback function - used for async user interaction */
+ if ( typeof fnCallback == 'function' )
+ {
+ fnCallback( oSettings );
+ }
+ } );
+ }
+
+ /*
+ * Function: _fnSortingClasses
+ * Purpose: Set the sortting classes on the header
+ * Returns: -
+ * Inputs: object:oSettings - dataTables settings object
+ * Notes: It is safe to call this function when bSort is false
+ */
+ function _fnSortingClasses( oSettings )
+ {
+ var i, iLen, j, jLen, iFound;
+ var aaSort, sClass;
+ var iColumns = oSettings.aoColumns.length;
+ var oClasses = oSettings.oClasses;
+
+ for ( i=0 ; i<iColumns ; i++ )
+ {
+ if ( oSettings.aoColumns[i].bSortable )
+ {
+ $(oSettings.aoColumns[i].nTh).removeClass( oClasses.sSortAsc +" "+ oClasses.sSortDesc +
+ " "+ oSettings.aoColumns[i].sSortingClass );
+ }
+ }
+
+ if ( oSettings.aaSortingFixed !== null )
+ {
+ aaSort = oSettings.aaSortingFixed.concat( oSettings.aaSorting );
+ }
+ else
+ {
+ aaSort = oSettings.aaSorting.slice();
+ }
+
+ /* Apply the required classes to the header */
+ for ( i=0 ; i<oSettings.aoColumns.length ; i++ )
+ {
+ if ( oSettings.aoColumns[i].bSortable )
+ {
+ sClass = oSettings.aoColumns[i].sSortingClass;
+ iFound = -1;
+ for ( j=0 ; j<aaSort.length ; j++ )
+ {
+ if ( aaSort[j][0] == i )
+ {
+ sClass = ( aaSort[j][1] == "asc" ) ?
+ oClasses.sSortAsc : oClasses.sSortDesc;
+ iFound = j;
+ break;
+ }
+ }
+ $(oSettings.aoColumns[i].nTh).addClass( sClass );
+
+ if ( oSettings.bJUI )
+ {
+ /* jQuery UI uses extra markup */
+ var jqSpan = $("span", oSettings.aoColumns[i].nTh);
+ jqSpan.removeClass(oClasses.sSortJUIAsc +" "+ oClasses.sSortJUIDesc +" "+
+ oClasses.sSortJUI +" "+ oClasses.sSortJUIAscAllowed +" "+ oClasses.sSortJUIDescAllowed );
+
+ var sSpanClass;
+ if ( iFound == -1 )
+ {
+ sSpanClass = oSettings.aoColumns[i].sSortingClassJUI;
+ }
+ else if ( aaSort[iFound][1] == "asc" )
+ {
+ sSpanClass = oClasses.sSortJUIAsc;
+ }
+ else
+ {
+ sSpanClass = oClasses.sSortJUIDesc;
+ }
+
+ jqSpan.addClass( sSpanClass );
+ }
+ }
+ else
+ {
+ /* No sorting on this column, so add the base class. This will have been assigned by
+ * _fnAddColumn
+ */
+ $(oSettings.aoColumns[i].nTh).addClass( oSettings.aoColumns[i].sSortingClass );
+ }
+ }
+
+ /*
+ * Apply the required classes to the table body
+ * Note that this is given as a feature switch since it can significantly slow down a sort
+ * on large data sets (adding and removing of classes is always slow at the best of times..)
+ * Further to this, note that this code is admitadly fairly ugly. It could be made a lot
+ * simpiler using jQuery selectors and add/removeClass, but that is significantly slower
+ * (on the order of 5 times slower) - hence the direct DOM manipulation here.
+ */
+ sClass = oClasses.sSortColumn;
+
+ if ( oSettings.oFeatures.bSort && oSettings.oFeatures.bSortClasses )
+ {
+ var nTds = _fnGetTdNodes( oSettings );
+
+ /* Remove the old classes */
+ if ( nTds.length >= iColumns )
+ {
+ for ( i=0 ; i<iColumns ; i++ )
+ {
+ if ( nTds[i].className.indexOf(sClass+"1") != -1 )
+ {
+ for ( j=0, jLen=(nTds.length/iColumns) ; j<jLen ; j++ )
+ {
+ nTds[(iColumns*j)+i].className =
+ $.trim( nTds[(iColumns*j)+i].className.replace( sClass+"1", "" ) );
+ }
+ }
+ else if ( nTds[i].className.indexOf(sClass+"2") != -1 )
+ {
+ for ( j=0, jLen=(nTds.length/iColumns) ; j<jLen ; j++ )
+ {
+ nTds[(iColumns*j)+i].className =
+ $.trim( nTds[(iColumns*j)+i].className.replace( sClass+"2", "" ) );
+ }
+ }
+ else if ( nTds[i].className.indexOf(sClass+"3") != -1 )
+ {
+ for ( j=0, jLen=(nTds.length/iColumns) ; j<jLen ; j++ )
+ {
+ nTds[(iColumns*j)+i].className =
+ $.trim( nTds[(iColumns*j)+i].className.replace( " "+sClass+"3", "" ) );
+ }
+ }
+ }
+ }
+
+ /* Add the new classes to the table */
+ var iClass = 1, iTargetCol;
+ for ( i=0 ; i<aaSort.length ; i++ )
+ {
+ iTargetCol = parseInt( aaSort[i][0], 10 );
+ for ( j=0, jLen=(nTds.length/iColumns) ; j<jLen ; j++ )
+ {
+ nTds[(iColumns*j)+iTargetCol].className += " "+sClass+iClass;
+ }
+
+ if ( iClass < 3 )
+ {
+ iClass++;
+ }
+ }
+ }
+ }
+
+
+ /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+ * Section - Feature: Pagination. Note that most of the paging logic is done in
+ * _oExt.oPagination
+ */
+
+ /*
+ * Function: _fnFeatureHtmlPaginate
+ * Purpose: Generate the node required for default pagination
+ * Returns: node
+ * Inputs: object:oSettings - dataTables settings object
+ */
+ function _fnFeatureHtmlPaginate ( oSettings )
+ {
+ if ( oSettings.oScroll.bInfinite )
+ {
+ return null;
+ }
+
+ var nPaginate = document.createElement( 'div' );
+ nPaginate.className = oSettings.oClasses.sPaging+oSettings.sPaginationType;
+
+ _oExt.oPagination[ oSettings.sPaginationType ].fnInit( oSettings, nPaginate,
+ function( oSettings ) {
+ _fnCalculateEnd( oSettings );
+ _fnDraw( oSettings );
+ }
+ );
+
+ /* Add a draw callback for the pagination on first instance, to update the paging display */
+ if ( typeof oSettings.aanFeatures.p == "undefined" )
+ {
+ oSettings.aoDrawCallback.push( {
+ "fn": function( oSettings ) {
+ _oExt.oPagination[ oSettings.sPaginationType ].fnUpdate( oSettings, function( oSettings ) {
+ _fnCalculateEnd( oSettings );
+ _fnDraw( oSettings );
+ } );
+ },
+ "sName": "pagination"
+ } );
+ }
+ return nPaginate;
+ }
+
+ /*
+ * Function: _fnPageChange
+ * Purpose: Alter the display settings to change the page
+ * Returns: bool:true - page has changed, false - no change (no effect) eg 'first' on page 1
+ * Inputs: object:oSettings - dataTables settings object
+ * string:sAction - paging action to take: "first", "previous", "next" or "last"
+ */
+ function _fnPageChange ( oSettings, sAction )
+ {
+ var iOldStart = oSettings._iDisplayStart;
+
+ if ( sAction == "first" )
+ {
+ oSettings._iDisplayStart = 0;
+ }
+ else if ( sAction == "previous" )
+ {
+ oSettings._iDisplayStart = oSettings._iDisplayLength>=0 ?
+ oSettings._iDisplayStart - oSettings._iDisplayLength :
+ 0;
+
+ /* Correct for underrun */
+ if ( oSettings._iDisplayStart < 0 )
+ {
+ oSettings._iDisplayStart = 0;
+ }
+ }
+ else if ( sAction == "next" )
+ {
+ if ( oSettings._iDisplayLength >= 0 )
+ {
+ /* Make sure we are not over running the display array */
+ if ( oSettings._iDisplayStart + oSettings._iDisplayLength < oSettings.fnRecordsDisplay() )
+ {
+ oSettings._iDisplayStart += oSettings._iDisplayLength;
+ }
+ }
+ else
+ {
+ oSettings._iDisplayStart = 0;
+ }
+ }
+ else if ( sAction == "last" )
+ {
+ if ( oSettings._iDisplayLength >= 0 )
+ {
+ var iPages = parseInt( (oSettings.fnRecordsDisplay()-1) / oSettings._iDisplayLength, 10 ) + 1;
+ oSettings._iDisplayStart = (iPages-1) * oSettings._iDisplayLength;
+ }
+ else
+ {
+ oSettings._iDisplayStart = 0;
+ }
+ }
+ else
+ {
+ _fnLog( oSettings, 0, "Unknown paging action: "+sAction );
+ }
+
+ return iOldStart != oSettings._iDisplayStart;
+ }
+
+
+ /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+ * Section - Feature: HTML info
+ */
+
+ /*
+ * Function: _fnFeatureHtmlInfo
+ * Purpose: Generate the node required for the info display
+ * Returns: node
+ * Inputs: object:oSettings - dataTables settings object
+ */
+ function _fnFeatureHtmlInfo ( oSettings )
+ {
+ var nInfo = document.createElement( 'div' );
+ nInfo.className = oSettings.oClasses.sInfo;
+
+ /* Actions that are to be taken once only for this feature */
+ if ( typeof oSettings.aanFeatures.i == "undefined" )
+ {
+ /* Add draw callback */
+ oSettings.aoDrawCallback.push( {
+ "fn": _fnUpdateInfo,
+ "sName": "information"
+ } );
+
+ /* Add id */
+ if ( oSettings.sTableId !== '' )
+ {
+ nInfo.setAttribute( 'id', oSettings.sTableId+'_info' );
+ }
+ }
+
+ return nInfo;
+ }
+
+ /*
+ * Function: _fnUpdateInfo
+ * Purpose: Update the information elements in the display
+ * Returns: -
+ * Inputs: object:oSettings - dataTables settings object
+ */
+ function _fnUpdateInfo ( oSettings )
+ {
+ /* Show information about the table */
+ if ( !oSettings.oFeatures.bInfo || oSettings.aanFeatures.i.length === 0 )
+ {
+ return;
+ }
+
+ var
+ iStart = oSettings._iDisplayStart+1, iEnd = oSettings.fnDisplayEnd(),
+ iMax = oSettings.fnRecordsTotal(), iTotal = oSettings.fnRecordsDisplay(),
+ sStart = oSettings.fnFormatNumber( iStart ), sEnd = oSettings.fnFormatNumber( iEnd ),
+ sMax = oSettings.fnFormatNumber( iMax ), sTotal = oSettings.fnFormatNumber( iTotal ),
+ sOut;
+
+ /* When infinite scrolling, we are always starting at 1. _iDisplayStart is used only
+ * internally
+ */
+ if ( oSettings.oScroll.bInfinite )
+ {
+ sStart = oSettings.fnFormatNumber( 1 );
+ }
+
+ if ( oSettings.fnRecordsDisplay() === 0 &&
+ oSettings.fnRecordsDisplay() == oSettings.fnRecordsTotal() )
+ {
+ /* Empty record set */
+ sOut = oSettings.oLanguage.sInfoEmpty+ oSettings.oLanguage.sInfoPostFix;
+ }
+ else if ( oSettings.fnRecordsDisplay() === 0 )
+ {
+ /* Rmpty record set after filtering */
+ sOut = oSettings.oLanguage.sInfoEmpty +' '+
+ oSettings.oLanguage.sInfoFiltered.replace('_MAX_', sMax)+
+ oSettings.oLanguage.sInfoPostFix;
+ }
+ else if ( oSettings.fnRecordsDisplay() == oSettings.fnRecordsTotal() )
+ {
+ /* Normal record set */
+ sOut = oSettings.oLanguage.sInfo.
+ replace('_START_', sStart).
+ replace('_END_', sEnd).
+ replace('_TOTAL_', sTotal)+
+ oSettings.oLanguage.sInfoPostFix;
+ }
+ else
+ {
+ /* Record set after filtering */
+ sOut = oSettings.oLanguage.sInfo.
+ replace('_START_', sStart).
+ replace('_END_', sEnd).
+ replace('_TOTAL_', sTotal) +' '+
+ oSettings.oLanguage.sInfoFiltered.replace('_MAX_',
+ oSettings.fnFormatNumber(oSettings.fnRecordsTotal()))+
+ oSettings.oLanguage.sInfoPostFix;
+ }
+
+ if ( oSettings.oLanguage.fnInfoCallback !== null )
+ {
+ sOut = oSettings.oLanguage.fnInfoCallback( oSettings, iStart, iEnd, iMax, iTotal, sOut );
+ }
+
+ var n = oSettings.aanFeatures.i;
+ for ( var i=0, iLen=n.length ; i<iLen ; i++ )
+ {
+ $(n[i]).html( sOut );
+ }
+ }
+
+
+ /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+ * Section - Feature: Length change
+ */
+
+ /*
+ * Function: _fnFeatureHtmlLength
+ * Purpose: Generate the node required for user display length changing
+ * Returns: node
+ * Inputs: object:oSettings - dataTables settings object
+ */
+ function _fnFeatureHtmlLength ( oSettings )
+ {
+ if ( oSettings.oScroll.bInfinite )
+ {
+ return null;
+ }
+
+ /* This can be overruled by not using the _MENU_ var/macro in the language variable */
+ var sName = (oSettings.sTableId === "") ? "" : 'name="'+oSettings.sTableId+'_length"';
+ var sStdMenu = '<select size="1" '+sName+'>';
+ var i, iLen;
+
+ if ( oSettings.aLengthMenu.length == 2 && typeof oSettings.aLengthMenu[0] == 'object' &&
+ typeof oSettings.aLengthMenu[1] == 'object' )
+ {
+ for ( i=0, iLen=oSettings.aLengthMenu[0].length ; i<iLen ; i++ )
+ {
+ sStdMenu += '<option value="'+oSettings.aLengthMenu[0][i]+'">'+
+ oSettings.aLengthMenu[1][i]+'</option>';
+ }
+ }
+ else
+ {
+ for ( i=0, iLen=oSettings.aLengthMenu.length ; i<iLen ; i++ )
+ {
+ sStdMenu += '<option value="'+oSettings.aLengthMenu[i]+'">'+
+ oSettings.aLengthMenu[i]+'</option>';
+ }
+ }
+ sStdMenu += '</select>';
+
+ var nLength = document.createElement( 'div' );
+ if ( oSettings.sTableId !== '' && typeof oSettings.aanFeatures.l == "undefined" )
+ {
+ nLength.setAttribute( 'id', oSettings.sTableId+'_length' );
+ }
+ nLength.className = oSettings.oClasses.sLength;
+ nLength.innerHTML = oSettings.oLanguage.sLengthMenu.replace( '_MENU_', sStdMenu );
+
+ /*
+ * Set the length to the current display length - thanks to Andrea Pavlovic for this fix,
+ * and Stefan Skopnik for fixing the fix!
+ */
+ $('select option[value="'+oSettings._iDisplayLength+'"]',nLength).attr("selected",true);
+
+ $('select', nLength).change( function(e) {
+ var iVal = $(this).val();
+
+ /* Update all other length options for the new display */
+ var n = oSettings.aanFeatures.l;
+ for ( i=0, iLen=n.length ; i<iLen ; i++ )
+ {
+ if ( n[i] != this.parentNode )
+ {
+ $('select', n[i]).val( iVal );
+ }
+ }
+
+ /* Redraw the table */
+ oSettings._iDisplayLength = parseInt(iVal, 10);
+ _fnCalculateEnd( oSettings );
+
+ /* If we have space to show extra rows (backing up from the end point - then do so */
+ if ( oSettings.fnDisplayEnd() == oSettings.fnRecordsDisplay() )
+ {
+ oSettings._iDisplayStart = oSettings.fnDisplayEnd() - oSettings._iDisplayLength;
+ if ( oSettings._iDisplayStart < 0 )
+ {
+ oSettings._iDisplayStart = 0;
+ }
+ }
+
+ if ( oSettings._iDisplayLength == -1 )
+ {
+ oSettings._iDisplayStart = 0;
+ }
+
+ _fnDraw( oSettings );
+ } );
+
+ return nLength;
+ }
+
+
+ /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+ * Section - Feature: Processing incidator
+ */
+
+ /*
+ * Function: _fnFeatureHtmlProcessing
+ * Purpose: Generate the node required for the processing node
+ * Returns: node
+ * Inputs: object:oSettings - dataTables settings object
+ */
+ function _fnFeatureHtmlProcessing ( oSettings )
+ {
+ var nProcessing = document.createElement( 'div' );
+
+ if ( oSettings.sTableId !== '' && typeof oSettings.aanFeatures.r == "undefined" )
+ {
+ nProcessing.setAttribute( 'id', oSettings.sTableId+'_processing' );
+ }
+ nProcessing.innerHTML = oSettings.oLanguage.sProcessing;
+ nProcessing.className = oSettings.oClasses.sProcessing;
+ oSettings.nTable.parentNode.insertBefore( nProcessing, oSettings.nTable );
+
+ return nProcessing;
+ }
+
+ /*
+ * Function: _fnProcessingDisplay
+ * Purpose: Display or hide the processing indicator
+ * Returns: -
+ * Inputs: object:oSettings - dataTables settings object
+ * bool:
+ * true - show the processing indicator
+ * false - don't show
+ */
+ function _fnProcessingDisplay ( oSettings, bShow )
+ {
+ if ( oSettings.oFeatures.bProcessing )
+ {
+ var an = oSettings.aanFeatures.r;
+ for ( var i=0, iLen=an.length ; i<iLen ; i++ )
+ {
+ an[i].style.visibility = bShow ? "visible" : "hidden";
+ }
+ }
+ }
+
+
+ /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+ * Section - Support functions
+ */
+
+ /*
+ * Function: _fnVisibleToColumnIndex
+ * Purpose: Covert the index of a visible column to the index in the data array (take account
+ * of hidden columns)
+ * Returns: int:i - the data index
+ * Inputs: object:oSettings - dataTables settings object
+ */
+ function _fnVisibleToColumnIndex( oSettings, iMatch )
+ {
+ var iColumn = -1;
+
+ for ( var i=0 ; i<oSettings.aoColumns.length ; i++ )
+ {
+ if ( oSettings.aoColumns[i].bVisible === true )
+ {
+ iColumn++;
+ }
+
+ if ( iColumn == iMatch )
+ {
+ return i;
+ }
+ }
+
+ return null;
+ }
+
+ /*
+ * Function: _fnColumnIndexToVisible
+ * Purpose: Covert the index of an index in the data array and convert it to the visible
+ * column index (take account of hidden columns)
+ * Returns: int:i - the data index
+ * Inputs: object:oSettings - dataTables settings object
+ */
+ function _fnColumnIndexToVisible( oSettings, iMatch )
+ {
+ var iVisible = -1;
+ for ( var i=0 ; i<oSettings.aoColumns.length ; i++ )
+ {
+ if ( oSettings.aoColumns[i].bVisible === true )
+ {
+ iVisible++;
+ }
+
+ if ( i == iMatch )
+ {
+ return oSettings.aoColumns[i].bVisible === true ? iVisible : null;
+ }
+ }
+
+ return null;
+ }
+
+
+ /*
+ * Function: _fnNodeToDataIndex
+ * Purpose: Take a TR element and convert it to an index in aoData
+ * Returns: int:i - index if found, null if not
+ * Inputs: object:s - dataTables settings object
+ * node:n - the TR element to find
+ */
+ function _fnNodeToDataIndex( s, n )
+ {
+ var i, iLen;
+
+ /* Optimisation - see if the nodes which are currently visible match, since that is
+ * the most likely node to be asked for (a selector or event for example)
+ */
+ for ( i=s._iDisplayStart, iLen=s._iDisplayEnd ; i<iLen ; i++ )
+ {
+ if ( s.aoData[ s.aiDisplay[i] ].nTr == n )
+ {
+ return s.aiDisplay[i];
+ }
+ }
+
+ /* Otherwise we are in for a slog through the whole data cache */
+ for ( i=0, iLen=s.aoData.length ; i<iLen ; i++ )
+ {
+ if ( s.aoData[i].nTr == n )
+ {
+ return i;
+ }
+ }
+ return null;
+ }
+
+ /*
+ * Function: _fnVisbleColumns
+ * Purpose: Get the number of visible columns
+ * Returns: int:i - the number of visible columns
+ * Inputs: object:oS - dataTables settings object
+ */
+ function _fnVisbleColumns( oS )
+ {
+ var iVis = 0;
+ for ( var i=0 ; i<oS.aoColumns.length ; i++ )
+ {
+ if ( oS.aoColumns[i].bVisible === true )
+ {
+ iVis++;
+ }
+ }
+ return iVis;
+ }
+
+ /*
+ * Function: _fnCalculateEnd
+ * Purpose: Rcalculate the end point based on the start point
+ * Returns: -
+ * Inputs: object:oSettings - dataTables settings object
+ */
+ function _fnCalculateEnd( oSettings )
+ {
+ if ( oSettings.oFeatures.bPaginate === false )
+ {
+ oSettings._iDisplayEnd = oSettings.aiDisplay.length;
+ }
+ else
+ {
+ /* Set the end point of the display - based on how many elements there are
+ * still to display
+ */
+ if ( oSettings._iDisplayStart + oSettings._iDisplayLength > oSettings.aiDisplay.length ||
+ oSettings._iDisplayLength == -1 )
+ {
+ oSettings._iDisplayEnd = oSettings.aiDisplay.length;
+ }
+ else
+ {
+ oSettings._iDisplayEnd = oSettings._iDisplayStart + oSettings._iDisplayLength;
+ }
+ }
+ }
+
+ /*
+ * Function: _fnConvertToWidth
+ * Purpose: Convert a CSS unit width to pixels (e.g. 2em)
+ * Returns: int:iWidth - width in pixels
+ * Inputs: string:sWidth - width to be converted
+ * node:nParent - parent to get the with for (required for
+ * relative widths) - optional
+ */
+ function _fnConvertToWidth ( sWidth, nParent )
+ {
+ if ( !sWidth || sWidth === null || sWidth === '' )
+ {
+ return 0;
+ }
+
+ if ( typeof nParent == "undefined" )
+ {
+ nParent = document.getElementsByTagName('body')[0];
+ }
+
+ var iWidth;
+ var nTmp = document.createElement( "div" );
+ nTmp.style.width = sWidth;
+
+ nParent.appendChild( nTmp );
+ iWidth = nTmp.offsetWidth;
+ nParent.removeChild( nTmp );
+
+ return ( iWidth );
+ }
+
+ /*
+ * Function: _fnCalculateColumnWidths
+ * Purpose: Calculate the width of columns for the table
+ * Returns: -
+ * Inputs: object:oSettings - dataTables settings object
+ */
+ function _fnCalculateColumnWidths ( oSettings )
+ {
+ var iTableWidth = oSettings.nTable.offsetWidth;
+ var iUserInputs = 0;
+ var iTmpWidth;
+ var iVisibleColumns = 0;
+ var iColums = oSettings.aoColumns.length;
+ var i;
+ var oHeaders = $('th', oSettings.nTHead);
+
+ /* Convert any user input sizes into pixel sizes */
+ for ( i=0 ; i<iColums ; i++ )
+ {
+ if ( oSettings.aoColumns[i].bVisible )
+ {
+ iVisibleColumns++;
+
+ if ( oSettings.aoColumns[i].sWidth !== null )
+ {
+ iTmpWidth = _fnConvertToWidth( oSettings.aoColumns[i].sWidthOrig,
+ oSettings.nTable.parentNode );
+ if ( iTmpWidth !== null )
+ {
+ oSettings.aoColumns[i].sWidth = _fnStringToCss( iTmpWidth );
+ }
+
+ iUserInputs++;
+ }
+ }
+ }
+
+ /* If the number of columns in the DOM equals the number that we have to process in
+ * DataTables, then we can use the offsets that are created by the web-browser. No custom
+ * sizes can be set in order for this to happen, nor scrolling used
+ */
+ if ( iColums == oHeaders.length && iUserInputs === 0 && iVisibleColumns == iColums &&
+ oSettings.oScroll.sX === "" && oSettings.oScroll.sY === "" )
+ {
+ for ( i=0 ; i<oSettings.aoColumns.length ; i++ )
+ {
+ iTmpWidth = $(oHeaders[i]).width();
+ if ( iTmpWidth !== null )
+ {
+ oSettings.aoColumns[i].sWidth = _fnStringToCss( iTmpWidth );
+ }
+ }
+ }
+ else
+ {
+ /* Otherwise we are going to have to do some calculations to get the width of each column.
+ * Construct a 1 row table with the widest node in the data, and any user defined widths,
+ * then insert it into the DOM and allow the browser to do all the hard work of
+ * calculating table widths.
+ */
+ var
+ nCalcTmp = oSettings.nTable.cloneNode( false ),
+ nBody = document.createElement( 'tbody' ),
+ nTr = document.createElement( 'tr' ),
+ nDivSizing;
+
+ nCalcTmp.removeAttribute( "id" );
+ nCalcTmp.appendChild( oSettings.nTHead.cloneNode(true) );
+ if ( oSettings.nTFoot !== null )
+ {
+ nCalcTmp.appendChild( oSettings.nTFoot.cloneNode(true) );
+ _fnApplyToChildren( function(n) {
+ n.style.width = "";
+ }, nCalcTmp.getElementsByTagName('tr') );
+ }
+
+ nCalcTmp.appendChild( nBody );
+ nBody.appendChild( nTr );
+
+ /* Remove any sizing that was previously applied by the styles */
+ var jqColSizing = $('thead th', nCalcTmp);
+ if ( jqColSizing.length === 0 )
+ {
+ jqColSizing = $('tbody tr:eq(0)>td', nCalcTmp);
+ }
+ jqColSizing.each( function (i) {
+ this.style.width = "";
+
+ var iIndex = _fnVisibleToColumnIndex( oSettings, i );
+ if ( iIndex !== null && oSettings.aoColumns[iIndex].sWidthOrig !== "" )
+ {
+ this.style.width = oSettings.aoColumns[iIndex].sWidthOrig;
+ }
+ } );
+
+ /* Find the biggest td for each column and put it into the table */
+ for ( i=0 ; i<iColums ; i++ )
+ {
+ if ( oSettings.aoColumns[i].bVisible )
+ {
+ var nTd = _fnGetWidestNode( oSettings, i );
+ if ( nTd !== null )
+ {
+ nTd = nTd.cloneNode(true);
+ nTr.appendChild( nTd );
+ }
+ }
+ }
+
+ /* Build the table and 'display' it */
+ var nWrapper = oSettings.nTable.parentNode;
+ nWrapper.appendChild( nCalcTmp );
+
+ /* When scrolling (X or Y) we want to set the width of the table as appropriate. However,
+ * when not scrolling leave the table width as it is. This results in slightly different,
+ * but I think correct behaviour
+ */
+ if ( oSettings.oScroll.sX !== "" && oSettings.oScroll.sXInner !== "" )
+ {
+ nCalcTmp.style.width = _fnStringToCss(oSettings.oScroll.sXInner);
+ }
+ else if ( oSettings.oScroll.sX !== "" )
+ {
+ nCalcTmp.style.width = "";
+ if ( $(nCalcTmp).width() < nWrapper.offsetWidth )
+ {
+ nCalcTmp.style.width = _fnStringToCss( nWrapper.offsetWidth );
+ }
+ }
+ else if ( oSettings.oScroll.sY !== "" )
+ {
+ nCalcTmp.style.width = _fnStringToCss( nWrapper.offsetWidth );
+ }
+ nCalcTmp.style.visibility = "hidden";
+
+ /* Scrolling considerations */
+ _fnScrollingWidthAdjust( oSettings, nCalcTmp );
+
+ /* Read the width's calculated by the browser and store them for use by the caller. We
+ * first of all try to use the elements in the body, but it is possible that there are
+ * no elements there, under which circumstances we use the header elements
+ */
+ var oNodes = $("tbody tr:eq(0)>td", nCalcTmp);
+ if ( oNodes.length === 0 )
+ {
+ oNodes = $("thead tr:eq(0)>th", nCalcTmp);
+ }
+
+ var iIndex, iCorrector = 0, iWidth;
+ for ( i=0 ; i<oSettings.aoColumns.length ; i++ )
+ {
+ if ( oSettings.aoColumns[i].bVisible )
+ {
+ iWidth = $(oNodes[iCorrector]).width();
+ if ( iWidth !== null && iWidth > 0 )
+ {
+ oSettings.aoColumns[i].sWidth = _fnStringToCss( iWidth );
+ }
+ iCorrector++;
+ }
+ }
+
+ oSettings.nTable.style.width = _fnStringToCss( $(nCalcTmp).outerWidth() );
+ nCalcTmp.parentNode.removeChild( nCalcTmp );
+ }
+ }
+
+ /*
+ * Function: _fnScrollingWidthAdjust
+ * Purpose: Adjust a table's width to take account of scrolling
+ * Returns: -
+ * Inputs: object:oSettings - dataTables settings object
+ * node:n - table node
+ */
+ function _fnScrollingWidthAdjust ( oSettings, n )
+ {
+ if ( oSettings.oScroll.sX === "" && oSettings.oScroll.sY !== "" )
+ {
+ /* When y-scrolling only, we want to remove the width of the scroll bar so the table
+ * + scroll bar will fit into the area avaialble.
+ */
+ var iOrigWidth = $(n).width();
+ n.style.width = _fnStringToCss( $(n).outerWidth()-oSettings.oScroll.iBarWidth );
+ }
+ else if ( oSettings.oScroll.sX !== "" )
+ {
+ /* When x-scrolling both ways, fix the table at it's current size, without adjusting */
+ n.style.width = _fnStringToCss( $(n).outerWidth() );
+ }
+ }
+
+ /*
+ * Function: _fnGetWidestNode
+ * Purpose: Get the widest node
+ * Returns: string: - max strlens for each column
+ * Inputs: object:oSettings - dataTables settings object
+ * int:iCol - column of interest
+ * boolean:bFast - Should we use fast (but non-accurate) calculation - optional,
+ * default true
+ * Notes: This operation is _expensive_ (!!!). It requires a lot of DOM interaction, but
+ * this is the only way to reliably get the widest string. For example 'mmm' would be wider
+ * than 'iiii' so we can't just ocunt characters. If this can be optimised it would be good
+ * to do so!
+ */
+ function _fnGetWidestNode( oSettings, iCol, bFast )
+ {
+ /* Use fast not non-accurate calculate based on the strlen */
+ if ( typeof bFast == 'undefined' || bFast )
+ {
+ var iMaxLen = _fnGetMaxLenString( oSettings, iCol );
+ var iFastVis = _fnColumnIndexToVisible( oSettings, iCol);
+ if ( iMaxLen < 0 )
+ {
+ return null;
+ }
+ return oSettings.aoData[iMaxLen].nTr.getElementsByTagName('td')[iFastVis];
+ }
+
+ /* Use the slow approach, but get high quality answers - note that this code is not actually
+ * used by DataTables by default. If you want to use it you can alter the call to
+ * _fnGetWidestNode to pass 'false' as the third argument
+ */
+ var
+ iMax = -1, i, iLen,
+ iMaxIndex = -1,
+ n = document.createElement('div');
+
+ n.style.visibility = "hidden";
+ n.style.position = "absolute";
+ document.body.appendChild( n );
+
+ for ( i=0, iLen=oSettings.aoData.length ; i<iLen ; i++ )
+ {
+ n.innerHTML = oSettings.aoData[i]._aData[iCol];
+ if ( n.offsetWidth > iMax )
+ {
+ iMax = n.offsetWidth;
+ iMaxIndex = i;
+ }
+ }
+ document.body.removeChild( n );
+
+ if ( iMaxIndex >= 0 )
+ {
+ var iVis = _fnColumnIndexToVisible( oSettings, iCol);
+ var nRet = oSettings.aoData[iMaxIndex].nTr.getElementsByTagName('td')[iVis];
+ if ( nRet )
+ {
+ return nRet;
+ }
+ }
+ return null;
+ }
+
+ /*
+ * Function: _fnGetMaxLenString
+ * Purpose: Get the maximum strlen for each data column
+ * Returns: string: - max strlens for each column
+ * Inputs: object:oSettings - dataTables settings object
+ * int:iCol - column of interest
+ */
+ function _fnGetMaxLenString( oSettings, iCol )
+ {
+ var iMax = -1;
+ var iMaxIndex = -1;
+
+ for ( var i=0 ; i<oSettings.aoData.length ; i++ )
+ {
+ var s = oSettings.aoData[i]._aData[iCol];
+ if ( s.length > iMax )
+ {
+ iMax = s.length;
+ iMaxIndex = i;
+ }
+ }
+
+ return iMaxIndex;
+ }
+
+ /*
+ * Function: _fnStringToCss
+ * Purpose: Append a CSS unit (only if required) to a string
+ * Returns: 0 if match, 1 if length is different, 2 if no match
+ * Inputs: array:aArray1 - first array
+ * array:aArray2 - second array
+ */
+ function _fnStringToCss( s )
+ {
+ if ( s === null )
+ {
+ return "0px";
+ }
+
+ if ( typeof s == 'number' )
+ {
+ if ( s < 0 )
+ {
+ return "0px";
+ }
+ return s+"px";
+ }
+
+ if ( s.indexOf('em') != -1 || s.indexOf('%') != -1 || s.indexOf('ex') != -1 ||
+ s.indexOf('px') != -1 )
+ {
+ return s;
+ }
+
+ return s+"px";
+ }
+
+ /*
+ * Function: _fnArrayCmp
+ * Purpose: Compare two arrays
+ * Returns: 0 if match, 1 if length is different, 2 if no match
+ * Inputs: array:aArray1 - first array
+ * array:aArray2 - second array
+ */
+ function _fnArrayCmp( aArray1, aArray2 )
+ {
+ if ( aArray1.length != aArray2.length )
+ {
+ return 1;
+ }
+
+ for ( var i=0 ; i<aArray1.length ; i++ )
+ {
+ if ( aArray1[i] != aArray2[i] )
+ {
+ return 2;
+ }
+ }
+
+ return 0;
+ }
+
+ /*
+ * Function: _fnDetectType
+ * Purpose: Get the sort type based on an input string
+ * Returns: string: - type (defaults to 'string' if no type can be detected)
+ * Inputs: string:sData - data we wish to know the type of
+ * Notes: This function makes use of the DataTables plugin objct _oExt
+ * (.aTypes) such that new types can easily be added.
+ */
+ function _fnDetectType( sData )
+ {
+ var aTypes = _oExt.aTypes;
+ var iLen = aTypes.length;
+
+ for ( var i=0 ; i<iLen ; i++ )
+ {
+ var sType = aTypes[i]( sData );
+ if ( sType !== null )
+ {
+ return sType;
+ }
+ }
+
+ return 'string';
+ }
+
+ /*
+ * Function: _fnSettingsFromNode
+ * Purpose: Return the settings object for a particular table
+ * Returns: object: Settings object - or null if not found
+ * Inputs: node:nTable - table we are using as a dataTable
+ */
+ function _fnSettingsFromNode ( nTable )
+ {
+ for ( var i=0 ; i<_aoSettings.length ; i++ )
+ {
+ if ( _aoSettings[i].nTable == nTable )
+ {
+ return _aoSettings[i];
+ }
+ }
+
+ return null;
+ }
+
+ /*
+ * Function: _fnGetDataMaster
+ * Purpose: Return an array with the full table data
+ * Returns: array array:aData - Master data array
+ * Inputs: object:oSettings - dataTables settings object
+ */
+ function _fnGetDataMaster ( oSettings )
+ {
+ var aData = [];
+ var iLen = oSettings.aoData.length;
+ for ( var i=0 ; i<iLen; i++ )
+ {
+ aData.push( oSettings.aoData[i]._aData );
+ }
+ return aData;
+ }
+
+ /*
+ * Function: _fnGetTrNodes
+ * Purpose: Return an array with the TR nodes for the table
+ * Returns: array: - TR array
+ * Inputs: object:oSettings - dataTables settings object
+ */
+ function _fnGetTrNodes ( oSettings )
+ {
+ var aNodes = [];
+ var iLen = oSettings.aoData.length;
+ for ( var i=0 ; i<iLen ; i++ )
+ {
+ aNodes.push( oSettings.aoData[i].nTr );
+ }
+ return aNodes;
+ }
+
+ /*
+ * Function: _fnGetTdNodes
+ * Purpose: Return an array with the TD nodes for the table
+ * Returns: array: - TD array
+ * Inputs: object:oSettings - dataTables settings object
+ */
+ function _fnGetTdNodes ( oSettings )
+ {
+ var nTrs = _fnGetTrNodes( oSettings );
+ var nTds = [], nTd;
+ var anReturn = [];
+ var iCorrector;
+ var iRow, iRows, iColumn, iColumns;
+
+ for ( iRow=0, iRows=nTrs.length ; iRow<iRows ; iRow++ )
+ {
+ nTds = [];
+ for ( iColumn=0, iColumns=nTrs[iRow].childNodes.length ; iColumn<iColumns ; iColumn++ )
+ {
+ nTd = nTrs[iRow].childNodes[iColumn];
+ if ( nTd.nodeName.toUpperCase() == "TD" )
+ {
+ nTds.push( nTd );
+ }
+ }
+
+ iCorrector = 0;
+ for ( iColumn=0, iColumns=oSettings.aoColumns.length ; iColumn<iColumns ; iColumn++ )
+ {
+ if ( oSettings.aoColumns[iColumn].bVisible )
+ {
+ anReturn.push( nTds[iColumn-iCorrector] );
+ }
+ else
+ {
+ anReturn.push( oSettings.aoData[iRow]._anHidden[iColumn] );
+ iCorrector++;
+ }
+ }
+ }
+ return anReturn;
+ }
+
+ /*
+ * Function: _fnEscapeRegex
+ * Purpose: scape a string stuch that it can be used in a regular expression
+ * Returns: string: - escaped string
+ * Inputs: string:sVal - string to escape
+ */
+ function _fnEscapeRegex ( sVal )
+ {
+ var acEscape = [ '/', '.', '*', '+', '?', '|', '(', ')', '[', ']', '{', '}', '\\', '$', '^' ];
+ var reReplace = new RegExp( '(\\' + acEscape.join('|\\') + ')', 'g' );
+ return sVal.replace(reReplace, '\\$1');
+ }
+
+ /*
+ * Function: _fnDeleteIndex
+ * Purpose: Take an array of integers (index array) and remove a target integer (value - not
+ * the key!)
+ * Returns: -
+ * Inputs: a:array int - Index array to target
+ * int:iTarget - value to find
+ */
+ function _fnDeleteIndex( a, iTarget )
+ {
+ var iTargetIndex = -1;
+
+ for ( var i=0, iLen=a.length ; i<iLen ; i++ )
+ {
+ if ( a[i] == iTarget )
+ {
+ iTargetIndex = i;
+ }
+ else if ( a[i] > iTarget )
+ {
+ a[i]--;
+ }
+ }
+
+ if ( iTargetIndex != -1 )
+ {
+ a.splice( iTargetIndex, 1 );
+ }
+ }
+
+ /*
+ * Function: _fnReOrderIndex
+ * Purpose: Figure out how to reorder a display list
+ * Returns: array int:aiReturn - index list for reordering
+ * Inputs: object:oSettings - dataTables settings object
+ */
+ function _fnReOrderIndex ( oSettings, sColumns )
+ {
+ var aColumns = sColumns.split(',');
+ var aiReturn = [];
+
+ for ( var i=0, iLen=oSettings.aoColumns.length ; i<iLen ; i++ )
+ {
+ for ( var j=0 ; j<iLen ; j++ )
+ {
+ if ( oSettings.aoColumns[i].sName == aColumns[j] )
+ {
+ aiReturn.push( j );
+ break;
+ }
+ }
+ }
+
+ return aiReturn;
+ }
+
+ /*
+ * Function: _fnColumnOrdering
+ * Purpose: Get the column ordering that DataTables expects
+ * Returns: string: - comma separated list of names
+ * Inputs: object:oSettings - dataTables settings object
+ */
+ function _fnColumnOrdering ( oSettings )
+ {
+ var sNames = '';
+ for ( var i=0, iLen=oSettings.aoColumns.length ; i<iLen ; i++ )
+ {
+ sNames += oSettings.aoColumns[i].sName+',';
+ }
+ if ( sNames.length == iLen )
+ {
+ return "";
+ }
+ return sNames.slice(0, -1);
+ }
+
+ /*
+ * Function: _fnLog
+ * Purpose: Log an error message
+ * Returns: -
+ * Inputs: int:iLevel - log error messages, or display them to the user
+ * string:sMesg - error message
+ */
+ function _fnLog( oSettings, iLevel, sMesg )
+ {
+ var sAlert = oSettings.sTableId === "" ?
+ "DataTables warning: " +sMesg :
+ "DataTables warning (table id = '"+oSettings.sTableId+"'): " +sMesg;
+
+ if ( iLevel === 0 )
+ {
+ if ( _oExt.sErrMode == 'alert' )
+ {
+ alert( sAlert );
+ }
+ else
+ {
+ throw sAlert;
+ }
+ return;
+ }
+ else if ( typeof console != 'undefined' && typeof console.log != 'undefined' )
+ {
+ console.log( sAlert );
+ }
+ }
+
+ /*
+ * Function: _fnClearTable
+ * Purpose: Nuke the table
+ * Returns: -
+ * Inputs: object:oSettings - dataTables settings object
+ */
+ function _fnClearTable( oSettings )
+ {
+ oSettings.aoData.splice( 0, oSettings.aoData.length );
+ oSettings.aiDisplayMaster.splice( 0, oSettings.aiDisplayMaster.length );
+ oSettings.aiDisplay.splice( 0, oSettings.aiDisplay.length );
+ _fnCalculateEnd( oSettings );
+ }
+
+ /*
+ * Function: _fnSaveState
+ * Purpose: Save the state of a table in a cookie such that the page can be reloaded
+ * Returns: -
+ * Inputs: object:oSettings - dataTables settings object
+ */
+ function _fnSaveState ( oSettings )
+ {
+ if ( !oSettings.oFeatures.bStateSave || typeof oSettings.bDestroying != 'undefined' )
+ {
+ return;
+ }
+
+ /* Store the interesting variables */
+ var i;
+ var sValue = "{";
+ sValue += '"iCreate":'+ new Date().getTime()+',';
+ sValue += '"iStart":'+ oSettings._iDisplayStart+',';
+ sValue += '"iEnd":'+ oSettings._iDisplayEnd+',';
+ sValue += '"iLength":'+ oSettings._iDisplayLength+',';
+ sValue += '"sFilter":"'+ encodeURIComponent(oSettings.oPreviousSearch.sSearch)+'",';
+ sValue += '"sFilterEsc":'+ !oSettings.oPreviousSearch.bRegex+',';
+
+ sValue += '"aaSorting":[ ';
+ for ( i=0 ; i<oSettings.aaSorting.length ; i++ )
+ {
+ sValue += '['+oSettings.aaSorting[i][0]+',"'+oSettings.aaSorting[i][1]+'"],';
+ }
+ sValue = sValue.substring(0, sValue.length-1);
+ sValue += "],";
+
+ sValue += '"aaSearchCols":[ ';
+ for ( i=0 ; i<oSettings.aoPreSearchCols.length ; i++ )
+ {
+ sValue += '["'+encodeURIComponent(oSettings.aoPreSearchCols[i].sSearch)+
+ '",'+!oSettings.aoPreSearchCols[i].bRegex+'],';
+ }
+ sValue = sValue.substring(0, sValue.length-1);
+ sValue += "],";
+
+ sValue += '"abVisCols":[ ';
+ for ( i=0 ; i<oSettings.aoColumns.length ; i++ )
+ {
+ sValue += oSettings.aoColumns[i].bVisible+",";
+ }
+ sValue = sValue.substring(0, sValue.length-1);
+ sValue += "]";
+
+ sValue += "}";
+
+ _fnCreateCookie( oSettings.sCookiePrefix+oSettings.sInstance, sValue,
+ oSettings.iCookieDuration, oSettings.sCookiePrefix, oSettings.fnCookieCallback );
+ }
+
+ /*
+ * Function: _fnLoadState
+ * Purpose: Attempt to load a saved table state from a cookie
+ * Returns: -
+ * Inputs: object:oSettings - dataTables settings object
+ * object:oInit - DataTables init object so we can override settings
+ */
+ function _fnLoadState ( oSettings, oInit )
+ {
+ if ( !oSettings.oFeatures.bStateSave )
+ {
+ return;
+ }
+
+ var oData;
+ var sData = _fnReadCookie( oSettings.sCookiePrefix+oSettings.sInstance );
+ if ( sData !== null && sData !== '' )
+ {
+ /* Try/catch the JSON eval - if it is bad then we ignore it - note that 1.7.0 and before
+ * incorrectly used single quotes for some strings - hence the replace below
+ */
+ try
+ {
+ oData = (typeof $.parseJSON == 'function') ?
+ $.parseJSON( sData.replace(/'/g, '"') ) : eval( '('+sData+')' );
+ }
+ catch( e )
+ {
+ return;
+ }
+
+ /* Restore key features */
+ oSettings._iDisplayStart = oData.iStart;
+ oSettings.iInitDisplayStart = oData.iStart;
+ oSettings._iDisplayEnd = oData.iEnd;
+ oSettings._iDisplayLength = oData.iLength;
+ oSettings.oPreviousSearch.sSearch = decodeURIComponent(oData.sFilter);
+ oSettings.aaSorting = oData.aaSorting.slice();
+ oSettings.saved_aaSorting = oData.aaSorting.slice();
+
+ /*
+ * Search filtering - global reference added in 1.4.1
+ * Note that we use a 'not' for the value of the regular expression indicator to maintain
+ * compatibility with pre 1.7 versions, where this was basically inverted. Added in 1.7.0
+ */
+ if ( typeof oData.sFilterEsc != 'undefined' )
+ {
+ oSettings.oPreviousSearch.bRegex = !oData.sFilterEsc;
+ }
+
+ /* Column filtering - added in 1.5.0 beta 6 */
+ if ( typeof oData.aaSearchCols != 'undefined' )
+ {
+ for ( var i=0 ; i<oData.aaSearchCols.length ; i++ )
+ {
+ oSettings.aoPreSearchCols[i] = {
+ "sSearch": decodeURIComponent(oData.aaSearchCols[i][0]),
+ "bRegex": !oData.aaSearchCols[i][1]
+ };
+ }
+ }
+
+ /* Column visibility state - added in 1.5.0 beta 10 */
+ if ( typeof oData.abVisCols != 'undefined' )
+ {
+ /* Pass back visibiliy settings to the init handler, but to do not here override
+ * the init object that the user might have passed in
+ */
+ oInit.saved_aoColumns = [];
+ for ( i=0 ; i<oData.abVisCols.length ; i++ )
+ {
+ oInit.saved_aoColumns[i] = {};
+ oInit.saved_aoColumns[i].bVisible = oData.abVisCols[i];
+ }
+ }
+ }
+ }
+
+ /*
+ * Function: _fnCreateCookie
+ * Purpose: Create a new cookie with a value to store the state of a table
+ * Returns: -
+ * Inputs: string:sName - name of the cookie to create
+ * string:sValue - the value the cookie should take
+ * int:iSecs - duration of the cookie
+ * string:sBaseName - sName is made up of the base + file name - this is the base
+ * function:fnCallback - User definable function to modify the cookie
+ */
+ function _fnCreateCookie ( sName, sValue, iSecs, sBaseName, fnCallback )
+ {
+ var date = new Date();
+ date.setTime( date.getTime()+(iSecs*1000) );
+
+ /*
+ * Shocking but true - it would appear IE has major issues with having the path not having
+ * a trailing slash on it. We need the cookie to be available based on the path, so we
+ * have to append the file name to the cookie name. Appalling. Thanks to vex for adding the
+ * patch to use at least some of the path
+ */
+ var aParts = window.location.pathname.split('/');
+ var sNameFile = sName + '_' + aParts.pop().replace(/[\/:]/g,"").toLowerCase();
+ var sFullCookie, oData;
+
+ if ( fnCallback !== null )
+ {
+ oData = (typeof $.parseJSON == 'function') ?
+ $.parseJSON( sValue ) : eval( '('+sValue+')' );
+ sFullCookie = fnCallback( sNameFile, oData, date.toGMTString(),
+ aParts.join('/')+"/" );
+ }
+ else
+ {
+ sFullCookie = sNameFile + "=" + encodeURIComponent(sValue) +
+ "; expires=" + date.toGMTString() +"; path=" + aParts.join('/')+"/";
+ }
+
+ /* Are we going to go over the cookie limit of 4KiB? If so, try to delete a cookies
+ * belonging to DataTables. This is FAR from bullet proof
+ */
+ var sOldName="", iOldTime=9999999999999;
+ var iLength = _fnReadCookie( sNameFile )!==null ? document.cookie.length :
+ sFullCookie.length + document.cookie.length;
+
+ if ( iLength+10 > 4096 ) /* Magic 10 for padding */
+ {
+ var aCookies =document.cookie.split(';');
+ for ( var i=0, iLen=aCookies.length ; i<iLen ; i++ )
+ {
+ if ( aCookies[i].indexOf( sBaseName ) != -1 )
+ {
+ /* It's a DataTables cookie, so eval it and check the time stamp */
+ var aSplitCookie = aCookies[i].split('=');
+ try { oData = eval( '('+decodeURIComponent(aSplitCookie[1])+')' ); }
+ catch( e ) { continue; }
+
+ if ( typeof oData.iCreate != 'undefined' && oData.iCreate < iOldTime )
+ {
+ sOldName = aSplitCookie[0];
+ iOldTime = oData.iCreate;
+ }
+ }
+ }
+
+ if ( sOldName !== "" )
+ {
+ document.cookie = sOldName+"=; expires=Thu, 01-Jan-1970 00:00:01 GMT; path="+
+ aParts.join('/') + "/";
+ }
+ }
+
+ document.cookie = sFullCookie;
+ }
+
+ /*
+ * Function: _fnReadCookie
+ * Purpose: Read an old cookie to get a cookie with an old table state
+ * Returns: string: - contents of the cookie - or null if no cookie with that name found
+ * Inputs: string:sName - name of the cookie to read
+ */
+ function _fnReadCookie ( sName )
+ {
+ var
+ aParts = window.location.pathname.split('/'),
+ sNameEQ = sName + '_' + aParts[aParts.length-1].replace(/[\/:]/g,"").toLowerCase() + '=',
+ sCookieContents = document.cookie.split(';');
+
+ for( var i=0 ; i<sCookieContents.length ; i++ )
+ {
+ var c = sCookieContents[i];
+
+ while (c.charAt(0)==' ')
+ {
+ c = c.substring(1,c.length);
+ }
+
+ if (c.indexOf(sNameEQ) === 0)
+ {
+ return decodeURIComponent( c.substring(sNameEQ.length,c.length) );
+ }
+ }
+ return null;
+ }
+
+ /*
+ * Function: _fnGetUniqueThs
+ * Purpose: Get an array of unique th elements, one for each column
+ * Returns: array node:aReturn - list of unique ths
+ * Inputs: node:nThead - The thead element for the table
+ */
+ function _fnGetUniqueThs ( nThead )
+ {
+ var nTrs = nThead.getElementsByTagName('tr');
+
+ /* Nice simple case */
+ if ( nTrs.length == 1 )
+ {
+ return nTrs[0].getElementsByTagName('th');
+ }
+
+ /* Otherwise we need to figure out the layout array to get the nodes */
+ var aLayout = [], aReturn = [];
+ var ROWSPAN = 2, COLSPAN = 3, TDELEM = 4;
+ var i, j, k, iLen, jLen, iColumnShifted;
+ var fnShiftCol = function ( a, i, j ) {
+ while ( typeof a[i][j] != 'undefined' ) {
+ j++;
+ }
+ return j;
+ };
+ var fnAddRow = function ( i ) {
+ if ( typeof aLayout[i] == 'undefined' ) {
+ aLayout[i] = [];
+ }
+ };
+
+ /* Calculate a layout array */
+ for ( i=0, iLen=nTrs.length ; i<iLen ; i++ )
+ {
+ fnAddRow( i );
+ var iColumn = 0;
+ var nTds = [];
+
+ for ( j=0, jLen=nTrs[i].childNodes.length ; j<jLen ; j++ )
+ {
+ if ( nTrs[i].childNodes[j].nodeName.toUpperCase() == "TD" ||
+ nTrs[i].childNodes[j].nodeName.toUpperCase() == "TH" )
+ {
+ nTds.push( nTrs[i].childNodes[j] );
+ }
+ }
+
+ for ( j=0, jLen=nTds.length ; j<jLen ; j++ )
+ {
+ var iColspan = nTds[j].getAttribute('colspan') * 1;
+ var iRowspan = nTds[j].getAttribute('rowspan') * 1;
+
+ if ( !iColspan || iColspan===0 || iColspan===1 )
+ {
+ iColumnShifted = fnShiftCol( aLayout, i, iColumn );
+ aLayout[i][iColumnShifted] = (nTds[j].nodeName.toUpperCase()=="TD") ? TDELEM : nTds[j];
+ if ( iRowspan || iRowspan===0 || iRowspan===1 )
+ {
+ for ( k=1 ; k<iRowspan ; k++ )
+ {
+ fnAddRow( i+k );
+ aLayout[i+k][iColumnShifted] = ROWSPAN;
+ }
+ }
+ iColumn++;
+ }
+ else
+ {
+ iColumnShifted = fnShiftCol( aLayout, i, iColumn );
+ for ( k=0 ; k<iColspan ; k++ )
+ {
+ aLayout[i][iColumnShifted+k] = COLSPAN;
+ }
+ iColumn += iColspan;
+ }
+ }
+ }
+
+ /* Convert the layout array into a node array */
+ for ( i=0, iLen=aLayout.length ; i<iLen ; i++ )
+ {
+ for ( j=0, jLen=aLayout[i].length ; j<jLen ; j++ )
+ {
+ if ( typeof aLayout[i][j] == 'object' )
+ {
+ aReturn[j] = aLayout[i][j];
+ }
+ }
+ }
+
+ return aReturn;
+ }
+
+ /*
+ * Function: _fnScrollBarWidth
+ * Purpose: Get the width of a scroll bar in this browser being used
+ * Returns: int: - width in pixels
+ * Inputs: -
+ * Notes: All credit for this function belongs to Alexandre Gomes. Thanks for sharing!
+ * http://www.alexandre-gomes.com/?p=115
+ */
+ function _fnScrollBarWidth ()
+ {
+ var inner = document.createElement('p');
+ var style = inner.style;
+ style.width = "100%";
+ style.height = "200px";
+
+ var outer = document.createElement('div');
+ style = outer.style;
+ style.position = "absolute";
+ style.top = "0px";
+ style.left = "0px";
+ style.visibility = "hidden";
+ style.width = "200px";
+ style.height = "150px";
+ style.overflow = "hidden";
+ outer.appendChild(inner);
+
+ document.body.appendChild(outer);
+ var w1 = inner.offsetWidth;
+ outer.style.overflow = 'scroll';
+ var w2 = inner.offsetWidth;
+ if ( w1 == w2 )
+ {
+ w2 = outer.clientWidth;
+ }
+
+ document.body.removeChild(outer);
+ return (w1 - w2);
+ }
+
+ /*
+ * Function: _fnApplyToChildren
+ * Purpose: Apply a given function to the display child nodes of an element array (typically
+ * TD children of TR rows
+ * Returns: - (done by reference)
+ * Inputs: function:fn - Method to apply to the objects
+ * array nodes:an1 - List of elements to look through for display children
+ * array nodes:an2 - Another list (identical structure to the first) - optional
+ */
+ function _fnApplyToChildren( fn, an1, an2 )
+ {
+ for ( var i=0, iLen=an1.length ; i<iLen ; i++ )
+ {
+ for ( var j=0, jLen=an1[i].childNodes.length ; j<jLen ; j++ )
+ {
+ if ( an1[i].childNodes[j].nodeType == 1 )
+ {
+ if ( typeof an2 != 'undefined' )
+ {
+ fn( an1[i].childNodes[j], an2[i].childNodes[j] );
+ }
+ else
+ {
+ fn( an1[i].childNodes[j] );
+ }
+ }
+ }
+ }
+ }
+
+ /*
+ * Function: _fnMap
+ * Purpose: See if a property is defined on one object, if so assign it to the other object
+ * Returns: - (done by reference)
+ * Inputs: object:oRet - target object
+ * object:oSrc - source object
+ * string:sName - property
+ * string:sMappedName - name to map too - optional, sName used if not given
+ */
+ function _fnMap( oRet, oSrc, sName, sMappedName )
+ {
+ if ( typeof sMappedName == 'undefined' )
+ {
+ sMappedName = sName;
+ }
+ if ( typeof oSrc[sName] != 'undefined' )
+ {
+ oRet[sMappedName] = oSrc[sName];
+ }
+ }
+
+ /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+ * Section - API
+ *
+ * I'm not overly happy with this solution - I'd much rather that there was a way of getting
+ * a list of all the private functions and do what we need to dynamically - but that doesn't
+ * appear to be possible. Bonkers. A better solution would be to provide a 'bind' type object
+ * To do - bind type method in DTs 2.x.
+ */
+ this.oApi._fnExternApiFunc = _fnExternApiFunc;
+ this.oApi._fnInitalise = _fnInitalise;
+ this.oApi._fnLanguageProcess = _fnLanguageProcess;
+ this.oApi._fnAddColumn = _fnAddColumn;
+ this.oApi._fnColumnOptions = _fnColumnOptions;
+ this.oApi._fnAddData = _fnAddData;
+ this.oApi._fnGatherData = _fnGatherData;
+ this.oApi._fnDrawHead = _fnDrawHead;
+ this.oApi._fnDraw = _fnDraw;
+ this.oApi._fnReDraw = _fnReDraw;
+ this.oApi._fnAjaxUpdate = _fnAjaxUpdate;
+ this.oApi._fnAjaxUpdateDraw = _fnAjaxUpdateDraw;
+ this.oApi._fnAddOptionsHtml = _fnAddOptionsHtml;
+ this.oApi._fnFeatureHtmlTable = _fnFeatureHtmlTable;
+ this.oApi._fnScrollDraw = _fnScrollDraw;
+ this.oApi._fnAjustColumnSizing = _fnAjustColumnSizing;
+ this.oApi._fnFeatureHtmlFilter = _fnFeatureHtmlFilter;
+ this.oApi._fnFilterComplete = _fnFilterComplete;
+ this.oApi._fnFilterCustom = _fnFilterCustom;
+ this.oApi._fnFilterColumn = _fnFilterColumn;
+ this.oApi._fnFilter = _fnFilter;
+ this.oApi._fnBuildSearchArray = _fnBuildSearchArray;
+ this.oApi._fnFilterCreateSearch = _fnFilterCreateSearch;
+ this.oApi._fnDataToSearch = _fnDataToSearch;
+ this.oApi._fnSort = _fnSort;
+ this.oApi._fnSortAttachListener = _fnSortAttachListener;
+ this.oApi._fnSortingClasses = _fnSortingClasses;
+ this.oApi._fnFeatureHtmlPaginate = _fnFeatureHtmlPaginate;
+ this.oApi._fnPageChange = _fnPageChange;
+ this.oApi._fnFeatureHtmlInfo = _fnFeatureHtmlInfo;
+ this.oApi._fnUpdateInfo = _fnUpdateInfo;
+ this.oApi._fnFeatureHtmlLength = _fnFeatureHtmlLength;
+ this.oApi._fnFeatureHtmlProcessing = _fnFeatureHtmlProcessing;
+ this.oApi._fnProcessingDisplay = _fnProcessingDisplay;
+ this.oApi._fnVisibleToColumnIndex = _fnVisibleToColumnIndex;
+ this.oApi._fnColumnIndexToVisible = _fnColumnIndexToVisible;
+ this.oApi._fnNodeToDataIndex = _fnNodeToDataIndex;
+ this.oApi._fnVisbleColumns = _fnVisbleColumns;
+ this.oApi._fnCalculateEnd = _fnCalculateEnd;
+ this.oApi._fnConvertToWidth = _fnConvertToWidth;
+ this.oApi._fnCalculateColumnWidths = _fnCalculateColumnWidths;
+ this.oApi._fnScrollingWidthAdjust = _fnScrollingWidthAdjust;
+ this.oApi._fnGetWidestNode = _fnGetWidestNode;
+ this.oApi._fnGetMaxLenString = _fnGetMaxLenString;
+ this.oApi._fnStringToCss = _fnStringToCss;
+ this.oApi._fnArrayCmp = _fnArrayCmp;
+ this.oApi._fnDetectType = _fnDetectType;
+ this.oApi._fnSettingsFromNode = _fnSettingsFromNode;
+ this.oApi._fnGetDataMaster = _fnGetDataMaster;
+ this.oApi._fnGetTrNodes = _fnGetTrNodes;
+ this.oApi._fnGetTdNodes = _fnGetTdNodes;
+ this.oApi._fnEscapeRegex = _fnEscapeRegex;
+ this.oApi._fnDeleteIndex = _fnDeleteIndex;
+ this.oApi._fnReOrderIndex = _fnReOrderIndex;
+ this.oApi._fnColumnOrdering = _fnColumnOrdering;
+ this.oApi._fnLog = _fnLog;
+ this.oApi._fnClearTable = _fnClearTable;
+ this.oApi._fnSaveState = _fnSaveState;
+ this.oApi._fnLoadState = _fnLoadState;
+ this.oApi._fnCreateCookie = _fnCreateCookie;
+ this.oApi._fnReadCookie = _fnReadCookie;
+ this.oApi._fnGetUniqueThs = _fnGetUniqueThs;
+ this.oApi._fnScrollBarWidth = _fnScrollBarWidth;
+ this.oApi._fnApplyToChildren = _fnApplyToChildren;
+ this.oApi._fnMap = _fnMap;
+
+
+ /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+ * Section - Constructor
+ */
+
+ /* Want to be able to reference "this" inside the this.each function */
+ var _that = this;
+ return this.each(function()
+ {
+ var i=0, iLen, j, jLen, k, kLen;
+
+ /* Check to see if we are re-initalising a table */
+ for ( i=0, iLen=_aoSettings.length ; i<iLen ; i++ )
+ {
+ /* Base check on table node */
+ if ( _aoSettings[i].nTable == this )
+ {
+ if ( typeof oInit == 'undefined' ||
+ ( typeof oInit.bRetrieve != 'undefined' && oInit.bRetrieve === true ) )
+ {
+ return _aoSettings[i].oInstance;
+ }
+ else if ( typeof oInit.bDestroy != 'undefined' && oInit.bDestroy === true )
+ {
+ _aoSettings[i].oInstance.fnDestroy();
+ break;
+ }
+ else
+ {
+ _fnLog( _aoSettings[i], 0, "Cannot reinitialise DataTable.\n\n"+
+ "To retrieve the DataTables object for this table, please pass either no arguments "+
+ "to the dataTable() function, or set bRetrieve to true. Alternatively, to destory "+
+ "the old table and create a new one, set bDestroy to true (note that a lot of "+
+ "changes to the configuration can be made through the API which is usually much "+
+ "faster)." );
+ return;
+ }
+ }
+
+ /* If the element we are initialising has the same ID as a table which was previously
+ * initialised, but the table nodes don't match (from before) then we destory the old
+ * instance by simply deleting it. This is under the assumption that the table has been
+ * destroyed by other methods. Anyone using non-id selectors will need to do this manually
+ */
+ if ( _aoSettings[i].sTableId !== "" && _aoSettings[i].sTableId == this.getAttribute('id') )
+ {
+ _aoSettings.splice( i, 1 );
+ break;
+ }
+ }
+
+ /* Make a complete and independent copy of the settings object */
+ var oSettings = new classSettings();
+ _aoSettings.push( oSettings );
+
+ var bInitHandedOff = false;
+ var bUsePassedData = false;
+
+ /* Set the id */
+ var sId = this.getAttribute( 'id' );
+ if ( sId !== null )
+ {
+ oSettings.sTableId = sId;
+ oSettings.sInstance = sId;
+ }
+ else
+ {
+ oSettings.sInstance = _oExt._oExternConfig.iNextUnique ++;
+ }
+
+ /* Sanity check */
+ if ( this.nodeName.toLowerCase() != 'table' )
+ {
+ _fnLog( oSettings, 0, "Attempted to initialise DataTables on a node which is not a "+
+ "table: "+this.nodeName );
+ return;
+ }
+
+ /* Store 'this' in the settings object for later retrieval */
+ oSettings.oInstance = _that;
+
+ /* Set the table node */
+ oSettings.nTable = this;
+
+ /* Bind the API functions to the settings, so we can perform actions whenever oSettings is
+ * available
+ */
+ oSettings.oApi = _that.oApi;
+
+ /* State the table's width for if a destroy is called at a later time */
+ oSettings.sDestroyWidth = $(this).width();
+
+ /* Store the features that we have available */
+ if ( typeof oInit != 'undefined' && oInit !== null )
+ {
+ oSettings.oInit = oInit;
+ _fnMap( oSettings.oFeatures, oInit, "bPaginate" );
+ _fnMap( oSettings.oFeatures, oInit, "bLengthChange" );
+ _fnMap( oSettings.oFeatures, oInit, "bFilter" );
+ _fnMap( oSettings.oFeatures, oInit, "bSort" );
+ _fnMap( oSettings.oFeatures, oInit, "bInfo" );
+ _fnMap( oSettings.oFeatures, oInit, "bProcessing" );
+ _fnMap( oSettings.oFeatures, oInit, "bAutoWidth" );
+ _fnMap( oSettings.oFeatures, oInit, "bSortClasses" );
+ _fnMap( oSettings.oFeatures, oInit, "bServerSide" );
+ _fnMap( oSettings.oScroll, oInit, "sScrollX", "sX" );
+ _fnMap( oSettings.oScroll, oInit, "sScrollXInner", "sXInner" );
+ _fnMap( oSettings.oScroll, oInit, "sScrollY", "sY" );
+ _fnMap( oSettings.oScroll, oInit, "bScrollCollapse", "bCollapse" );
+ _fnMap( oSettings.oScroll, oInit, "bScrollInfinite", "bInfinite" );
+ _fnMap( oSettings.oScroll, oInit, "iScrollLoadGap", "iLoadGap" );
+ _fnMap( oSettings, oInit, "asStripClasses" );
+ _fnMap( oSettings, oInit, "fnRowCallback" );
+ _fnMap( oSettings, oInit, "fnHeaderCallback" );
+ _fnMap( oSettings, oInit, "fnFooterCallback" );
+ _fnMap( oSettings, oInit, "fnCookieCallback" );
+ _fnMap( oSettings, oInit, "fnInitComplete" );
+ _fnMap( oSettings, oInit, "fnServerData" );
+ _fnMap( oSettings, oInit, "fnFormatNumber" );
+ _fnMap( oSettings, oInit, "aaSorting" );
+ _fnMap( oSettings, oInit, "aaSortingFixed" );
+ _fnMap( oSettings, oInit, "aLengthMenu" );
+ _fnMap( oSettings, oInit, "sPaginationType" );
+ _fnMap( oSettings, oInit, "sAjaxSource" );
+ _fnMap( oSettings, oInit, "iCookieDuration" );
+ _fnMap( oSettings, oInit, "sCookiePrefix" );
+ _fnMap( oSettings, oInit, "sDom" );
+ _fnMap( oSettings, oInit, "oSearch", "oPreviousSearch" );
+ _fnMap( oSettings, oInit, "aoSearchCols", "aoPreSearchCols" );
+ _fnMap( oSettings, oInit, "iDisplayLength", "_iDisplayLength" );
+ _fnMap( oSettings, oInit, "bJQueryUI", "bJUI" );
+ _fnMap( oSettings.oLanguage, oInit, "fnInfoCallback" );
+
+ if ( typeof oInit.fnDrawCallback == 'function' )
+ {
+ /* Add user given callback function to array */
+ oSettings.aoDrawCallback.push( {
+ "fn": oInit.fnDrawCallback,
+ "sName": "user"
+ } );
+ }
+
+ if ( oSettings.oFeatures.bServerSide && oSettings.oFeatures.bSort &&
+ oSettings.oFeatures.bSortClasses )
+ {
+ /* Enable sort classes for server-side processing. Safe to do it here, since server-side
+ * processing must be enabled by the developer
+ */
+ oSettings.aoDrawCallback.push( {
+ "fn": _fnSortingClasses,
+ "sName": "server_side_sort_classes"
+ } );
+ }
+
+ if ( typeof oInit.bJQueryUI != 'undefined' && oInit.bJQueryUI )
+ {
+ /* Use the JUI classes object for display. You could clone the oStdClasses object if
+ * you want to have multiple tables with multiple independent classes
+ */
+ oSettings.oClasses = _oExt.oJUIClasses;
+
+ if ( typeof oInit.sDom == 'undefined' )
+ {
+ /* Set the DOM to use a layout suitable for jQuery UI's theming */
+ oSettings.sDom = '<"H"lfr>t<"F"ip>';
+ }
+ }
+
+ /* Calculate the scroll bar width and cache it for use later on */
+ if ( oSettings.oScroll.sX !== "" || oSettings.oScroll.sY !== "" )
+ {
+ oSettings.oScroll.iBarWidth = _fnScrollBarWidth();
+ }
+
+ if ( typeof oInit.iDisplayStart != 'undefined' &&
+ typeof oSettings.iInitDisplayStart == 'undefined' )
+ {
+ /* Display start point, taking into account the save saving */
+ oSettings.iInitDisplayStart = oInit.iDisplayStart;
+ oSettings._iDisplayStart = oInit.iDisplayStart;
+ }
+
+ /* Must be done after everything which can be overridden by a cookie! */
+ if ( typeof oInit.bStateSave != 'undefined' )
+ {
+ oSettings.oFeatures.bStateSave = oInit.bStateSave;
+ _fnLoadState( oSettings, oInit );
+ oSettings.aoDrawCallback.push( {
+ "fn": _fnSaveState,
+ "sName": "state_save"
+ } );
+ }
+
+ if ( typeof oInit.aaData != 'undefined' )
+ {
+ bUsePassedData = true;
+ }
+
+ /* Backwards compatability */
+ /* aoColumns / aoData - remove at some point... */
+ if ( typeof oInit != 'undefined' && typeof oInit.aoData != 'undefined' )
+ {
+ oInit.aoColumns = oInit.aoData;
+ }
+
+ /* Language definitions */
+ if ( typeof oInit.oLanguage != 'undefined' )
+ {
+ if ( typeof oInit.oLanguage.sUrl != 'undefined' && oInit.oLanguage.sUrl !== "" )
+ {
+ /* Get the language definitions from a file */
+ oSettings.oLanguage.sUrl = oInit.oLanguage.sUrl;
+ $.getJSON( oSettings.oLanguage.sUrl, null, function( json ) {
+ _fnLanguageProcess( oSettings, json, true ); } );
+ bInitHandedOff = true;
+ }
+ else
+ {
+ _fnLanguageProcess( oSettings, oInit.oLanguage, false );
+ }
+ }
+ /* Warning: The _fnLanguageProcess function is async to the remainder of this function due
+ * to the XHR. We use _bInitialised in _fnLanguageProcess() to check this the processing
+ * below is complete. The reason for spliting it like this is optimisation - we can fire
+ * off the XHR (if needed) and then continue processing the data.
+ */
+ }
+ else
+ {
+ /* Create a dummy object for quick manipulation later on. */
+ oInit = {};
+ }
+
+ /*
+ * Stripes
+ * Add the strip classes now that we know which classes to apply - unless overruled
+ */
+ if ( typeof oInit.asStripClasses == 'undefined' )
+ {
+ oSettings.asStripClasses.push( oSettings.oClasses.sStripOdd );
+ oSettings.asStripClasses.push( oSettings.oClasses.sStripEven );
+ }
+
+ /* Remove row stripe classes if they are already on the table row */
+ var bStripeRemove = false;
+ var anRows = $('tbody>tr', this);
+ for ( i=0, iLen=oSettings.asStripClasses.length ; i<iLen ; i++ )
+ {
+ if ( anRows.filter(":lt(2)").hasClass( oSettings.asStripClasses[i]) )
+ {
+ bStripeRemove = true;
+ break;
+ }
+ }
+
+ if ( bStripeRemove )
+ {
+ /* Store the classes which we are about to remove so they can be readded on destory */
+ oSettings.asDestoryStrips = [ '', '' ];
+ if ( $(anRows[0]).hasClass(oSettings.oClasses.sStripOdd) )
+ {
+ oSettings.asDestoryStrips[0] += oSettings.oClasses.sStripOdd+" ";
+ }
+ if ( $(anRows[0]).hasClass(oSettings.oClasses.sStripEven) )
+ {
+ oSettings.asDestoryStrips[0] += oSettings.oClasses.sStripEven;
+ }
+ if ( $(anRows[1]).hasClass(oSettings.oClasses.sStripOdd) )
+ {
+ oSettings.asDestoryStrips[1] += oSettings.oClasses.sStripOdd+" ";
+ }
+ if ( $(anRows[1]).hasClass(oSettings.oClasses.sStripEven) )
+ {
+ oSettings.asDestoryStrips[1] += oSettings.oClasses.sStripEven;
+ }
+
+ anRows.removeClass( oSettings.asStripClasses.join(' ') );
+ }
+
+ /*
+ * Columns
+ * See if we should load columns automatically or use defined ones
+ */
+ var nThead = this.getElementsByTagName('thead');
+ var anThs = nThead.length===0 ? [] : _fnGetUniqueThs( nThead[0] );
+ var aoColumnsInit;
+
+ /* If not given a column array, generate one with nulls */
+ if ( typeof oInit.aoColumns == 'undefined' )
+ {
+ aoColumnsInit = [];
+ for ( i=0, iLen=anThs.length ; i<iLen ; i++ )
+ {
+ aoColumnsInit.push( null );
+ }
+ }
+ else
+ {
+ aoColumnsInit = oInit.aoColumns;
+ }
+
+ /* Add the columns */
+ for ( i=0, iLen=aoColumnsInit.length ; i<iLen ; i++ )
+ {
+ /* Check if we have column visibilty state to restore */
+ if ( typeof oInit.saved_aoColumns != 'undefined' && oInit.saved_aoColumns.length == iLen )
+ {
+ if ( aoColumnsInit[i] === null )
+ {
+ aoColumnsInit[i] = {};
+ }
+ aoColumnsInit[i].bVisible = oInit.saved_aoColumns[i].bVisible;
+ }
+
+ _fnAddColumn( oSettings, anThs ? anThs[i] : null );
+ }
+
+ /* Add options from column definations */
+ if ( typeof oInit.aoColumnDefs != 'undefined' )
+ {
+ /* Loop over the column defs array - loop in reverse so first instace has priority */
+ for ( i=oInit.aoColumnDefs.length-1 ; i>=0 ; i-- )
+ {
+ /* Each column def can target multiple columns, as it is an array */
+ var aTargets = oInit.aoColumnDefs[i].aTargets;
+ for ( j=0, jLen=aTargets.length ; j<jLen ; j++ )
+ {
+ if ( typeof aTargets[j] == 'number' && aTargets[j] >= 0 )
+ {
+ /* 0+ integer, left to right column counting. We add columns which are unknown
+ * automatically. Is this the right behaviour for this? We should at least
+ * log it in future. We cannot do this for the negative or class targets, only here.
+ */
+ while( oSettings.aoColumns.length <= aTargets[j] )
+ {
+ _fnAddColumn( oSettings );
+ }
+ _fnColumnOptions( oSettings, aTargets[j], oInit.aoColumnDefs[i] );
+ }
+ else if ( typeof aTargets[j] == 'number' && aTargets[j] < 0 )
+ {
+ /* Negative integer, right to left column counting */
+ _fnColumnOptions( oSettings, oSettings.aoColumns.length+aTargets[j],
+ oInit.aoColumnDefs[i] );
+ }
+ else if ( typeof aTargets[j] == 'string' )
+ {
+ /* Class name matching on TH element */
+ for ( k=0, kLen=oSettings.aoColumns.length ; k<kLen ; k++ )
+ {
+ if ( aTargets[j] == "_all" ||
+ oSettings.aoColumns[k].nTh.className.indexOf( aTargets[j] ) != -1 )
+ {
+ _fnColumnOptions( oSettings, k, oInit.aoColumnDefs[i] );
+ }
+ }
+ }
+ }
+ }
+ }
+
+ /* Add options from column array - after the defs array so this has priority */
+ if ( typeof aoColumnsInit != 'undefined' )
+ {
+ for ( i=0, iLen=aoColumnsInit.length ; i<iLen ; i++ )
+ {
+ _fnColumnOptions( oSettings, i, aoColumnsInit[i] );
+ }
+ }
+
+ /*
+ * Sorting
+ * Check the aaSorting array
+ */
+ for ( i=0, iLen=oSettings.aaSorting.length ; i<iLen ; i++ )
+ {
+ var oColumn = oSettings.aoColumns[ oSettings.aaSorting[i][0] ];
+
+ /* Add a default sorting index */
+ if ( typeof oSettings.aaSorting[i][2] == 'undefined' )
+ {
+ oSettings.aaSorting[i][2] = 0;
+ }
+
+ /* If aaSorting is not defined, then we use the first indicator in asSorting */
+ if ( typeof oInit.aaSorting == "undefined" &&
+ typeof oSettings.saved_aaSorting == "undefined" )
+ {
+ oSettings.aaSorting[i][1] = oColumn.asSorting[0];
+ }
+
+ /* Set the current sorting index based on aoColumns.asSorting */
+ for ( j=0, jLen=oColumn.asSorting.length ; j<jLen ; j++ )
+ {
+ if ( oSettings.aaSorting[i][1] == oColumn.asSorting[j] )
+ {
+ oSettings.aaSorting[i][2] = j;
+ break;
+ }
+ }
+ }
+
+ /*
+ * Final init
+ * Sanity check that there is a thead and tbody. If not let's just create them
+ */
+ if ( this.getElementsByTagName('thead').length === 0 )
+ {
+ this.appendChild( document.createElement( 'thead' ) );
+ }
+
+ if ( this.getElementsByTagName('tbody').length === 0 )
+ {
+ this.appendChild( document.createElement( 'tbody' ) );
+ }
+
+ oSettings.nTHead = this.getElementsByTagName('thead')[0];
+ oSettings.nTBody = this.getElementsByTagName('tbody')[0];
+ if ( this.getElementsByTagName('tfoot').length > 0 )
+ {
+ oSettings.nTFoot = this.getElementsByTagName('tfoot')[0];
+ }
+
+ /* Check if there is data passing into the constructor */
+ if ( bUsePassedData )
+ {
+ for ( i=0 ; i<oInit.aaData.length ; i++ )
+ {
+ _fnAddData( oSettings, oInit.aaData[ i ] );
+ }
+ }
+ else
+ {
+ /* Grab the data from the page */
+ _fnGatherData( oSettings );
+ }
+
+ /* Copy the data index array */
+ oSettings.aiDisplay = oSettings.aiDisplayMaster.slice();
+
+ /* Initialisation complete - table can be drawn */
+ oSettings.bInitialised = true;
+
+ /* Check if we need to initialise the table (it might not have been handed off to the
+ * language processor)
+ */
+ if ( bInitHandedOff === false )
+ {
+ _fnInitalise( oSettings );
+ }
+ });
+ };
+})(jQuery, window, document);
diff --git a/load.php b/load.php
new file mode 100644
index 0000000000..76ed104fe1
--- /dev/null
+++ b/load.php
@@ -0,0 +1,120 @@
+<?php
+//
+// Callback function for inline editing.
+//
+// webtrees: Web based Family History software
+// Copyright (C) 2010 webtrees development team.
+//
+// This program is free software; you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation; either version 2 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program; if not, write to the Free Software
+// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+//
+// @version $Id$
+
+define('WT_SCRIPT_NAME', 'load.php');
+require './includes/session.php';
+require_once WT_ROOT.'includes/functions/functions_edit.php';
+
+// Header for an AJAX response. There is no error/return value.
+// Invalid requests just get an empty response - same as no data.
+header('Content-type: text/html; charset=UTF-8');
+
+// Which data set are we requesting?
+$src=safe_GET('src');
+
+switch ($src) {
+case 'user_list':
+ // callback for administration/user_list.php
+ if (!WT_USER_IS_ADMIN) {
+ exit;
+ }
+ $sSearch=safe_GET('sSearch');
+ if ($sSearch) {
+ $WHERE=
+ " WHERE".
+ " u.user_id>0 AND (".
+ " real_name LIKE CONCAT('%', " . WT_DB::quote($sSearch) . ", '%') OR " .
+ " user_name LIKE CONCAT('%', " . WT_DB::quote($sSearch) . ", '%') OR " .
+ " email LIKE CONCAT('%', " . WT_DB::quote($sSearch) . ", '%'))";
+ } else {
+ $WHERE=" WHERE u.user_id>0";
+ }
+ $iDisplayStart =safe_GET('iDisplayStart', WT_REGEX_INTEGER);
+ $iDisplayLength=safe_GET('iDisplayLength', WT_REGEX_INTEGER);
+ if ($iDisplayLength>0) {
+ $LIMIT=" LIMIT " . $iDisplayStart . ',' . $iDisplayLength;
+ } else {
+ $LIMIT="";
+ }
+ $iSortingCols =safe_GET('iSortingCols', WT_REGEX_INTEGER);
+ if ($iSortingCols) {
+ $ORDER_BY=' ORDER BY ';
+ for ($i=0; $i<$iSortingCols; ++$i) {
+ // Datatables numbers columns 0, 1, 2, ...
+ // MySQL numbers columns 1, 2, 3, ...
+ $ORDER_BY.=(1+safe_GET('iSortCol_'.$i, WT_REGEX_INTEGER)).' '.safe_GET('sSortDir_'.$i, 'asc|desc').' ';
+ if ($i<$iSortingCols-1) {
+ $ORDER_BY.=',';
+ }
+ }
+ } else {
+ $ORDER_BY='';
+ }
+
+ // This becomes a JSON list, not array, so need to fetch with numeric keys.
+ $aaData=WT_DB::prepare(
+ "SELECT SQL_CALC_FOUND_ROWS u.user_id AS user_id, real_name, user_name, email,".
+ " us1.setting_value AS language,".
+ " FROM_UNIXTIME(us2.setting_value) AS reg_timestamp,".
+ " FROM_UNIXTIME(us3.setting_value) AS sessiontime,".
+ " us4.setting_value AS verified,".
+ " us5.setting_value AS verified_by_admin".
+ " FROM `##user` u".
+ " LEFT JOIN `##user_setting` us1 ON (u.user_id=us1.user_id AND us1.setting_name='language')".
+ " LEFT JOIN `##user_setting` us2 ON (u.user_id=us2.user_id AND us2.setting_name='reg_timestamp')".
+ " LEFT JOIN `##user_setting` us3 ON (u.user_id=us3.user_id AND us3.setting_name='sessiontime')".
+ " LEFT JOIN `##user_setting` us4 ON (u.user_id=us4.user_id AND us4.setting_name='verified')".
+ " LEFT JOIN `##user_setting` us5 ON (u.user_id=us5.user_id AND us5.setting_name='verified_by_admin')".
+ $WHERE.
+ $ORDER_BY.
+ $LIMIT
+ )->execute()->fetchAll(PDO::FETCH_NUM);
+
+ // Reformat various columns for display
+ foreach ($aaData as &$row) {
+ $row[1]=edit_field_inline('user-real_name-'.$row[0], $row[1]);
+ $row[2]=edit_field_inline('user-user_name-'.$row[0], $row[2]);
+ $row[3]=edit_field_inline('user-email-'. $row[0], $row[3]);
+ $row[4]=edit_field_language_inline('user_setting-language-'. $row[0], $row[4]);
+ if ($row[0]==WT_USER_ID) {
+ $row[7]=$row[7] ? i18n::translate('yes') : i18n::translate('no');
+ $row[8]=$row[7] ? i18n::translate('yes') : i18n::translate('no');
+ } else {
+ // Cannot approve/unapprove your own account
+ $row[7]=edit_field_yes_no_inline('user_setting-verified-'. $row[0], $row[7]);
+ $row[8]=edit_field_yes_no_inline('user_setting-verified_by_admin-'.$row[0], $row[8]);
+ }
+ }
+
+ // Total filtered/unfiltered rows
+ $iTotalDisplayRecords=WT_DB::prepare("SELECT FOUND_ROWS()")->fetchOne();
+ $iTotalRecords=WT_DB::prepare("SELECT COUNT(*) FROM `##user` WHERE user_id>0")->fetchOne();
+
+ echo json_encode(array(
+ 'sEcho' =>(int)safe_GET('sEcho'), // See http://www.datatables.net/usage/server-side
+ 'iTotalRecords' =>$iTotalRecords,
+ 'iTotalDisplayRecords'=>$iTotalDisplayRecords,
+ 'aaData' =>$aaData
+ ));
+ exit;
+}
diff --git a/module_admin.php b/module_admin.php
deleted file mode 100644
index 1798d94be9..0000000000
--- a/module_admin.php
+++ /dev/null
@@ -1,682 +0,0 @@
-<?php
-/**
- * Module Administration User Interface.
- *
- * webtrees: Web based Family History software
- * Copyright (C) 2010 webtrees development team.
- *
- * Derived from PhpGedView
- * Copyright (C) 2002 to 2009 PGV Development Team
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; if not, write to the Free Software
- * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
- *
- * @package webtrees
- * @subpackage Module
- * @version $Id$
- */
-
-define('WT_SCRIPT_NAME', 'module_admin.php');
-
-require 'includes/session.php';
-require WT_ROOT.'includes/functions/functions_edit.php';
-
-if (!WT_USER_GEDCOM_ADMIN) {
- header('Location: '.WT_SERVER_NAME.WT_SCRIPT_PATH.'login.php?url='.WT_SCRIPT_NAME);
- exit;
-}
-
-// Modules may have been added or updated to no longer provide a particular component
-$installed_modules=WT_Module::getInstalledModules();
-foreach ($installed_modules as $module_name=>$module) {
- // New module
- WT_DB::prepare("INSERT IGNORE INTO `##module` (module_name) VALUES (?)")->execute(array($module_name));
-
- // Removed component
- if (!$module instanceof WT_Module_Block) {
- WT_DB::prepare(
- "DELETE FROM `##module_privacy` WHERE module_name=? AND component='block'"
- )->execute(array($module_name));
- WT_DB::prepare(
- "DELETE `##block_setting` FROM `##block_setting` JOIN `##block` USING (block_id) WHERE module_name=?"
- )->execute(array($module_name));
- WT_DB::prepare(
- "DELETE FROM `##block` WHERE module_name=?"
- )->execute(array($module_name));
- }
- if (!$module instanceof WT_Module_Chart) {
- WT_DB::prepare(
- "DELETE FROM `##module_privacy` WHERE module_name=? AND component='chart'"
- )->execute(array($module_name));
- }
- if (!$module instanceof WT_Module_Menu) {
- WT_DB::prepare(
- "DELETE FROM `##module_privacy` WHERE module_name=? AND component='menu'"
- )->execute(array($module_name));
- WT_DB::prepare(
- "UPDATE `##module` SET menu_order=NULL WHERE module_name=?"
- )->execute(array($module_name));
- }
- if (!$module instanceof WT_Module_Report) {
- WT_DB::prepare(
- "DELETE FROM `##module_privacy` WHERE module_name=? AND component='report'"
- )->execute(array($module_name));
- }
- if (!$module instanceof WT_Module_Sidebar) {
- WT_DB::prepare(
- "DELETE FROM `##module_privacy` WHERE module_name=? AND component='sidebar'"
- )->execute(array($module_name));
- WT_DB::prepare(
- "UPDATE `##module` SET sidebar_order=NULL WHERE module_name=?"
- )->execute(array($module_name));
- }
- if (!$module instanceof WT_Module_Tab) {
- WT_DB::prepare(
- "DELETE FROM `##module_privacy` WHERE module_name=? AND component='tab'"
- )->execute(array($module_name));
- WT_DB::prepare(
- "UPDATE `##module` SET tab_order=NULL WHERE module_name=?"
- )->execute(array($module_name));
- }
- if (!$module instanceof WT_Module_Theme) {
- WT_DB::prepare(
- "DELETE FROM `##module_privacy` WHERE module_name=? AND component='theme'"
- )->execute(array($module_name));
- }
-}
-
-// Delete config for modules that no longer exist
-$module_names=WT_DB::prepare("SELECT module_name FROM `##module`")->fetchOneColumn();
-foreach ($module_names as $module_name) {
- if (!array_key_exists($module_name, $installed_modules)) {
- WT_DB::prepare(
- "DELETE FROM `##module_privacy` WHERE module_name=?"
- )->execute(array($module_name));
- WT_DB::prepare(
- "DELETE `##block_setting` FROM `##block_setting` JOIN `##block` USING (block_id) WHERE module_name=?"
- )->execute(array($module_name));
- WT_DB::prepare(
- "DELETE FROM `##block` WHERE module_name=?"
- )->execute(array($module_name));
- WT_DB::prepare(
- "DELETE FROM `##module` WHERE module_name=?"
- )->execute(array($module_name));
- }
-}
-
-$action = safe_POST('action');
-
-if ($action=='update_mods') {
- foreach (WT_Module::getInstalledModules() as $module) {
- $module_name=$module->getName();
- $status=safe_POST("status-{$module_name}");
- if ($status!==null) {
- WT_DB::prepare("UPDATE `##module` SET status=? WHERE module_name=?")->execute(array($status ? 'enabled' : 'disabled', $module_name));
- }
- foreach (get_all_gedcoms() as $ged_id=>$ged_name) {
- if ($module instanceof WT_Module_Block) {
- $value = safe_POST("blockaccess-{$module_name}-{$ged_id}", WT_REGEX_INTEGER, $module->defaultAccessLevel());
- WT_DB::prepare(
- "REPLACE INTO `##module_privacy` (module_name, gedcom_id, component, access_level) VALUES (?, ?, 'block', ?)"
- )->execute(array($module_name, $ged_id, $value));
- }
-
- if ($module instanceof WT_Module_Chart) {
- $value = safe_POST("chartaccess-{$module_name}-{$ged_id}", WT_REGEX_INTEGER, $module->defaultAccessLevel());
- WT_DB::prepare(
- "REPLACE INTO `##module_privacy` (module_name, gedcom_id, component, access_level) VALUES (?, ?, 'chart', ?)"
- )->execute(array($module_name, $ged_id, $value));
- }
-
- if ($module instanceof WT_Module_Menu) {
- $value = safe_POST("menuaccess-{$module_name}-{$ged_id}", WT_REGEX_INTEGER, $module->defaultAccessLevel());
- WT_DB::prepare(
- "REPLACE INTO `##module_privacy` (module_name, gedcom_id, component, access_level) VALUES (?, ?, 'menu', ?)"
- )->execute(array($module_name, $ged_id, $value));
- }
-
- if ($module instanceof WT_Module_Sidebar) {
- $value = safe_POST("sidebaraccess-{$module_name}-{$ged_id}", WT_REGEX_INTEGER, $module->defaultAccessLevel());
- WT_DB::prepare(
- "REPLACE INTO `##module_privacy` (module_name, gedcom_id, component, access_level) VALUES (?, ?, 'sidebar', ?)"
- )->execute(array($module_name, $ged_id, $value));
- }
-
- if ($module instanceof WT_Module_Report) {
- $value = safe_POST("reportaccess-{$module_name}-{$ged_id}", WT_REGEX_INTEGER, $module->defaultAccessLevel());
- WT_DB::prepare(
- "REPLACE INTO `##module_privacy` (module_name, gedcom_id, component, access_level) VALUES (?, ?, 'report', ?)"
- )->execute(array($module_name, $ged_id, $value));
- }
-
- if ($module instanceof WT_Module_Tab) {
- $value = safe_POST("tabaccess-{$module_name}-{$ged_id}", WT_REGEX_INTEGER, $module->defaultAccessLevel());
- WT_DB::prepare(
- "REPLACE INTO `##module_privacy` (module_name, gedcom_id, component, access_level) VALUES (?, ?, 'tab', ?)"
- )->execute(array($module_name, $ged_id, $value));
- }
-
- if ($module instanceof WT_Module_Theme) {
- $value = safe_POST("themeaccess-{$module_name}-{$ged_id}", WT_REGEX_INTEGER, $module->defaultAccessLevel());
- WT_DB::prepare(
- "REPLACE INTO `##module_privacy` (module_name, gedcom_id, component, access_level) VALUES (?, ?, 'theme', ?)"
- )->execute(array($module_name, $ged_id, $value));
- }
- }
-
- $value = safe_POST('menuorder-'.$module_name);
- if ($value) {
- WT_DB::prepare(
- "UPDATE `##module` SET menu_order=? WHERE module_name=?"
- )->execute(array($value, $module_name));
- }
-
- $value = safe_POST('taborder-'.$module_name);
- if ($value) {
- WT_DB::prepare(
- "UPDATE `##module` SET tab_order=? WHERE module_name=?"
- )->execute(array($value, $module_name));
- }
-
- $value = safe_POST('sidebarorder-'.$module_name);
- if ($value) {
- WT_DB::prepare(
- "UPDATE `##module` SET sidebar_order=? WHERE module_name=?"
- )->execute(array($value, $module_name));
- }
- }
-}
-
-print_header(i18n::translate('Module administration'));
-?>
-<style type="text/css">
-<!--
-.sortme {
- cursor: move;
-}
-.sortme img {
- cursor: pointer;
-}
-//-->
-</style>
-<script type="text/javascript">
-//<![CDATA[
-
- function reindexMods(id) {
- jQuery('#'+id+' input').each(
- function (index, value) {
- value.value = index+1;
- });
- }
-
- jQuery(document).ready(function() {
- //-- tabs
- jQuery("#tabs").tabs();
-
- //-- sortable menus and tabs tables
- jQuery("#menus_table, #tabs_table, #sidebars_table").sortable({items: '.sortme', forceHelperSize: true, forcePlaceholderSize: true, opacity: 0.7, cursor: 'move', axis: 'y'});
-
- //-- update the order numbers after drag-n-drop sorting is complete
- jQuery('#menus_table').bind('sortupdate', function(event, ui) {
- var id = jQuery(this).attr('id');
- reindexMods(id);
- });
-
- jQuery('#tabs_table').bind('sortupdate', function(event, ui) {
- var id = jQuery(this).attr('id');
- reindexMods(id);
- });
-
- jQuery('#sidebars_table').bind('sortupdate', function(event, ui) {
- var id = jQuery(this).attr('id');
- reindexMods(id);
- });
-
- //-- enable the arrows buttons
- jQuery(".uarrow").click(function() {
- var curr = jQuery(this).parent().parent().get(0);
- var prev = jQuery(curr).prev();
- if (prev) jQuery(prev).insertAfter(curr);
- reindexMods('menus_table');
- reindexMods('tabs_table');
- reindexMods('sidebars_table');
- });
-
- jQuery(".udarrow").click(function() {
- var curr = jQuery(this).parent().parent().get(0);
- var prev = jQuery(curr).parent().children().get(0);
- if (prev) jQuery(curr).insertBefore(prev);
- reindexMods('menus_table');
- reindexMods('tabs_table');
- reindexMods('sidebars_table');
- });
-
- jQuery(".darrow").click(function() {
- var curr = jQuery(this).parent().parent().get(0);
- var next = jQuery(curr).next();
- if (next) jQuery(next).insertBefore(curr);
- reindexMods('menus_table');
- reindexMods('tabs_table');
- reindexMods('sidebars_table');
- });
-
- jQuery(".ddarrow").click(function() {
- var curr = jQuery(this).parent().parent().get(0);
- var prev = jQuery(curr).parent().children(":last").get(0);
- if (prev) jQuery(curr).insertAfter(prev);
- reindexMods('menus_table');
- reindexMods('tabs_table');
- reindexMods('sidebars_table');
- });
-
- // Table sorting and pageing
- jQuery("#installed_table")
- .tablesorter({
- sortList: [[2,0], [3,0]], widgets: ['zebra'],
- headers: { 0: { sorter: false }}
- })
- .tablesorterPager({
- container: jQuery("#pager"),
- positionFixed: false,
- size: 15
- });
-
-});
-//]]>
-</script>
-
-<div align="center">
- <p><?php echo "<h2>".i18n::translate('Module administration')."</h2>"; ?></p>
- <p><?php echo i18n::translate('Below is the list of all the modules installed in this instance of webtrees. Modules are installed by placing them in the <i>modules</i> directory. Here you can set the access level per GEDCOM for each module. If a module includes tabs for the individual page or menus for the menu bar, you can also set the access level and order of each of them.'); ?></p>
- <p><input TYPE="button" VALUE="<?php echo i18n::translate('Return to Administration page'); ?>" onclick="javascript:window.location='admin.php'" /></p>
- <div id="tabs">
- <form method="post" action="module_admin.php">
- <input type="hidden" name="action" value="update_mods" />
- <!-- page tabs -->
- <ul>
- <li><a href="#installed_tab"><span><?php echo i18n::translate('All modules'); ?></span></a></li>
- <li><a href="#menus_tab"><span><?php echo i18n::translate('Menus'); ?></span></a></li>
- <li><a href="#tabs_tab"><span><?php echo i18n::translate('Tabs'); ?></span></a></li>
- <li><a href="#sidebars_tab"><span><?php echo i18n::translate('Sidebar'); ?></span></a></li>
- <li><a href="#blocks_tab"><span><?php echo i18n::translate('Blocks'); ?></span></a></li>
- <li><a href="#charts_tab"><span><?php echo i18n::translate('Charts'); ?></span></a></li>
- <li><a href="#reports_tab"><span><?php echo i18n::translate('Reports'); ?></span></a></li>
- <li><a href="#themes_tab"><span><?php echo i18n::translate('Themes'); ?></span></a></li>
- </ul>
- <!-- installed -->
- <div id="installed_tab">
- <table id="installed_table" class="tablesorter" border="0" cellpadding="0" cellspacing="1">
- <thead>
- <tr>
- <th><?php echo i18n::translate('Enabled'); ?></th>
- <th><?php echo i18n::translate('Configuration'); ?></th>
- <th><?php echo i18n::translate('Module Name'); ?></th>
- <th><?php echo i18n::translate('Description'); ?></th>
- <th><?php echo i18n::translate('Menu'); ?></th>
- <th><?php echo i18n::translate('Tab'); ?></th>
- <th><?php echo i18n::translate('Sidebar'); ?></th>
- <th><?php echo i18n::translate('Block'); ?></th>
- <th><?php echo i18n::translate('Chart'); ?></th>
- <th><?php echo i18n::translate('Report'); ?></th>
- <th><?php echo i18n::translate('Theme'); ?></th>
- </tr>
- </thead>
- <tbody>
- <?php
- foreach (WT_Module::getInstalledModules() as $module) {
- $status=WT_DB::prepare(
- "SELECT status FROM `##module` WHERE module_name=?"
- )->execute(array($module->getName()))->fetchOne();
- echo '<tr><td>', two_state_checkbox('status-'.$module->getName(), $status=='enabled'), '</td><td>';
- if ($module instanceof WT_Module_Config) echo '<a href="', $module->getConfigLink(), '"><img class="adminicon" src="', $WT_IMAGES["admin"], '" border="0" alt="', $module->getName(), '" /></a>'; ?></td>
- <td><?php echo $module->getTitle(); ?></td>
- <td><?php echo $module->getDescription(); ?></td>
- <td><?php if ($module instanceof WT_Module_Menu) echo i18n::translate('Yes'); else echo i18n::translate('No'); ?></td>
- <td><?php if ($module instanceof WT_Module_Tab) echo i18n::translate('Yes'); else echo i18n::translate('No'); ?></td>
- <td><?php if ($module instanceof WT_Module_Sidebar) echo i18n::translate('Yes'); else echo i18n::translate('No'); ?></td>
- <td><?php if ($module instanceof WT_Module_Block) echo i18n::translate('Yes'); else echo i18n::translate('No'); ?></td>
- <td><?php if ($module instanceof WT_Module_Chart) echo i18n::translate('Yes'); else echo i18n::translate('No'); ?></td>
- <td><?php if ($module instanceof WT_Module_Report) echo i18n::translate('Yes'); else echo i18n::translate('No'); ?></td>
- <td><?php if ($module instanceof WT_Module_Theme) echo i18n::translate('Yes'); else echo i18n::translate('No'); ?></td>
- </tr>
- <?php
- }
- ?>
- </tbody>
- </table>
- <div id="pager" class="pager">
- <!--<form>-->
- <img src="<?php echo WT_THEME_DIR; ?>images/jquery/first.png" class="first"/>
- <img src="<?php echo WT_THEME_DIR; ?>images/jquery/prev.png" class="prev"/>
- <input type="text" class="pagedisplay"/>
- <img src="<?php echo WT_THEME_DIR; ?>images/jquery/next.png" class="next"/>
- <img src="<?php echo WT_THEME_DIR; ?>images/jquery/last.png" class="last"/>
- <select class="pagesize">
- <option value="10">10</option>
- <option selected="selected" value="15">15</option>
- <option value="30">30</option>
- <option value="40">40</option>
- <option value="50">50</option>
- <option value="100">100</option>
- </select>
- <!--</form>-->
- </div>
- </div>
- <!-- menus -->
- <div id="menus_tab">
- <table id="menus_table" class="list_table">
- <thead>
- <tr>
- <th class="list_label"><?php echo i18n::translate('Module Name'); ?></th>
- <th class="list_label"><?php echo i18n::translate('Order'); ?></th>
- <th class="list_label"><?php echo i18n::translate('Access level'); ?></th>
- </tr>
- </thead>
- <tbody>
- <?php
- $order = 1;
- foreach (WT_Module::getInstalledMenus() as $module) { ?>
- <tr class="sortme">
- <td class="list_value"><?php echo $module->getTitle(); ?></td>
- <td class="list_value"><input type="text" size="5" value="<?php echo $order; ?>" name="menuorder-<?php echo $module->getName(); ?>" />
- <img class="uarrow" style="vertical-align:bottom;" src="<?php echo $WT_IMAGES["uarrow"]; ?>" border="0" title="<?php echo i18n::translate('Move up'); ?>" />
- <img class="udarrow" style="vertical-align:bottom;" src="<?php echo $WT_IMAGES["udarrow"]; ?>" border="0" title="<?php echo i18n::translate('Move to top'); ?>" />
- <img class="darrow" style="vertical-align:bottom;" src="<?php echo $WT_IMAGES["darrow"]; ?>" border="0" title="<?php echo i18n::translate('Move down'); ?>" />
- <img class="ddarrow" style="vertical-align:bottom;" src="<?php echo $WT_IMAGES["ddarrow"]; ?>" border="0" title="<?php echo i18n::translate('Move to bottom'); ?>" />
- </td>
- <td class="list_value_wrap">
- <table>
- <?php
- foreach (get_all_gedcoms() as $ged_id=>$ged_name) {
- $varname = 'menuaccess-'.$module->getName().'-'.$ged_id;
- $access_level=WT_DB::prepare(
- "SELECT access_level FROM `##module_privacy` WHERE gedcom_id=? AND module_name=? AND component='menu'"
- )->execute(array($ged_id, $module->getName()))->fetchOne();
- if ($access_level===null) {
- $access_level=$module->defaultAccessLevel();
- }
- echo '<tr><td>', htmlspecialchars($ged_name), '</td><td>';
- echo edit_field_access_level($varname, $access_level);
- }
- ?>
- </table>
- </td>
- </tr>
- <?php
- $order++;
- }
- ?>
- </tbody>
- </table>
- </div>
- <!-- tabs -->
- <div id="tabs_tab">
- <table id="tabs_table" class="list_table">
- <thead>
- <tr>
- <th class="list_label"><?php echo i18n::translate('Module Name'); ?></th>
- <th class="list_label"><?php echo i18n::translate('Order'); ?></th>
- <th class="list_label"><?php echo i18n::translate('Access level'); ?></th>
- </tr>
- </thead>
- <tbody>
- <?php
- $order = 1;
- foreach (WT_Module::getInstalledTabs() as $module) { ?>
- <tr class="sortme">
- <td class="list_value"><?php echo $module->getTitle(); ?></td>
- <td class="list_value"><input type="text" size="5" value="<?php echo $order; ?>" name="taborder-<?php echo $module->getName(); ?>" />
- <img class="uarrow" style="vertical-align:bottom;" src="<?php echo $WT_IMAGES["uarrow"]; ?>" border="0" title="<?php echo i18n::translate('Move up'); ?>" />
- <img class="udarrow" style="vertical-align:bottom;" src="<?php echo $WT_IMAGES["udarrow"]; ?>" border="0" title="<?php echo i18n::translate('Move to top'); ?>" />
- <img class="darrow" style="vertical-align:bottom;" src="<?php echo $WT_IMAGES["darrow"]; ?>" border="0" title="<?php echo i18n::translate('Move down'); ?>" />
- <img class="ddarrow" style="vertical-align:bottom;" src="<?php echo $WT_IMAGES["ddarrow"]; ?>" border="0" title="<?php echo i18n::translate('Move to bottom'); ?>" />
- </td>
- <td class="list_value_wrap">
- <table>
- <?php
- foreach (get_all_gedcoms() as $ged_id=>$ged_name) {
- $varname = 'tabaccess-'.$module->getName().'-'.$ged_id;
- $access_level=WT_DB::prepare(
- "SELECT access_level FROM `##module_privacy` WHERE gedcom_id=? AND module_name=? AND component='tab'"
- )->execute(array($ged_id, $module->getName()))->fetchOne();
- if ($access_level===null) {
- $access_level=$module->defaultAccessLevel();
- }
- echo '<tr><td>', htmlspecialchars($ged_name), '</td><td>';
- echo edit_field_access_level($varname, $access_level);
- }
- ?>
- </table>
- </td>
- </tr>
- <?php
- $order++;
- }
- ?>
- </tbody>
- </table>
- </div>
- <!-- sidebars -->
- <div id="sidebars_tab">
- <table id="sidebars_table" class="list_table">
- <thead>
- <tr>
- <th class="list_label"><?php echo i18n::translate('Module Name'); ?></th>
- <th class="list_label"><?php echo i18n::translate('Order'); ?></th>
- <th class="list_label"><?php echo i18n::translate('Access level'); ?></th>
- </tr>
- </thead>
- <tbody>
- <?php
- $order = 1;
- foreach (WT_Module::getInstalledSidebars() as $module) { ?>
- <tr class="sortme">
- <td class="list_value"><?php echo $module->getTitle(); ?></td>
- <td class="list_value"><input type="text" size="5" value="<?php echo $order; ?>" name="sidebarorder-<?php echo $module->getName(); ?>" />
- <img class="uarrow" style="vertical-align:bottom;" src="<?php echo $WT_IMAGES["uarrow"]; ?>" border="0" title="<?php echo i18n::translate('Move up'); ?>" />
- <img class="udarrow" style="vertical-align:bottom;" src="<?php echo $WT_IMAGES["udarrow"]; ?>" border="0" title="<?php echo i18n::translate('Move to top'); ?>" />
- <img class="darrow" style="vertical-align:bottom;" src="<?php echo $WT_IMAGES["darrow"]; ?>" border="0" title="<?php echo i18n::translate('Move down'); ?>" />
- <img class="ddarrow" style="vertical-align:bottom;" src="<?php echo $WT_IMAGES["ddarrow"]; ?>" border="0" title="<?php echo i18n::translate('Move to bottom'); ?>" />
- </td>
- <td class="list_value_wrap">
- <table>
- <?php
- foreach (get_all_gedcoms() as $ged_id=>$ged_name) {
- $varname = 'sidebaraccess-'.$module->getName().'-'.$ged_id;
- $access_level=WT_DB::prepare(
- "SELECT access_level FROM `##module_privacy` WHERE gedcom_id=? AND module_name=? AND component='sidebar'"
- )->execute(array($ged_id, $module->getName()))->fetchOne();
- if ($access_level===null) {
- $access_level=$module->defaultAccessLevel();
- }
- echo '<tr><td>', htmlspecialchars($ged_name), '</td><td>';
- echo edit_field_access_level($varname, $access_level);
- }
- ?>
- </table>
- </td>
- </tr>
- <?php
- $order++;
- }
- ?>
- </tbody>
- </table>
- </div>
- <!-- blocks -->
- <div id="blocks_tab">
- <table id="blocks_table" class="list_table">
- <thead>
- <tr>
- <th class="list_label"><?php echo i18n::translate('Module Name'); ?></th>
- <th class="list_label"><?php echo i18n::translate('Access level'); ?></th>
- </tr>
- </thead>
- <tbody>
- <?php
- $order = 1;
- foreach (WT_Module::getInstalledBlocks() as $module) { ?>
- <tr class="sortme">
- <td class="list_value"><?php echo $module->getTitle(); ?></td>
- <td class="list_value_wrap">
- <table>
- <?php
- foreach (get_all_gedcoms() as $ged_id=>$ged_name) {
- $varname = 'blockaccess-'.$module->getName().'-'.$ged_id;
- $access_level=WT_DB::prepare(
- "SELECT access_level FROM `##module_privacy` WHERE gedcom_id=? AND module_name=? AND component='block'"
- )->execute(array($ged_id, $module->getName()))->fetchOne();
- if ($access_level===null) {
- $access_level=$module->defaultAccessLevel();
- }
- echo '<tr><td>', htmlspecialchars($ged_name), '</td><td>';
- echo edit_field_access_level($varname, $access_level);
- }
- ?>
- </table>
- </td>
- </tr>
- <?php
- $order++;
- }
- ?>
- </tbody>
- </table>
- </div>
- <!-- charts -->
- <div id="charts_tab">
- <table id="charts_table" class="list_table">
- <thead>
- <tr>
- <th class="list_label"><?php echo i18n::translate('Module Name'); ?></th>
- <th class="list_label"><?php echo i18n::translate('Access level'); ?></th>
- </tr>
- </thead>
- <tbody>
- <?php
- $order = 1;
- foreach (WT_Module::getInstalledCharts() as $module) { ?>
- <tr class="sortme">
- <td class="list_value"><?php echo $module->getTitle(); ?></td>
- <td class="list_value_wrap">
- <table>
- <?php
- foreach (get_all_gedcoms() as $ged_id=>$ged_name) {
- $varname = 'chartaccess-'.$module->getName().'-'.$ged_id;
- $access_level=WT_DB::prepare(
- "SELECT access_level FROM `##module_privacy` WHERE gedcom_id=? AND module_name=? AND component='chart'"
- )->execute(array($ged_id, $module->getName()))->fetchOne();
- if ($access_level===null) {
- $access_level=$module->defaultAccessLevel();
- }
- echo '<tr><td>', htmlspecialchars($ged_name), '</td><td>';
- echo edit_field_access_level($varname, $access_level);
- }
- ?>
- </table>
- </td>
- </tr>
- <?php
- $order++;
- }
- ?>
- </tbody>
- </table>
- </div>
- <!-- reports -->
- <div id="reports_tab">
- <table id="reports_table" class="list_table">
- <thead>
- <tr>
- <th class="list_label"><?php echo i18n::translate('Module Name'); ?></th>
- <th class="list_label"><?php echo i18n::translate('Access level'); ?></th>
- </tr>
- </thead>
- <tbody>
- <?php
- $order = 1;
- foreach (WT_Module::getInstalledReports() as $module) { ?>
- <tr class="sortme">
- <td class="list_value"><?php echo $module->getTitle(); ?></td>
- <td class="list_value_wrap">
- <table>
- <?php
- foreach (get_all_gedcoms() as $ged_id=>$ged_name) {
- $varname = 'reportaccess-'.$module->getName().'-'.$ged_id;
- $access_level=WT_DB::prepare(
- "SELECT access_level FROM `##module_privacy` WHERE gedcom_id=? AND module_name=? AND component='report'"
- )->execute(array($ged_id, $module->getName()))->fetchOne();
- if ($access_level===null) {
- $access_level=$module->defaultAccessLevel();
- }
- echo '<tr><td>', htmlspecialchars($ged_name), '</td><td>';
- echo edit_field_access_level($varname, $access_level);
- }
- ?>
- </table>
- </td>
- </tr>
- <?php
- $order++;
- }
- ?>
- </tbody>
- </table>
- </div>
- <!-- themes -->
- <div id="themes_tab">
- <table id="themes_table" class="list_table">
- <thead>
- <tr>
- <th class="list_label"><?php echo i18n::translate('Module Name'); ?></th>
- <th class="list_label"><?php echo i18n::translate('Access level'); ?></th>
- </tr>
- </thead>
- <tbody>
- <?php
- $order = 1;
- foreach (WT_Module::getInstalledThemes() as $module) { ?>
- <tr class="sortme">
- <td class="list_value"><?php echo $module->getTitle(); ?></td>
- <td class="list_value_wrap">
- <table>
- <?php
- foreach (get_all_gedcoms() as $ged_id=>$ged_name) {
- $varname = 'themeaccess-'.$module->getName().'-'.$ged_id;
- $access_level=WT_DB::prepare(
- "SELECT access_level FROM `##module_privacy` WHERE gedcom_id=? AND module_name=? AND component='theme'"
- )->execute(array($ged_id, $module->getName()))->fetchOne();
- if ($access_level===null) {
- $access_level=$module->defaultAccessLevel();
- }
- echo '<tr><td>', htmlspecialchars($ged_name), '</td><td>';
- echo edit_field_access_level($varname, $access_level);
- }
- ?>
- </table>
- </td>
- </tr>
- <?php
- $order++;
- }
- ?>
- </tbody>
- </table>
- </div>
- <input type="submit" value="<?php echo i18n::translate('Save'); ?>" />
- </form>
- </div>
-</div>
-<?php
-print_footer();
diff --git a/modules/descendancy/module.php b/modules/descendancy/module.php
index 5d5ab20657..f2a4c45ec5 100644
--- a/modules/descendancy/module.php
+++ b/modules/descendancy/module.php
@@ -78,9 +78,9 @@ class descendancy_WT_Module extends WT_Module implements WT_Module_Sidebar {
}
}
- jQuery(document).ready(function() {
- jQuery("#sb_desc_name").focus(function() {this.select();});
- jQuery("#sb_desc_name").blur(function() {if (this.value=="") this.value="'.i18n::translate('Search').'";});
+ jQuery(document).ready(function(){
+ jQuery("#sb_desc_name").focus(function(){this.select();});
+ jQuery("#sb_desc_name").blur(function(){if (this.value=="") this.value="'.i18n::translate('Search').'";});
var dtimerid = null;
jQuery("#sb_desc_name").keyup(function(e) {
if (dtimerid) window.clearTimeout(dtimerid);
@@ -151,7 +151,7 @@ class descendancy_WT_Module extends WT_Module implements WT_Module_Sidebar {
$out .= '<div class="desc_tree_div_visible">';
$out .= $this->loadSpouses($person->getXref());
$out .= '</div><script type="text/javascript">dloadedNames["'.$person->getXref().'"]=2;</script>';
- } else {
+ }else {
$out .= '<div class="desc_tree_div">';
$out .= '</div>';
}
@@ -213,7 +213,7 @@ class descendancy_WT_Module extends WT_Module implements WT_Module_Sidebar {
$person = WT_Person::getInstance($pid);
if ($person->canDisplayDetails()) {
$families = $person->getSpouseFamilies();
- foreach ($families as $family) {
+ foreach($families as $family) {
$spouse = $family->getSpouse($person);
if ($spouse) {
$out .= $this->getFamilyLi($family, $spouse, $generations-1);
@@ -231,7 +231,7 @@ class descendancy_WT_Module extends WT_Module implements WT_Module_Sidebar {
$children = $family->getChildren();
if (count($children)>0) {
$private = 0;
- foreach ($children as $child) {
+ foreach($children as $child) {
if ($child->canDisplayName()) $out .= $this->getPersonLi($child, $generations-1);
else $private++;
}
diff --git a/modules/random_media/module.php b/modules/random_media/module.php
index b0bd72889d..fcb0add7ab 100644
--- a/modules/random_media/module.php
+++ b/modules/random_media/module.php
@@ -38,7 +38,7 @@ class random_media_WT_Module extends WT_Module implements WT_Module_Block {
// Extend class WT_Module
public function getDescription() {
- return i18n::translate('The Random Media block randomly selects a photo or other media item from the currently active database and displays it to the user.<br /><br />The administrator determines whether this block can show media items associated with persons or events.');
+ return i18n::translate('The Random Media block randomly selects a photo or other media item from the currently active database and displays it to the user.<br />The administrator determines whether this block can show media items associated with persons or events.');
}
// Implement class WT_Module_Block
diff --git a/themes/_administration/footer.php b/themes/_administration/footer.php
new file mode 100644
index 0000000000..5db26dff13
--- /dev/null
+++ b/themes/_administration/footer.php
@@ -0,0 +1,33 @@
+<?php
+// Footer for webtrees administration theme
+//
+// webtrees: Web based Family History software
+// Copyright (C) 2010 webtrees development team.
+//
+// This program is free software; you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation; either version 2 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program; if not, write to the Free Software
+// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+//
+// @version $Id$
+
+if (!defined('WT_WEBTREES')) {
+ header('HTTP/1.0 403 Forbidden');
+ exit;
+}
+
+echo '</div>'; // id="admin_content"
+echo '<div id="admin_footer">';
+if (WT_DEBUG) {
+ echo execution_stats();
+}
+echo '</div>'; // id="admin_footer"
diff --git a/themes/_administration/header.php b/themes/_administration/header.php
new file mode 100644
index 0000000000..bfd280ca38
--- /dev/null
+++ b/themes/_administration/header.php
@@ -0,0 +1,149 @@
+<?php
+// Header for webtrees administration theme
+//
+// webtrees: Web based Family History software
+// Copyright (C) 2010 webtrees development team.
+//
+// This program is free software; you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation; either version 2 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program; if not, write to the Free Software
+// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+//
+// @version $Id$
+
+if (!defined('WT_WEBTREES')) {
+ header('HTTP/1.0 403 Forbidden');
+ exit;
+}
+
+echo
+ '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">',
+ '<html xmlns="http://www.w3.org/1999/xhtml" ', i18n::html_markup(), '>',
+ '<head>',
+ '<meta http-equiv="Content-type" content="text/html;charset=UTF-8" />',
+ '<title>', htmlspecialchars($title), '</title>',
+ '<link rel="shortcut icon" href="favicon.ico" type="image/x-icon" />',
+ '<link rel="stylesheet" href="', WT_THEME_DIR, 'jquery/jquery-ui_theme.css" type="text/css" />',
+ '<link rel="stylesheet" href="', $stylesheet, '" type="text/css" media="all" />',
+ $javascript,
+ '</head>',
+ '<body id="body">',
+// Header
+ '<div id="admin_head" class="ui-widget-content">',
+ '<div id="title"><a href="administration.php">', i18n::translate('Administration'), '</a></div>',
+ '<div id="links">',
+ '<a href="index.php">', i18n::translate('My page'), '</a> | ',
+ logout_link(),
+ '<span>';
+ $language_menu=WT_MenuBar::getLanguageMenu();
+ if ($language_menu) {
+ echo ' | ', $language_menu->getMenuAsList();
+ }
+ echo '</span>';
+ if (WT_USER_CAN_ACCEPT && exists_pending_change()) {
+ echo ' | <li><a href="javascript:;" onclick="window.open(\'edit_changes.php\',\'_blank\',\'width=600,height=500,resizable=1,scrollbars=1\'); return false;" style="color:red;">', i18n::translate('Pending changes'), '</a></li>';
+ }
+ echo '</div>',
+ '<div id="info">',
+ WT_WEBTREES, ' ', WT_VERSION_TEXT,
+ '<br />',
+ i18n::translate('Current Server Time:'), ' ', format_timestamp(time()),
+ '<br />',
+ i18n::translate('Current User Time:'), ' ', format_timestamp(client_time()),
+ '</div>',
+ '</div>',
+// Side menu
+ '<div id="admin_menu" class="ui-widget-content">',
+ '<ul>',
+ '<li><a ', (WT_SCRIPT_NAME=="administration.php" ? 'class="current" ' : ''), 'href="administration.php">',
+ i18n::translate('Administration'),
+ '</a></li>';
+if (WT_USER_IS_ADMIN) {
+ echo
+ '<li><ul>',
+ '<li><a ', (WT_SCRIPT_NAME=="admin_site_config.php" ? 'class="current" ' : ''), 'href="admin_site_config.php">',
+ i18n::translate('Server configuration'),
+ '</a></li>',
+ '<li><a ', (WT_SCRIPT_NAME=="admin_site_logs.php" ? 'class="current" ' : ''), 'href="admin_site_logs.php">',
+ i18n::translate('Logs'),
+ '</a></li>',
+ '<li><a ', (WT_SCRIPT_NAME=="admin_site_readme.php" ? 'class="current" ' : ''), 'href="admin_site_readme.php">', i18n::translate('README documentation'), '</a></li>',
+ '<li><a ', (WT_SCRIPT_NAME=="admin_site_info.php" ? 'class="current" ' : ''), 'href="admin_site_info.php?action=phpinfo">', i18n::translate('PHP information'), '</a></li>',
+ '<li><a ', (WT_SCRIPT_NAME=="admin_site_manageservers.php" ? 'class="current" ' : ''), 'href="admin_site_ipaddress.php">', i18n::translate('Manage sites'), '</a></li>',
+ '</ul></li>',
+ '<li><a ', (WT_SCRIPT_NAME=="admin_trees_manage.php" ? 'class="current" ' : ''), 'href="admin_trees_manage.php">',
+ i18n::translate('Family trees'),
+ '</a></li>';
+} else {
+ echo '<li>', i18n::translate('Family trees'), '</li>';
+}
+echo '<li><ul>';
+//-- gedcom list
+foreach (get_all_gedcoms() as $ged_id=>$gedcom) {
+ if (userGedcomAdmin(WT_USER_ID, $ged_id)) {
+ echo
+ '<li><a ', (WT_SCRIPT_NAME=="admin_trees_config.php" && WT_GED_ID==$ged_id ? 'class="current" ' : ''), 'href="admin_trees_config.php?ged='.rawurlencode($gedcom).'">',
+ htmlspecialchars(get_gedcom_setting($ged_id, 'title')),
+ '</a></li>';
+ }
+}
+echo '</ul></li>';
+if (WT_USER_IS_ADMIN) {
+ echo
+ '<li><a ', (WT_SCRIPT_NAME=="admin_users_list.php" ? 'class="current" ' : ''), 'href="useradmin.php">',
+ i18n::translate('Users'),
+ '</a></li>',
+ '<li><ul>',
+ '<li><a ', (WT_SCRIPT_NAME=="admin_users_add.php" ? 'class="current" ' : ''), 'href="admin_users_add.php?action=createform">', i18n::translate('Add user'), '</a></li>',
+ '<li><a ', (WT_SCRIPT_NAME=="#" ? 'class="current" ' : ''), 'href="useradmin.php">',
+ i18n::translate('Bulk messaging'),
+ '</a></li>',
+ '</ul></li>',
+ '<li><a ', (WT_SCRIPT_NAME=="admin_media.php" ? 'class="current" ' : ''), 'href="admin_media.php">',
+ i18n::translate('Media'),
+ '</a></li>',
+ '<li><ul>',
+ '<li><a ', (WT_SCRIPT_NAME=="admin_media_upload.php" ? 'class="current" ' : ''), 'href="admin_media_upload.php">',
+ i18n::translate('Upload media files'),
+ '</a></li>',
+ '</ul></li>',
+ '<li><a ', (WT_SCRIPT_NAME=="admin_modules.php" ? 'class="current" ' : ''), 'href="admin_modules.php">',
+ i18n::translate('Modules'),
+ '</a></li>',
+ '<li><ul>',
+ '<li><a ', (WT_SCRIPT_NAME=="admin_module_menus.php" ? 'class="current" ' : ''), 'href="admin_module_menus.php">',
+ i18n::translate('Menus'),
+ '</a></li>',
+ '<li><a ', (WT_SCRIPT_NAME=="admin_module_tabs.php" ? 'class="current" ' : ''), 'href="admin_module_tabs.php">',
+ i18n::translate('Tabs'),
+ '</a></li>',
+ '<li><a ', (WT_SCRIPT_NAME=="admin_module_blocks.php" ? 'class="current" ' : ''), 'href="admin_module_blocks.php">',
+ i18n::translate('Blocks'),
+ '</a></li>',
+ '<li><a ', (WT_SCRIPT_NAME=="admin_module_sidebar.php" ? 'class="current" ' : ''), 'href="admin_module_sidebar.php">',
+ i18n::translate('Sidebar'),
+ '</a></li>',
+ '<li><a ', (WT_SCRIPT_NAME=="admin_module_reports.php" ? 'class="current" ' : ''), 'href="admin_module_reports.php">',
+ i18n::translate('Reports'),
+ '</a></li>',
+ '</ul></li>';
+ foreach (WT_Module::getInstalledModules() as $module) {
+ if ($module instanceof WT_Module_Config) {
+ echo '<li><i><a ', (WT_SCRIPT_NAME=="module.php" && safe_GET('mod')==$module->getName() ? 'class="current" ' : ''), 'href="', $module->getConfigLink(), '">', $module->getTitle(), '</a></i></li>';
+ }
+ }
+}
+echo
+ '</ul>',
+ '</div>',
+ // Content -->
+ '<div id="admin_content" class="ui-widget-content">';
diff --git a/themes/_administration/images/close.png b/themes/_administration/images/close.png
new file mode 100644
index 0000000000..08f249365a
--- /dev/null
+++ b/themes/_administration/images/close.png
Binary files differ
diff --git a/themes/_administration/images/delete.png b/themes/_administration/images/delete.png
new file mode 100644
index 0000000000..03e317066d
--- /dev/null
+++ b/themes/_administration/images/delete.png
Binary files differ
diff --git a/themes/_administration/images/edit.png b/themes/_administration/images/edit.png
new file mode 100644
index 0000000000..0c0759811e
--- /dev/null
+++ b/themes/_administration/images/edit.png
Binary files differ
diff --git a/themes/_administration/images/email.png b/themes/_administration/images/email.png
new file mode 100644
index 0000000000..aa26a28aa8
--- /dev/null
+++ b/themes/_administration/images/email.png
Binary files differ
diff --git a/themes/_administration/images/find_facts.png b/themes/_administration/images/find_facts.png
new file mode 100644
index 0000000000..a74698d7e7
--- /dev/null
+++ b/themes/_administration/images/find_facts.png
Binary files differ
diff --git a/themes/_administration/images/header.png b/themes/_administration/images/header.png
new file mode 100644
index 0000000000..6ed7e52074
--- /dev/null
+++ b/themes/_administration/images/header.png
Binary files differ
diff --git a/themes/_administration/images/help.jpg b/themes/_administration/images/help.jpg
new file mode 100644
index 0000000000..0b8e51f764
--- /dev/null
+++ b/themes/_administration/images/help.jpg
Binary files differ
diff --git a/themes/_administration/images/indi.gif b/themes/_administration/images/indi.gif
new file mode 100644
index 0000000000..581bdd59ad
--- /dev/null
+++ b/themes/_administration/images/indi.gif
Binary files differ
diff --git a/themes/_administration/images/media/audio.png b/themes/_administration/images/media/audio.png
new file mode 100644
index 0000000000..884d9a8129
--- /dev/null
+++ b/themes/_administration/images/media/audio.png
Binary files differ
diff --git a/themes/_administration/images/media/doc.png b/themes/_administration/images/media/doc.png
new file mode 100644
index 0000000000..5dfed062cc
--- /dev/null
+++ b/themes/_administration/images/media/doc.png
Binary files differ
diff --git a/themes/_administration/images/media/flash.png b/themes/_administration/images/media/flash.png
new file mode 100644
index 0000000000..e8957138e9
--- /dev/null
+++ b/themes/_administration/images/media/flash.png
Binary files differ
diff --git a/themes/_administration/images/media/ged.png b/themes/_administration/images/media/ged.png
new file mode 100644
index 0000000000..bbb0fdf3ec
--- /dev/null
+++ b/themes/_administration/images/media/ged.png
Binary files differ
diff --git a/themes/_administration/images/media/globe.png b/themes/_administration/images/media/globe.png
new file mode 100644
index 0000000000..1239009cf2
--- /dev/null
+++ b/themes/_administration/images/media/globe.png
Binary files differ
diff --git a/themes/_administration/images/media/html.png b/themes/_administration/images/media/html.png
new file mode 100644
index 0000000000..f122d30232
--- /dev/null
+++ b/themes/_administration/images/media/html.png
Binary files differ
diff --git a/themes/_administration/images/media/media.png b/themes/_administration/images/media/media.png
new file mode 100644
index 0000000000..ef3c48c8cc
--- /dev/null
+++ b/themes/_administration/images/media/media.png
Binary files differ
diff --git a/themes/_administration/images/media/pdf.png b/themes/_administration/images/media/pdf.png
new file mode 100644
index 0000000000..b99420b2c1
--- /dev/null
+++ b/themes/_administration/images/media/pdf.png
Binary files differ
diff --git a/themes/_administration/images/media/picasa.png b/themes/_administration/images/media/picasa.png
new file mode 100644
index 0000000000..65f94f07d4
--- /dev/null
+++ b/themes/_administration/images/media/picasa.png
Binary files differ
diff --git a/themes/_administration/images/media/tex.png b/themes/_administration/images/media/tex.png
new file mode 100644
index 0000000000..717f9f84d4
--- /dev/null
+++ b/themes/_administration/images/media/tex.png
Binary files differ
diff --git a/themes/_administration/images/media/wmv.png b/themes/_administration/images/media/wmv.png
new file mode 100644
index 0000000000..3a39b3ce7e
--- /dev/null
+++ b/themes/_administration/images/media/wmv.png
Binary files differ
diff --git a/themes/_administration/images/open.png b/themes/_administration/images/open.png
new file mode 100644
index 0000000000..6332fefea4
--- /dev/null
+++ b/themes/_administration/images/open.png
Binary files differ
diff --git a/themes/_administration/images/webtrees_s.png b/themes/_administration/images/webtrees_s.png
new file mode 100644
index 0000000000..e166393c86
--- /dev/null
+++ b/themes/_administration/images/webtrees_s.png
Binary files differ
diff --git a/themes/_administration/images/yes.png b/themes/_administration/images/yes.png
new file mode 100644
index 0000000000..6bd8724f66
--- /dev/null
+++ b/themes/_administration/images/yes.png
Binary files differ
diff --git a/themes/_administration/jquery/images/ui-bg_diagonal-maze_20_6e4f1c_10x10.png b/themes/_administration/jquery/images/ui-bg_diagonal-maze_20_6e4f1c_10x10.png
new file mode 100644
index 0000000000..1bb13a2539
--- /dev/null
+++ b/themes/_administration/jquery/images/ui-bg_diagonal-maze_20_6e4f1c_10x10.png
Binary files differ
diff --git a/themes/_administration/jquery/images/ui-bg_diagonal-maze_40_000000_10x10.png b/themes/_administration/jquery/images/ui-bg_diagonal-maze_40_000000_10x10.png
new file mode 100644
index 0000000000..c6b7e984a0
--- /dev/null
+++ b/themes/_administration/jquery/images/ui-bg_diagonal-maze_40_000000_10x10.png
Binary files differ
diff --git a/themes/_administration/jquery/images/ui-bg_fine-grain_10_eceadf_60x60.png b/themes/_administration/jquery/images/ui-bg_fine-grain_10_eceadf_60x60.png
new file mode 100644
index 0000000000..4656742078
--- /dev/null
+++ b/themes/_administration/jquery/images/ui-bg_fine-grain_10_eceadf_60x60.png
Binary files differ
diff --git a/themes/_administration/jquery/images/ui-bg_fine-grain_10_f8f7f6_60x60.png b/themes/_administration/jquery/images/ui-bg_fine-grain_10_f8f7f6_60x60.png
new file mode 100644
index 0000000000..4550e40778
--- /dev/null
+++ b/themes/_administration/jquery/images/ui-bg_fine-grain_10_f8f7f6_60x60.png
Binary files differ
diff --git a/themes/_administration/jquery/images/ui-bg_fine-grain_15_eceadf_60x60.png b/themes/_administration/jquery/images/ui-bg_fine-grain_15_eceadf_60x60.png
new file mode 100644
index 0000000000..e630829191
--- /dev/null
+++ b/themes/_administration/jquery/images/ui-bg_fine-grain_15_eceadf_60x60.png
Binary files differ
diff --git a/themes/_administration/jquery/images/ui-bg_fine-grain_15_f7f3de_60x60.png b/themes/_administration/jquery/images/ui-bg_fine-grain_15_f7f3de_60x60.png
new file mode 100644
index 0000000000..787d10941f
--- /dev/null
+++ b/themes/_administration/jquery/images/ui-bg_fine-grain_15_f7f3de_60x60.png
Binary files differ
diff --git a/themes/_administration/jquery/images/ui-bg_fine-grain_15_ffffff_60x60.png b/themes/_administration/jquery/images/ui-bg_fine-grain_15_ffffff_60x60.png
new file mode 100644
index 0000000000..19ab46c3f8
--- /dev/null
+++ b/themes/_administration/jquery/images/ui-bg_fine-grain_15_ffffff_60x60.png
Binary files differ
diff --git a/themes/_administration/jquery/images/ui-bg_fine-grain_65_654b24_60x60.png b/themes/_administration/jquery/images/ui-bg_fine-grain_65_654b24_60x60.png
new file mode 100644
index 0000000000..40b75b9943
--- /dev/null
+++ b/themes/_administration/jquery/images/ui-bg_fine-grain_65_654b24_60x60.png
Binary files differ
diff --git a/themes/_administration/jquery/images/ui-bg_fine-grain_68_b83400_60x60.png b/themes/_administration/jquery/images/ui-bg_fine-grain_68_b83400_60x60.png
new file mode 100644
index 0000000000..4e19798623
--- /dev/null
+++ b/themes/_administration/jquery/images/ui-bg_fine-grain_68_b83400_60x60.png
Binary files differ
diff --git a/themes/_administration/jquery/images/ui-icons_222222_256x240.png b/themes/_administration/jquery/images/ui-icons_222222_256x240.png
new file mode 100644
index 0000000000..b273ff111d
--- /dev/null
+++ b/themes/_administration/jquery/images/ui-icons_222222_256x240.png
Binary files differ
diff --git a/themes/_administration/jquery/images/ui-icons_3572ac_256x240.png b/themes/_administration/jquery/images/ui-icons_3572ac_256x240.png
new file mode 100644
index 0000000000..6127bb0c84
--- /dev/null
+++ b/themes/_administration/jquery/images/ui-icons_3572ac_256x240.png
Binary files differ
diff --git a/themes/_administration/jquery/images/ui-icons_8c291d_256x240.png b/themes/_administration/jquery/images/ui-icons_8c291d_256x240.png
new file mode 100644
index 0000000000..a90f0cec75
--- /dev/null
+++ b/themes/_administration/jquery/images/ui-icons_8c291d_256x240.png
Binary files differ
diff --git a/themes/_administration/jquery/images/ui-icons_b83400_256x240.png b/themes/_administration/jquery/images/ui-icons_b83400_256x240.png
new file mode 100644
index 0000000000..7ca9d0b84c
--- /dev/null
+++ b/themes/_administration/jquery/images/ui-icons_b83400_256x240.png
Binary files differ
diff --git a/themes/_administration/jquery/images/ui-icons_fbdb93_256x240.png b/themes/_administration/jquery/images/ui-icons_fbdb93_256x240.png
new file mode 100644
index 0000000000..95c4a66f66
--- /dev/null
+++ b/themes/_administration/jquery/images/ui-icons_fbdb93_256x240.png
Binary files differ
diff --git a/themes/_administration/jquery/images/ui-icons_ffffff_256x240.png b/themes/_administration/jquery/images/ui-icons_ffffff_256x240.png
new file mode 100644
index 0000000000..42f8f992c7
--- /dev/null
+++ b/themes/_administration/jquery/images/ui-icons_ffffff_256x240.png
Binary files differ
diff --git a/themes/_administration/jquery/jquery-ui_theme.css b/themes/_administration/jquery/jquery-ui_theme.css
new file mode 100644
index 0000000000..3d7ac9ecad
--- /dev/null
+++ b/themes/_administration/jquery/jquery-ui_theme.css
@@ -0,0 +1,572 @@
+/*
+ * jQuery UI CSS Framework 1.8.6
+ *
+ * Copyright 2010, AUTHORS.txt (http://jqueryui.com/about)
+ * Dual licensed under the MIT or GPL Version 2 licenses.
+ * http://jquery.org/license
+ *
+ * http://docs.jquery.com/UI/Theming/API
+ */
+
+/* Layout helpers
+----------------------------------*/
+.ui-helper-hidden { display: none; }
+.ui-helper-hidden-accessible { position: absolute; left: -99999999px; }
+.ui-helper-reset { margin: 0; padding: 0; border: 0; outline: 0; line-height: 1.3; text-decoration: none; font-size: 100%; list-style: none; }
+.ui-helper-clearfix:after { content: "."; display: block; height: 0; clear: both; visibility: hidden; }
+.ui-helper-clearfix { display: inline-block; }
+/* required comment for clearfix to work in Opera \*/
+* html .ui-helper-clearfix { height:1%; }
+.ui-helper-clearfix { display:block; }
+/* end clearfix */
+.ui-helper-zfix { width: 100%; height: 100%; top: 0; left: 0; position: absolute; opacity: 0; filter:Alpha(Opacity=0); }
+
+
+/* Interaction Cues
+----------------------------------*/
+.ui-state-disabled { cursor: default !important; }
+
+
+/* Icons
+----------------------------------*/
+
+/* states and images */
+.ui-icon { display: block; text-indent: -99999px; overflow: hidden; background-repeat: no-repeat; }
+
+
+/* Misc visuals
+----------------------------------*/
+
+/* Overlays */
+.ui-widget-overlay { position: absolute; top: 0; left: 0; width: 100%; height: 100%; }
+
+
+/*
+ * jQuery UI CSS Framework 1.8.6
+ *
+ * Copyright 2010, AUTHORS.txt (http://jqueryui.com/about)
+ * Dual licensed under the MIT or GPL Version 2 licenses.
+ * http://jquery.org/license
+ *
+ * http://docs.jquery.com/UI/Theming/API
+ *
+ * To view and modify this theme, visit http://jqueryui.com/themeroller/?ffDefault=Trebuchet%20MS,%20Tahoma,%20Verdana,%20Arial,%20sans-serif&fwDefault=bold&fsDefault=1.1em&cornerRadius=6px&bgColorHeader=ffffff&bgTextureHeader=23_fine_grain.png&bgImgOpacityHeader=15&borderColorHeader=d4d1bf&fcHeader=453821&iconColorHeader=b83400&bgColorContent=eceadf&bgTextureContent=23_fine_grain.png&bgImgOpacityContent=10&borderColorContent=d9d6c4&fcContent=1f1f1f&iconColorContent=222222&bgColorDefault=f8f7f6&bgTextureDefault=23_fine_grain.png&bgImgOpacityDefault=10&borderColorDefault=cbc7bd&fcDefault=654b24&iconColorDefault=b83400&bgColorHover=654b24&bgTextureHover=23_fine_grain.png&bgImgOpacityHover=65&borderColorHover=654b24&fcHover=ffffff&iconColorHover=ffffff&bgColorActive=eceadf&bgTextureActive=23_fine_grain.png&bgImgOpacityActive=15&borderColorActive=d9d6c4&fcActive=140f06&iconColorActive=8c291d&bgColorHighlight=f7f3de&bgTextureHighlight=23_fine_grain.png&bgImgOpacityHighlight=15&borderColorHighlight=b2a266&fcHighlight=3a3427&iconColorHighlight=3572ac&bgColorError=b83400&bgTextureError=23_fine_grain.png&bgImgOpacityError=68&borderColorError=681818&fcError=ffffff&iconColorError=fbdb93&bgColorOverlay=6e4f1c&bgTextureOverlay=16_diagonal_maze.png&bgImgOpacityOverlay=20&opacityOverlay=60&bgColorShadow=000000&bgTextureShadow=16_diagonal_maze.png&bgImgOpacityShadow=40&opacityShadow=60&thicknessShadow=5px&offsetTopShadow=0&offsetLeftShadow=-10px&cornerRadiusShadow=18px
+ */
+
+
+/* Component containers
+----------------------------------*/
+.ui-widget { font-family: Trebuchet MS, Tahoma, Verdana, Arial, sans-serif; font-size: 1.1em; }
+.ui-widget .ui-widget { font-size: 1em; }
+.ui-widget input, .ui-widget select, .ui-widget textarea, .ui-widget button { font-family: Trebuchet MS, Tahoma, Verdana, Arial, sans-serif; font-size: 1em; }
+.ui-widget-content { border: 1px solid #d9d6c4; background: #eceadf url(images/ui-bg_fine-grain_10_eceadf_60x60.png) 50% 50% repeat; color: #1f1f1f; }
+.ui-widget-content a { color: #1f1f1f; }
+.ui-widget-header { border: 1px solid #d4d1bf; background: #ffffff url(images/ui-bg_fine-grain_15_ffffff_60x60.png) 50% 50% repeat; color: #453821; font-weight: bold; }
+.ui-widget-header a { color: #453821; }
+
+/* Interaction states
+----------------------------------*/
+.ui-state-default, .ui-widget-content .ui-state-default, .ui-widget-header .ui-state-default { border: 1px solid #cbc7bd; background: #f8f7f6 url(images/ui-bg_fine-grain_10_f8f7f6_60x60.png) 50% 50% repeat; font-weight: bold; color: #654b24; }
+.ui-state-default a, .ui-state-default a:link, .ui-state-default a:visited { color: #654b24; text-decoration: none; }
+.ui-state-hover, .ui-widget-content .ui-state-hover, .ui-widget-header .ui-state-hover, .ui-state-focus, .ui-widget-content .ui-state-focus, .ui-widget-header .ui-state-focus { border: 1px solid #654b24; background: #654b24 url(images/ui-bg_fine-grain_65_654b24_60x60.png) 50% 50% repeat; font-weight: bold; color: #ffffff; }
+.ui-state-hover a, .ui-state-hover a:hover { color: #ffffff; text-decoration: none; }
+.ui-state-active, .ui-widget-content .ui-state-active, .ui-widget-header .ui-state-active { border: 1px solid #d9d6c4; background: #eceadf url(images/ui-bg_fine-grain_15_eceadf_60x60.png) 50% 50% repeat; font-weight: bold; color: #140f06; }
+.ui-state-active a, .ui-state-active a:link, .ui-state-active a:visited { color: #140f06; text-decoration: none; }
+.ui-widget :active { outline: none; }
+
+/* Interaction Cues
+----------------------------------*/
+.ui-state-highlight, .ui-widget-content .ui-state-highlight, .ui-widget-header .ui-state-highlight {border: 1px solid #b2a266; background: #f7f3de url(images/ui-bg_fine-grain_15_f7f3de_60x60.png) 50% 50% repeat; color: #3a3427; }
+.ui-state-highlight a, .ui-widget-content .ui-state-highlight a,.ui-widget-header .ui-state-highlight a { color: #3a3427; }
+.ui-state-error, .ui-widget-content .ui-state-error, .ui-widget-header .ui-state-error {border: 1px solid #681818; background: #b83400 url(images/ui-bg_fine-grain_68_b83400_60x60.png) 50% 50% repeat; color: #ffffff; }
+.ui-state-error a, .ui-widget-content .ui-state-error a, .ui-widget-header .ui-state-error a { color: #ffffff; }
+.ui-state-error-text, .ui-widget-content .ui-state-error-text, .ui-widget-header .ui-state-error-text { color: #ffffff; }
+.ui-priority-primary, .ui-widget-content .ui-priority-primary, .ui-widget-header .ui-priority-primary { font-weight: bold; }
+.ui-priority-secondary, .ui-widget-content .ui-priority-secondary, .ui-widget-header .ui-priority-secondary { opacity: .7; filter:Alpha(Opacity=70); font-weight: normal; }
+.ui-state-disabled, .ui-widget-content .ui-state-disabled, .ui-widget-header .ui-state-disabled { opacity: .35; filter:Alpha(Opacity=35); background-image: none; }
+
+/* Icons
+----------------------------------*/
+
+/* states and images */
+.ui-icon { width: 16px; height: 16px; background-image: url(images/ui-icons_222222_256x240.png); }
+.ui-widget-content .ui-icon {background-image: url(images/ui-icons_222222_256x240.png); }
+.ui-widget-header .ui-icon {background-image: url(images/ui-icons_b83400_256x240.png); }
+.ui-state-default .ui-icon { background-image: url(images/ui-icons_b83400_256x240.png); }
+.ui-state-hover .ui-icon, .ui-state-focus .ui-icon {background-image: url(images/ui-icons_ffffff_256x240.png); }
+.ui-state-active .ui-icon {background-image: url(images/ui-icons_8c291d_256x240.png); }
+.ui-state-highlight .ui-icon {background-image: url(images/ui-icons_3572ac_256x240.png); }
+.ui-state-error .ui-icon, .ui-state-error-text .ui-icon {background-image: url(images/ui-icons_fbdb93_256x240.png); }
+
+/* positioning */
+.ui-icon-carat-1-n { background-position: 0 0; }
+.ui-icon-carat-1-ne { background-position: -16px 0; }
+.ui-icon-carat-1-e { background-position: -32px 0; }
+.ui-icon-carat-1-se { background-position: -48px 0; }
+.ui-icon-carat-1-s { background-position: -64px 0; }
+.ui-icon-carat-1-sw { background-position: -80px 0; }
+.ui-icon-carat-1-w { background-position: -96px 0; }
+.ui-icon-carat-1-nw { background-position: -112px 0; }
+.ui-icon-carat-2-n-s { background-position: -128px 0; }
+.ui-icon-carat-2-e-w { background-position: -144px 0; }
+.ui-icon-triangle-1-n { background-position: 0 -16px; }
+.ui-icon-triangle-1-ne { background-position: -16px -16px; }
+.ui-icon-triangle-1-e { background-position: -32px -16px; }
+.ui-icon-triangle-1-se { background-position: -48px -16px; }
+.ui-icon-triangle-1-s { background-position: -64px -16px; }
+.ui-icon-triangle-1-sw { background-position: -80px -16px; }
+.ui-icon-triangle-1-w { background-position: -96px -16px; }
+.ui-icon-triangle-1-nw { background-position: -112px -16px; }
+.ui-icon-triangle-2-n-s { background-position: -128px -16px; }
+.ui-icon-triangle-2-e-w { background-position: -144px -16px; }
+.ui-icon-arrow-1-n { background-position: 0 -32px; }
+.ui-icon-arrow-1-ne { background-position: -16px -32px; }
+.ui-icon-arrow-1-e { background-position: -32px -32px; }
+.ui-icon-arrow-1-se { background-position: -48px -32px; }
+.ui-icon-arrow-1-s { background-position: -64px -32px; }
+.ui-icon-arrow-1-sw { background-position: -80px -32px; }
+.ui-icon-arrow-1-w { background-position: -96px -32px; }
+.ui-icon-arrow-1-nw { background-position: -112px -32px; }
+.ui-icon-arrow-2-n-s { background-position: -128px -32px; }
+.ui-icon-arrow-2-ne-sw { background-position: -144px -32px; }
+.ui-icon-arrow-2-e-w { background-position: -160px -32px; }
+.ui-icon-arrow-2-se-nw { background-position: -176px -32px; }
+.ui-icon-arrowstop-1-n { background-position: -192px -32px; }
+.ui-icon-arrowstop-1-e { background-position: -208px -32px; }
+.ui-icon-arrowstop-1-s { background-position: -224px -32px; }
+.ui-icon-arrowstop-1-w { background-position: -240px -32px; }
+.ui-icon-arrowthick-1-n { background-position: 0 -48px; }
+.ui-icon-arrowthick-1-ne { background-position: -16px -48px; }
+.ui-icon-arrowthick-1-e { background-position: -32px -48px; }
+.ui-icon-arrowthick-1-se { background-position: -48px -48px; }
+.ui-icon-arrowthick-1-s { background-position: -64px -48px; }
+.ui-icon-arrowthick-1-sw { background-position: -80px -48px; }
+.ui-icon-arrowthick-1-w { background-position: -96px -48px; }
+.ui-icon-arrowthick-1-nw { background-position: -112px -48px; }
+.ui-icon-arrowthick-2-n-s { background-position: -128px -48px; }
+.ui-icon-arrowthick-2-ne-sw { background-position: -144px -48px; }
+.ui-icon-arrowthick-2-e-w { background-position: -160px -48px; }
+.ui-icon-arrowthick-2-se-nw { background-position: -176px -48px; }
+.ui-icon-arrowthickstop-1-n { background-position: -192px -48px; }
+.ui-icon-arrowthickstop-1-e { background-position: -208px -48px; }
+.ui-icon-arrowthickstop-1-s { background-position: -224px -48px; }
+.ui-icon-arrowthickstop-1-w { background-position: -240px -48px; }
+.ui-icon-arrowreturnthick-1-w { background-position: 0 -64px; }
+.ui-icon-arrowreturnthick-1-n { background-position: -16px -64px; }
+.ui-icon-arrowreturnthick-1-e { background-position: -32px -64px; }
+.ui-icon-arrowreturnthick-1-s { background-position: -48px -64px; }
+.ui-icon-arrowreturn-1-w { background-position: -64px -64px; }
+.ui-icon-arrowreturn-1-n { background-position: -80px -64px; }
+.ui-icon-arrowreturn-1-e { background-position: -96px -64px; }
+.ui-icon-arrowreturn-1-s { background-position: -112px -64px; }
+.ui-icon-arrowrefresh-1-w { background-position: -128px -64px; }
+.ui-icon-arrowrefresh-1-n { background-position: -144px -64px; }
+.ui-icon-arrowrefresh-1-e { background-position: -160px -64px; }
+.ui-icon-arrowrefresh-1-s { background-position: -176px -64px; }
+.ui-icon-arrow-4 { background-position: 0 -80px; }
+.ui-icon-arrow-4-diag { background-position: -16px -80px; }
+.ui-icon-extlink { background-position: -32px -80px; }
+.ui-icon-newwin { background-position: -48px -80px; }
+.ui-icon-refresh { background-position: -64px -80px; }
+.ui-icon-shuffle { background-position: -80px -80px; }
+.ui-icon-transfer-e-w { background-position: -96px -80px; }
+.ui-icon-transferthick-e-w { background-position: -112px -80px; }
+.ui-icon-folder-collapsed { background-position: 0 -96px; }
+.ui-icon-folder-open { background-position: -16px -96px; }
+.ui-icon-document { background-position: -32px -96px; }
+.ui-icon-document-b { background-position: -48px -96px; }
+.ui-icon-note { background-position: -64px -96px; }
+.ui-icon-mail-closed { background-position: -80px -96px; }
+.ui-icon-mail-open { background-position: -96px -96px; }
+.ui-icon-suitcase { background-position: -112px -96px; }
+.ui-icon-comment { background-position: -128px -96px; }
+.ui-icon-person { background-position: -144px -96px; }
+.ui-icon-print { background-position: -160px -96px; }
+.ui-icon-trash { background-position: -176px -96px; }
+.ui-icon-locked { background-position: -192px -96px; }
+.ui-icon-unlocked { background-position: -208px -96px; }
+.ui-icon-bookmark { background-position: -224px -96px; }
+.ui-icon-tag { background-position: -240px -96px; }
+.ui-icon-home { background-position: 0 -112px; }
+.ui-icon-flag { background-position: -16px -112px; }
+.ui-icon-calendar { background-position: -32px -112px; }
+.ui-icon-cart { background-position: -48px -112px; }
+.ui-icon-pencil { background-position: -64px -112px; }
+.ui-icon-clock { background-position: -80px -112px; }
+.ui-icon-disk { background-position: -96px -112px; }
+.ui-icon-calculator { background-position: -112px -112px; }
+.ui-icon-zoomin { background-position: -128px -112px; }
+.ui-icon-zoomout { background-position: -144px -112px; }
+.ui-icon-search { background-position: -160px -112px; }
+.ui-icon-wrench { background-position: -176px -112px; }
+.ui-icon-gear { background-position: -192px -112px; }
+.ui-icon-heart { background-position: -208px -112px; }
+.ui-icon-star { background-position: -224px -112px; }
+.ui-icon-link { background-position: -240px -112px; }
+.ui-icon-cancel { background-position: 0 -128px; }
+.ui-icon-plus { background-position: -16px -128px; }
+.ui-icon-plusthick { background-position: -32px -128px; }
+.ui-icon-minus { background-position: -48px -128px; }
+.ui-icon-minusthick { background-position: -64px -128px; }
+.ui-icon-close { background-position: -80px -128px; }
+.ui-icon-closethick { background-position: -96px -128px; }
+.ui-icon-key { background-position: -112px -128px; }
+.ui-icon-lightbulb { background-position: -128px -128px; }
+.ui-icon-scissors { background-position: -144px -128px; }
+.ui-icon-clipboard { background-position: -160px -128px; }
+.ui-icon-copy { background-position: -176px -128px; }
+.ui-icon-contact { background-position: -192px -128px; }
+.ui-icon-image { background-position: -208px -128px; }
+.ui-icon-video { background-position: -224px -128px; }
+.ui-icon-script { background-position: -240px -128px; }
+.ui-icon-alert { background-position: 0 -144px; }
+.ui-icon-info { background-position: -16px -144px; }
+.ui-icon-notice { background-position: -32px -144px; }
+.ui-icon-help { background-position: -48px -144px; }
+.ui-icon-check { background-position: -64px -144px; }
+.ui-icon-bullet { background-position: -80px -144px; }
+.ui-icon-radio-off { background-position: -96px -144px; }
+.ui-icon-radio-on { background-position: -112px -144px; }
+.ui-icon-pin-w { background-position: -128px -144px; }
+.ui-icon-pin-s { background-position: -144px -144px; }
+.ui-icon-play { background-position: 0 -160px; }
+.ui-icon-pause { background-position: -16px -160px; }
+.ui-icon-seek-next { background-position: -32px -160px; }
+.ui-icon-seek-prev { background-position: -48px -160px; }
+.ui-icon-seek-end { background-position: -64px -160px; }
+.ui-icon-seek-start { background-position: -80px -160px; }
+/* ui-icon-seek-first is deprecated, use ui-icon-seek-start instead */
+.ui-icon-seek-first { background-position: -80px -160px; }
+.ui-icon-stop { background-position: -96px -160px; }
+.ui-icon-eject { background-position: -112px -160px; }
+.ui-icon-volume-off { background-position: -128px -160px; }
+.ui-icon-volume-on { background-position: -144px -160px; }
+.ui-icon-power { background-position: 0 -176px; }
+.ui-icon-signal-diag { background-position: -16px -176px; }
+.ui-icon-signal { background-position: -32px -176px; }
+.ui-icon-battery-0 { background-position: -48px -176px; }
+.ui-icon-battery-1 { background-position: -64px -176px; }
+.ui-icon-battery-2 { background-position: -80px -176px; }
+.ui-icon-battery-3 { background-position: -96px -176px; }
+.ui-icon-circle-plus { background-position: 0 -192px; }
+.ui-icon-circle-minus { background-position: -16px -192px; }
+.ui-icon-circle-close { background-position: -32px -192px; }
+.ui-icon-circle-triangle-e { background-position: -48px -192px; }
+.ui-icon-circle-triangle-s { background-position: -64px -192px; }
+.ui-icon-circle-triangle-w { background-position: -80px -192px; }
+.ui-icon-circle-triangle-n { background-position: -96px -192px; }
+.ui-icon-circle-arrow-e { background-position: -112px -192px; }
+.ui-icon-circle-arrow-s { background-position: -128px -192px; }
+.ui-icon-circle-arrow-w { background-position: -144px -192px; }
+.ui-icon-circle-arrow-n { background-position: -160px -192px; }
+.ui-icon-circle-zoomin { background-position: -176px -192px; }
+.ui-icon-circle-zoomout { background-position: -192px -192px; }
+.ui-icon-circle-check { background-position: -208px -192px; }
+.ui-icon-circlesmall-plus { background-position: 0 -208px; }
+.ui-icon-circlesmall-minus { background-position: -16px -208px; }
+.ui-icon-circlesmall-close { background-position: -32px -208px; }
+.ui-icon-squaresmall-plus { background-position: -48px -208px; }
+.ui-icon-squaresmall-minus { background-position: -64px -208px; }
+.ui-icon-squaresmall-close { background-position: -80px -208px; }
+.ui-icon-grip-dotted-vertical { background-position: 0 -224px; }
+.ui-icon-grip-dotted-horizontal { background-position: -16px -224px; }
+.ui-icon-grip-solid-vertical { background-position: -32px -224px; }
+.ui-icon-grip-solid-horizontal { background-position: -48px -224px; }
+.ui-icon-gripsmall-diagonal-se { background-position: -64px -224px; }
+.ui-icon-grip-diagonal-se { background-position: -80px -224px; }
+
+
+/* Misc visuals
+----------------------------------*/
+
+/* Corner radius */
+.ui-corner-tl { -moz-border-radius-topleft: 6px; -webkit-border-top-left-radius: 6px; border-top-left-radius: 6px; }
+.ui-corner-tr { -moz-border-radius-topright: 6px; -webkit-border-top-right-radius: 6px; border-top-right-radius: 6px; }
+.ui-corner-bl { -moz-border-radius-bottomleft: 6px; -webkit-border-bottom-left-radius: 6px; border-bottom-left-radius: 6px; }
+.ui-corner-br { -moz-border-radius-bottomright: 6px; -webkit-border-bottom-right-radius: 6px; border-bottom-right-radius: 6px; }
+.ui-corner-top { -moz-border-radius-topleft: 6px; -webkit-border-top-left-radius: 6px; border-top-left-radius: 6px; -moz-border-radius-topright: 6px; -webkit-border-top-right-radius: 6px; border-top-right-radius: 6px; }
+.ui-corner-bottom { -moz-border-radius-bottomleft: 6px; -webkit-border-bottom-left-radius: 6px; border-bottom-left-radius: 6px; -moz-border-radius-bottomright: 6px; -webkit-border-bottom-right-radius: 6px; border-bottom-right-radius: 6px; }
+.ui-corner-right { -moz-border-radius-topright: 6px; -webkit-border-top-right-radius: 6px; border-top-right-radius: 6px; -moz-border-radius-bottomright: 6px; -webkit-border-bottom-right-radius: 6px; border-bottom-right-radius: 6px; }
+.ui-corner-left { -moz-border-radius-topleft: 6px; -webkit-border-top-left-radius: 6px; border-top-left-radius: 6px; -moz-border-radius-bottomleft: 6px; -webkit-border-bottom-left-radius: 6px; border-bottom-left-radius: 6px; }
+.ui-corner-all { -moz-border-radius: 6px; -webkit-border-radius: 6px; border-radius: 6px; }
+
+/* Overlays */
+.ui-widget-overlay { background: #6e4f1c url(images/ui-bg_diagonal-maze_20_6e4f1c_10x10.png) 50% 50% repeat; opacity: .60;filter:Alpha(Opacity=60); }
+.ui-widget-shadow { margin: 0 0 0 -10px; padding: 5px; background: #000000 url(images/ui-bg_diagonal-maze_40_000000_10x10.png) 50% 50% repeat; opacity: .60;filter:Alpha(Opacity=60); -moz-border-radius: 18px; -webkit-border-radius: 18px; border-radius: 18px; }/*
+ * jQuery UI Resizable 1.8.6
+ *
+ * Copyright 2010, AUTHORS.txt (http://jqueryui.com/about)
+ * Dual licensed under the MIT or GPL Version 2 licenses.
+ * http://jquery.org/license
+ *
+ * http://docs.jquery.com/UI/Resizable#theming
+ */
+.ui-resizable { position: relative;}
+.ui-resizable-handle { position: absolute;font-size: 0.1px;z-index: 99999; display: block;}
+.ui-resizable-disabled .ui-resizable-handle, .ui-resizable-autohide .ui-resizable-handle { display: none; }
+.ui-resizable-n { cursor: n-resize; height: 7px; width: 100%; top: -5px; left: 0; }
+.ui-resizable-s { cursor: s-resize; height: 7px; width: 100%; bottom: -5px; left: 0; }
+.ui-resizable-e { cursor: e-resize; width: 7px; right: -5px; top: 0; height: 100%; }
+.ui-resizable-w { cursor: w-resize; width: 7px; left: -5px; top: 0; height: 100%; }
+.ui-resizable-se { cursor: se-resize; width: 12px; height: 12px; right: 1px; bottom: 1px; }
+.ui-resizable-sw { cursor: sw-resize; width: 9px; height: 9px; left: -5px; bottom: -5px; }
+.ui-resizable-nw { cursor: nw-resize; width: 9px; height: 9px; left: -5px; top: -5px; }
+.ui-resizable-ne { cursor: ne-resize; width: 9px; height: 9px; right: -5px; top: -5px;}/*
+ * jQuery UI Selectable 1.8.6
+ *
+ * Copyright 2010, AUTHORS.txt (http://jqueryui.com/about)
+ * Dual licensed under the MIT or GPL Version 2 licenses.
+ * http://jquery.org/license
+ *
+ * http://docs.jquery.com/UI/Selectable#theming
+ */
+.ui-selectable-helper { position: absolute; z-index: 100; border:1px dotted black; }
+/*
+ * jQuery UI Accordion 1.8.6
+ *
+ * Copyright 2010, AUTHORS.txt (http://jqueryui.com/about)
+ * Dual licensed under the MIT or GPL Version 2 licenses.
+ * http://jquery.org/license
+ *
+ * http://docs.jquery.com/UI/Accordion#theming
+ */
+/* IE/Win - Fix animation bug - #4615 */
+.ui-accordion { width: 100%; }
+.ui-accordion .ui-accordion-header { cursor: pointer; position: relative; margin-top: 1px; zoom: 1; }
+.ui-accordion .ui-accordion-li-fix { display: inline; }
+.ui-accordion .ui-accordion-header-active { border-bottom: 0 !important; }
+.ui-accordion .ui-accordion-header a { display: block; font-size: 1em; padding: .5em .5em .5em .7em; }
+.ui-accordion-icons .ui-accordion-header a { padding-left: 2.2em; }
+.ui-accordion .ui-accordion-header .ui-icon { position: absolute; left: .5em; top: 50%; margin-top: -8px; }
+.ui-accordion .ui-accordion-content { padding: 1em 2.2em; border-top: 0; margin-top: -2px; position: relative; top: 1px; margin-bottom: 2px; overflow: auto; display: none; zoom: 1; }
+.ui-accordion .ui-accordion-content-active { display: block; }/*
+ * jQuery UI Autocomplete 1.8.6
+ *
+ * Copyright 2010, AUTHORS.txt (http://jqueryui.com/about)
+ * Dual licensed under the MIT or GPL Version 2 licenses.
+ * http://jquery.org/license
+ *
+ * http://docs.jquery.com/UI/Autocomplete#theming
+ */
+.ui-autocomplete { position: absolute; cursor: default; }
+
+/* workarounds */
+* html .ui-autocomplete { width:1px; } /* without this, the menu expands to 100% in IE6 */
+
+/*
+ * jQuery UI Menu 1.8.6
+ *
+ * Copyright 2010, AUTHORS.txt (http://jqueryui.com/about)
+ * Dual licensed under the MIT or GPL Version 2 licenses.
+ * http://jquery.org/license
+ *
+ * http://docs.jquery.com/UI/Menu#theming
+ */
+.ui-menu {
+ list-style:none;
+ padding: 2px;
+ margin: 0;
+ display:block;
+ float: left;
+}
+.ui-menu .ui-menu {
+ margin-top: -3px;
+}
+.ui-menu .ui-menu-item {
+ margin:0;
+ padding: 0;
+ zoom: 1;
+ float: left;
+ clear: left;
+ width: 100%;
+}
+.ui-menu .ui-menu-item a {
+ text-decoration:none;
+ display:block;
+ padding:.2em .4em;
+ line-height:1.5;
+ zoom:1;
+}
+.ui-menu .ui-menu-item a.ui-state-hover,
+.ui-menu .ui-menu-item a.ui-state-active {
+ font-weight: normal;
+ margin: -1px;
+}
+/*
+ * jQuery UI Button 1.8.6
+ *
+ * Copyright 2010, AUTHORS.txt (http://jqueryui.com/about)
+ * Dual licensed under the MIT or GPL Version 2 licenses.
+ * http://jquery.org/license
+ *
+ * http://docs.jquery.com/UI/Button#theming
+ */
+.ui-button { display: inline-block; position: relative; padding: 0; margin-right: .1em; text-decoration: none !important; cursor: pointer; text-align: center; zoom: 1; overflow: visible; } /* the overflow property removes extra width in IE */
+.ui-button-icon-only { width: 2.2em; } /* to make room for the icon, a width needs to be set here */
+button.ui-button-icon-only { width: 2.4em; } /* button elements seem to need a little more width */
+.ui-button-icons-only { width: 3.4em; }
+button.ui-button-icons-only { width: 3.7em; }
+
+/*button text element */
+.ui-button .ui-button-text { display: block; line-height: 1.4; }
+.ui-button-text-only .ui-button-text { padding: .4em 1em; }
+.ui-button-icon-only .ui-button-text, .ui-button-icons-only .ui-button-text { padding: .4em; text-indent: -9999999px; }
+.ui-button-text-icon-primary .ui-button-text, .ui-button-text-icons .ui-button-text { padding: .4em 1em .4em 2.1em; }
+.ui-button-text-icon-secondary .ui-button-text, .ui-button-text-icons .ui-button-text { padding: .4em 2.1em .4em 1em; }
+.ui-button-text-icons .ui-button-text { padding-left: 2.1em; padding-right: 2.1em; }
+/* no icon support for input elements, provide padding by default */
+input.ui-button { padding: .4em 1em; }
+
+/*button icon element(s) */
+.ui-button-icon-only .ui-icon, .ui-button-text-icon-primary .ui-icon, .ui-button-text-icon-secondary .ui-icon, .ui-button-text-icons .ui-icon, .ui-button-icons-only .ui-icon { position: absolute; top: 50%; margin-top: -8px; }
+.ui-button-icon-only .ui-icon { left: 50%; margin-left: -8px; }
+.ui-button-text-icon-primary .ui-button-icon-primary, .ui-button-text-icons .ui-button-icon-primary, .ui-button-icons-only .ui-button-icon-primary { left: .5em; }
+.ui-button-text-icon-secondary .ui-button-icon-secondary, .ui-button-text-icons .ui-button-icon-secondary, .ui-button-icons-only .ui-button-icon-secondary { right: .5em; }
+.ui-button-text-icons .ui-button-icon-secondary, .ui-button-icons-only .ui-button-icon-secondary { right: .5em; }
+
+/*button sets*/
+.ui-buttonset { margin-right: 7px; }
+.ui-buttonset .ui-button { margin-left: 0; margin-right: -.3em; }
+
+/* workarounds */
+button.ui-button::-moz-focus-inner { border: 0; padding: 0; } /* reset extra padding in Firefox */
+/*
+ * jQuery UI Dialog 1.8.6
+ *
+ * Copyright 2010, AUTHORS.txt (http://jqueryui.com/about)
+ * Dual licensed under the MIT or GPL Version 2 licenses.
+ * http://jquery.org/license
+ *
+ * http://docs.jquery.com/UI/Dialog#theming
+ */
+.ui-dialog { position: absolute; padding: .2em; width: 300px; overflow: hidden; }
+.ui-dialog .ui-dialog-titlebar { padding: .5em 1em .3em; position: relative; }
+.ui-dialog .ui-dialog-title { float: left; margin: .1em 16px .2em 0; }
+.ui-dialog .ui-dialog-titlebar-close { position: absolute; right: .3em; top: 50%; width: 19px; margin: -10px 0 0 0; padding: 1px; height: 18px; }
+.ui-dialog .ui-dialog-titlebar-close span { display: block; margin: 1px; }
+.ui-dialog .ui-dialog-titlebar-close:hover, .ui-dialog .ui-dialog-titlebar-close:focus { padding: 0; }
+.ui-dialog .ui-dialog-content { position: relative; border: 0; padding: .5em 1em; background: none; overflow: auto; zoom: 1; }
+.ui-dialog .ui-dialog-buttonpane { text-align: left; border-width: 1px 0 0 0; background-image: none; margin: .5em 0 0 0; padding: .3em 1em .5em .4em; }
+.ui-dialog .ui-dialog-buttonpane .ui-dialog-buttonset { float: right; }
+.ui-dialog .ui-dialog-buttonpane button { margin: .5em .4em .5em 0; cursor: pointer; }
+.ui-dialog .ui-resizable-se { width: 14px; height: 14px; right: 3px; bottom: 3px; }
+.ui-draggable .ui-dialog-titlebar { cursor: move; }
+/*
+ * jQuery UI Slider 1.8.6
+ *
+ * Copyright 2010, AUTHORS.txt (http://jqueryui.com/about)
+ * Dual licensed under the MIT or GPL Version 2 licenses.
+ * http://jquery.org/license
+ *
+ * http://docs.jquery.com/UI/Slider#theming
+ */
+.ui-slider { position: relative; text-align: left; }
+.ui-slider .ui-slider-handle { position: absolute; z-index: 2; width: 1.2em; height: 1.2em; cursor: default; }
+.ui-slider .ui-slider-range { position: absolute; z-index: 1; font-size: .7em; display: block; border: 0; background-position: 0 0; }
+
+.ui-slider-horizontal { height: .8em; }
+.ui-slider-horizontal .ui-slider-handle { top: -.3em; margin-left: -.6em; }
+.ui-slider-horizontal .ui-slider-range { top: 0; height: 100%; }
+.ui-slider-horizontal .ui-slider-range-min { left: 0; }
+.ui-slider-horizontal .ui-slider-range-max { right: 0; }
+
+.ui-slider-vertical { width: .8em; height: 100px; }
+.ui-slider-vertical .ui-slider-handle { left: -.3em; margin-left: 0; margin-bottom: -.6em; }
+.ui-slider-vertical .ui-slider-range { left: 0; width: 100%; }
+.ui-slider-vertical .ui-slider-range-min { bottom: 0; }
+.ui-slider-vertical .ui-slider-range-max { top: 0; }/*
+ * jQuery UI Tabs 1.8.6
+ *
+ * Copyright 2010, AUTHORS.txt (http://jqueryui.com/about)
+ * Dual licensed under the MIT or GPL Version 2 licenses.
+ * http://jquery.org/license
+ *
+ * http://docs.jquery.com/UI/Tabs#theming
+ */
+.ui-tabs { position: relative; padding: .2em; zoom: 1; } /* position: relative prevents IE scroll bug (element with position: relative inside container with overflow: auto appear as "fixed") */
+.ui-tabs .ui-tabs-nav { margin: 0; padding: .2em .2em 0; }
+.ui-tabs .ui-tabs-nav li { list-style: none; float: left; position: relative; top: 1px; margin: 0 .2em 1px 0; border-bottom: 0 !important; padding: 0; white-space: nowrap; }
+.ui-tabs .ui-tabs-nav li a { float: left; padding: .5em 1em; text-decoration: none; }
+.ui-tabs .ui-tabs-nav li.ui-tabs-selected { margin-bottom: 0; padding-bottom: 1px; }
+.ui-tabs .ui-tabs-nav li.ui-tabs-selected a, .ui-tabs .ui-tabs-nav li.ui-state-disabled a, .ui-tabs .ui-tabs-nav li.ui-state-processing a { cursor: text; }
+.ui-tabs .ui-tabs-nav li a, .ui-tabs.ui-tabs-collapsible .ui-tabs-nav li.ui-tabs-selected a { cursor: pointer; } /* first selector in group seems obsolete, but required to overcome bug in Opera applying cursor: text overall if defined elsewhere... */
+.ui-tabs .ui-tabs-panel { display: block; border-width: 0; padding: 1em 1.4em; background: none; }
+.ui-tabs .ui-tabs-hide { display: none !important; }
+/*
+ * jQuery UI Datepicker 1.8.6
+ *
+ * Copyright 2010, AUTHORS.txt (http://jqueryui.com/about)
+ * Dual licensed under the MIT or GPL Version 2 licenses.
+ * http://jquery.org/license
+ *
+ * http://docs.jquery.com/UI/Datepicker#theming
+ */
+.ui-datepicker { width: 17em; padding: .2em .2em 0; }
+.ui-datepicker .ui-datepicker-header { position:relative; padding:.2em 0; }
+.ui-datepicker .ui-datepicker-prev, .ui-datepicker .ui-datepicker-next { position:absolute; top: 2px; width: 1.8em; height: 1.8em; }
+.ui-datepicker .ui-datepicker-prev-hover, .ui-datepicker .ui-datepicker-next-hover { top: 1px; }
+.ui-datepicker .ui-datepicker-prev { left:2px; }
+.ui-datepicker .ui-datepicker-next { right:2px; }
+.ui-datepicker .ui-datepicker-prev-hover { left:1px; }
+.ui-datepicker .ui-datepicker-next-hover { right:1px; }
+.ui-datepicker .ui-datepicker-prev span, .ui-datepicker .ui-datepicker-next span { display: block; position: absolute; left: 50%; margin-left: -8px; top: 50%; margin-top: -8px; }
+.ui-datepicker .ui-datepicker-title { margin: 0 2.3em; line-height: 1.8em; text-align: center; }
+.ui-datepicker .ui-datepicker-title select { font-size:1em; margin:1px 0; }
+.ui-datepicker select.ui-datepicker-month-year {width: 100%;}
+.ui-datepicker select.ui-datepicker-month,
+.ui-datepicker select.ui-datepicker-year { width: 49%;}
+.ui-datepicker table {width: 100%; font-size: .9em; border-collapse: collapse; margin:0 0 .4em; }
+.ui-datepicker th { padding: .7em .3em; text-align: center; font-weight: bold; border: 0; }
+.ui-datepicker td { border: 0; padding: 1px; }
+.ui-datepicker td span, .ui-datepicker td a { display: block; padding: .2em; text-align: right; text-decoration: none; }
+.ui-datepicker .ui-datepicker-buttonpane { background-image: none; margin: .7em 0 0 0; padding:0 .2em; border-left: 0; border-right: 0; border-bottom: 0; }
+.ui-datepicker .ui-datepicker-buttonpane button { float: right; margin: .5em .2em .4em; cursor: pointer; padding: .2em .6em .3em .6em; width:auto; overflow:visible; }
+.ui-datepicker .ui-datepicker-buttonpane button.ui-datepicker-current { float:left; }
+
+/* with multiple calendars */
+.ui-datepicker.ui-datepicker-multi { width:auto; }
+.ui-datepicker-multi .ui-datepicker-group { float:left; }
+.ui-datepicker-multi .ui-datepicker-group table { width:95%; margin:0 auto .4em; }
+.ui-datepicker-multi-2 .ui-datepicker-group { width:50%; }
+.ui-datepicker-multi-3 .ui-datepicker-group { width:33.3%; }
+.ui-datepicker-multi-4 .ui-datepicker-group { width:25%; }
+.ui-datepicker-multi .ui-datepicker-group-last .ui-datepicker-header { border-left-width:0; }
+.ui-datepicker-multi .ui-datepicker-group-middle .ui-datepicker-header { border-left-width:0; }
+.ui-datepicker-multi .ui-datepicker-buttonpane { clear:left; }
+.ui-datepicker-row-break { clear:both; width:100%; }
+
+/* RTL support */
+.ui-datepicker-rtl { direction: rtl; }
+.ui-datepicker-rtl .ui-datepicker-prev { right: 2px; left: auto; }
+.ui-datepicker-rtl .ui-datepicker-next { left: 2px; right: auto; }
+.ui-datepicker-rtl .ui-datepicker-prev:hover { right: 1px; left: auto; }
+.ui-datepicker-rtl .ui-datepicker-next:hover { left: 1px; right: auto; }
+.ui-datepicker-rtl .ui-datepicker-buttonpane { clear:right; }
+.ui-datepicker-rtl .ui-datepicker-buttonpane button { float: left; }
+.ui-datepicker-rtl .ui-datepicker-buttonpane button.ui-datepicker-current { float:right; }
+.ui-datepicker-rtl .ui-datepicker-group { float:right; }
+.ui-datepicker-rtl .ui-datepicker-group-last .ui-datepicker-header { border-right-width:0; border-left-width:1px; }
+.ui-datepicker-rtl .ui-datepicker-group-middle .ui-datepicker-header { border-right-width:0; border-left-width:1px; }
+
+/* IE6 IFRAME FIX (taken from datepicker 1.5.3 */
+.ui-datepicker-cover {
+ display: none; /*sorry for IE5*/
+ display/**/: block; /*sorry for IE5*/
+ position: absolute; /*must have*/
+ z-index: -1; /*must have*/
+ filter: mask(); /*must have*/
+ top: -4px; /*must have*/
+ left: -4px; /*must have*/
+ width: 200px; /*must have*/
+ height: 200px; /*must have*/
+}/*
+ * jQuery UI Progressbar 1.8.6
+ *
+ * Copyright 2010, AUTHORS.txt (http://jqueryui.com/about)
+ * Dual licensed under the MIT or GPL Version 2 licenses.
+ * http://jquery.org/license
+ *
+ * http://docs.jquery.com/UI/Progressbar#theming
+ */
+.ui-progressbar { height:2em; text-align: left; }
+.ui-progressbar .ui-progressbar-value {margin: -1px; height:100%; } \ No newline at end of file
diff --git a/themes/_administration/style.css b/themes/_administration/style.css
new file mode 100644
index 0000000000..2c7ecf7345
--- /dev/null
+++ b/themes/_administration/style.css
@@ -0,0 +1,282 @@
+/**
+ * Style for Admiistration pages
+ *
+ * Use this stylesheet to customize the display of your site.
+ * To learn more about CSS visit http://www.w3.org/TR/REC-CSS2/
+ *
+ * webtrees:Web based Family History software
+ * Copyright (C) 2010 webtrees development team.
+ *
+ * Derived from PhpGedView
+ * Copyright (C) 2002 to 2010 PGV Development Team. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ * @package webtrees
+ * @subpackage Administration
+ * @version $Id:admin_style.css 9851 2010-11-15 21:16:14Z nigel $
+ */
+
+/* general styles */
+body { font-size:12px; margin:10px;}
+.ui-widget-content a {text-decoration:none;}
+.ui-widget-content a.current {color:#E17009; font-weight:bold;}
+.ui-widget-content a:hover {color:red; cursor:hand; cursor:pointer;}
+.ui-widget-header {padding:5px;}
+.css_right {float:right;}
+input.ui-button { padding:1px 3px;}
+input, textarea { color:black; padding-left:3px; padding-right:3px;}
+fieldset {margin:8px 8px 4px 8px;}
+legend {font-style:italic; font-weight:bold; padding:0 5px 5px; align:top;}
+img {border:none;}
+.icon {border:none; padding-left:0pt;padding-right:5pt;}
+.warning, .error, .red { font-weight:bold; color:red;}
+.accepted { font-weight:bold; color:green;}
+.center {text-align:center;}
+.nowrap {white-space:nowrap;}
+.indent {padding-left:15px;}
+h3 {margin:0}
+h4 {margin: 5px 3px; font-weight:normal; font-style:italic;}
+
+/* HEADER */
+#admin_head {position:relative; height:70px; margin-bottom:10px; background-image: url("images/header.png"); background-repeat:no-repeat; background-position:top left;}
+#title {position:absolute; left:47%; top: 20px; font-size:18px; font-weight:bold;}
+#info {position:absolute; right:10px; bottom:0;text-align:right;}
+#links {position:absolute; left:10px; bottom:0; display:inline;margin-bottom:5px;}
+#links li {display:inline;position:relative;white-space:nowrap;}
+#links li ul {display:none; position:absolute;left:0px;top:1.5em; background: url("images/ui-bg_fine-grain_10_eceadf_60x60.png") repeat scroll 50% 50% #ECEADF; border: 1px solid #D9D6C4;z-index:999;}
+#links li ul li {display:block; padding: 0 5px;text-align:left;}
+#links, #links li ul {list-style:none;margin:0; padding:1px;}
+#links li:hover > ul {display:block;}
+
+/* SIDE and CONTENT panels */
+#admin_menu, #admin_content {min-height:720px;}
+
+/* SIDE MENU */
+#admin_menu {padding:10px 0 10px 5px; width:200px; float:left; position:absolute; color:#2E6E9E;}
+#admin_menu ul {list-style: none outside none; margin:0 0 0 -30px;}
+#admin_menu li ul {list-style: none outside none; margin:0 0 0 -20px;}
+#admin_menu li ul span {font-style: italic; font-weight:bold;}
+
+/* CONTENT */
+#admin_content {margin-left:220px; padding:10px; }
+
+/* FOOTER */
+#admin_footer {float:left; text-align:center; width:100%; margin-top:30px;}
+
+/* INDEX */
+#admin_index {}
+
+/* All DASHBOARD BLOCKS */
+#about, #block1 , #block2, #block3{background:OldLace; padding:5px; border:1px inset #D9D6C4;}
+#about h2, #block1 h2, #block2 h2, #block3 h2 {margin:0;}
+#tree_stats h3, #changes h3 {text-align:left; }
+#block1, #block2, #block3 {height:540px; overflow:auto; width:31%;}
+#block1 {float:left;}
+#block2 {margin: 0 auto;}
+#block3 {float:right;}
+#about {margin-bottom:10px;}
+#tree_stats th, #changes th {padding: 0 10px 0 0; text-align:left; white-space:nowrap; font-weight:normal;}
+#tree_stats td, #changes td {padding-right:10px; width:30px; text-align:right; white-space:nowrap;}
+#tree_stats .ui-accordion-content, #changes .ui-accordion-content {padding: 1em 0;}
+#tree_stats.ui-widget, #changes.ui-widget {font-size: 100% !important;}
+.ui-accordion .ui-accordion-content-active {
+ display: block;
+}
+
+/*SITE CONFIG */
+.editable {width:100%; display:block}
+.editable:hover {color:red; cursor:pointer; cursor:hand;} /* both cursors needed for cross-browser support */
+.editable input, select {padding:0;}
+.site_config {padding: 0;}/* table */
+.site_config th {text-align:left; padding:0 0 1px 3px;font-size:105%;} /* sub-headings, both cols */
+.site_config th+th { font-style:italic;font-size:95%;} /* sub-headings, right col */
+.site_config td {white-space:normal;padding:3px 3px 3px 30px;} /* left-col */
+.site_config td+td {background-color:#dcdcdc; width:250px; padding:3px;} /* right/col */
+
+/*SITE LOGS */
+.site_logs {padding: 0;}/* table */
+.site_logs td {background-color:#dcdcdc; width:250px; padding:3px; text-align:right;}
+.site_logs td+td {text-align:center;}
+table#log_list {width:100%}
+#log_list_length {float:left;}
+#log_list_filter {float:right;}
+#log_list_info {float:left;}
+#log_list_paginate {float:right;}
+
+/* USER List */
+table#list {width:100%}
+#list th {text-align:left;white-space:nowrap;}
+#list img {padding-left:15px; width:25px; vertical-align:middle;}
+#list img.open {width:16px; vertical-align:middle;}
+#list .yes_no img {width:16px; vertical-align:middle;}
+#list .name {padding-right:5px;}
+#list .description {font-size:10px; color:#666666; width:35%;}
+.dataTables_paginate {float:right;text-align:right;}
+.paging_full_numbers .fg-button {cursor:pointer;padding:2px 6px;}
+td.details {background-color:#D1CFD0;border:2px solid #A19B9E;}
+.edit_link {white-space:nowrap;}
+.edit_link:after {content:" " url("../images/edit.png");}
+.details td {vertical-align:top; border-right:1px solid #808080; padding-right:20px;}
+.details th {font-weight:bold; vertical-align:top; padding-left:20px;}
+.details ul {margin:0; padding:0 0 0 20px;}
+.odd {background-color:#dfd;}
+.even {background-color:#efe;}
+#list_length {float:left;}
+#list_filter {float:right;}
+#list_info {float:left;}
+#list_paginate {float:right;}
+#user-list_length {float:left;}
+#user-list_filter {float:right;}
+#user-list_info {float:left;}
+#user-list_paginate {float:right;}
+
+/* USER add */
+table#adduser {width:100%;}
+#adduser th {text-align:left; padding:30px 0 1px 3px;font-size:105%;}
+#adduser td, #adduser td+td+td {background-color:#eceadf;white-space:normal;padding:3px 3px 3px 10px;width:25%;} /* left-cols */
+#adduser td+td, #adduser td+td+td+td {background-color:#dcdcdc; width:250px; padding:3px;} /* right-cols */
+
+table#adduser2 {margin-left:30px; margin-bottom:20px; width:80%;}
+#adduser2 th {background-color:#dcdcdc; text-align:left; padding:0;font-size:100%;white-space:nowrap;}
+#adduser2 td, #adduser2 td+td, #adduser2 td+td+td, #adduser2 td+td+td+td {background-color:#eceadf; padding:0;width:auto;}
+#adduser2 td+td+td+td {text-align:center;}
+
+/* GEDCOM config tables */
+.facts_table {text-align:left;width:100%;}
+.config {}
+.descriptionbox {border-bottom:solid #81a9cb 1px;vertical-align:top;white-space:nowrap;padding:3px;background-color:#DFEFFC;}
+.optionbox { border-bottom:solid #81a9cb 1px;vertical-align:top;white-space:nowrap;padding:3px;}
+.subbar {background-color:#d1d9ef;border:solid #81a9cb 1px;font-size:11px;font-weight:bold;vertical-align:top;white-space:nowrap;padding:3px;}
+
+/* README */
+#readme {font-size:12px; padding:10px; }
+#readme h1 {font-size:16px; font-weight:bold; margin-top:0; }
+#readme h2 {font-size:14px; font-weight:bold; margin-top:0; }
+#readme h3 {font-size:14px; font-weight:bold; margin:14px 0; }
+#readme li {line-height:150%;}
+#readme .back {float:right;}
+
+/* PHP INFO */
+#phpinfo table.heading {padding:3px; text-align:center;width:100%;}
+
+/* MANAGE GEDCOMS */
+.gedcom_table th {font-weight:bold; font-size:14px; text-align:left; width: 15%}
+.gedcom_table th+th {width: 80%}
+.gedcom_table2 {margin-top: 20px; width:85%;}
+.gedcom_table2 th {font-weight:bold; font-size:14px; text-align:center;}
+.gedcom_table2 td {text-align:center;}
+
+/* help text pop-ups */
+.help {
+ cursor:help;
+}
+
+.helpcontent {
+ margin-left:10px;
+ margin-right:10px;
+}
+
+.helpfooter {
+ margin:10px;
+}
+
+.helpheader {
+ margin:10px;
+ font-size:18px;
+ font-weight:bold;
+ color:black;
+}
+
+.helpstart {
+ margin:10px;
+ font-size:14px;
+ color:blue;
+ text-decoration:none;
+ font-weight: bold;
+}
+
+.helptext {
+ font-size:12px;
+ color:black;
+}
+/*------------------------------------------------*/
+/* General Info page */
+
+
+/* GEDCOM management */
+
+table#gedcom_table {
+ border-bottom:solid #81a9cb 1px;
+ vertical-align:top;
+ white-space:nowrap;
+ padding:3px;
+ width:100%;
+}
+
+table#gedcom_table .gedcom {
+ font-weight:bold;
+ font-size:12px;
+ width:40%;
+}
+
+table#gedcom_table .defaults {
+ font-weight:bold;
+ font-size:12px;
+ text-align:center;
+ width:25%;
+ color:#666666;
+}
+
+table#site_config {
+ font-size:12px;
+}/
+
+
+.facts_table, .facts_table_rtl {
+ width:100%;
+}
+
+.facts_value {
+ border-bottom:solid #81a9cb 1px;
+ text-align:left;
+ padding:3px;
+}
+
+/* jQuery UI CSS customisations
+.css_right {float:right;}
+.ui-toolbar {padding:5px;}*/
+
+/* Text direction
+.ltr {text-align:left;}
+.rtl {text-align:right; direction:rtl;}*/
+
+/*#admin_content dt {
+ float:left;
+ width:300px;
+}
+
+#admin_content dd {
+ color: grey;
+ font-size:14px;
+ font-style:italic;
+ padding-top:3px;
+}*/
+
+/* Drag-n-drop sorting for modules, etc. */
+.sortme {
+ cursor: move;
+}
+
diff --git a/themes/_administration/theme.php b/themes/_administration/theme.php
new file mode 100644
index 0000000000..a64e9f44c2
--- /dev/null
+++ b/themes/_administration/theme.php
@@ -0,0 +1,70 @@
+<?php
+/**
+ * Standard theme
+ *
+ * webtrees: Web based Family History software
+ * Copyright (C) 2010 webtrees development team.
+ *
+ * Derived from PhpGedView
+ * Copyright (C) 2002 to 2009 PGV Development Team. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ * @package webtrees
+ * @subpackage Themes
+ * @version $Id: theme.php 9831 2010-11-13 04:43:15Z nigel $
+ */
+
+if (!defined('WT_WEBTREES')) {
+ header('HTTP/1.0 403 Forbidden');
+ exit;
+}
+
+$theme_name = "_administration"; // need double quotes, as file is scanned/parsed by script
+$stylesheet = WT_THEME_DIR.'style.css';
+$headerfile = WT_THEME_DIR.'header.php';
+$footerfile = WT_THEME_DIR.'footer.php';
+$WT_USE_HELPIMG = true;
+
+//- main icons
+$WT_IMAGES=array(
+ 'webtrees'=>WT_THEME_DIR.'images/webtrees_s.png',
+ 'help'=>WT_THEME_DIR.'images/help.jpg',
+ 'email'=>WT_THEME_DIR.'images/email.png',
+ 'open'=>WT_THEME_DIR.'images/open.png',
+ 'close'=>WT_THEME_DIR.'images/close.png',
+ 'button_indi'=>WT_THEME_DIR.'images/indi.gif',
+ 'zoomin'=>WT_THEME_DIR.'images/zoomin.gif', //added only to avoid error messages from print_header function
+ 'zoomout'=>WT_THEME_DIR.'images/zoomout.gif', //added only to avoid error messages from print_header function
+ 'minus'=>WT_THEME_DIR.'images/minus.gif', //added only to avoid error messages from print_header function
+ 'plus'=>WT_THEME_DIR.'images/plus.gif', //added only to avoid error messages from print_header function
+ 'rarrow2'=>WT_THEME_DIR.'images/rarrow2.gif', //added only to avoid error messages from print_header function
+ 'larrow2'=>WT_THEME_DIR.'images/larrow2.gif', //added only to avoid error messages from print_header function
+ 'darrow2'=>WT_THEME_DIR.'images/darrow2.gif', //added only to avoid error messages from print_header function
+ 'uarrow2'=>WT_THEME_DIR.'images/uarrow2.gif', //added only to avoid error messages from print_header function
+ // media images
+ 'media'=>WT_THEME_DIR.'images/media/media.png',
+ 'media_audio'=>WT_THEME_DIR.'images/media/audio.png',
+ 'media_doc'=>WT_THEME_DIR.'images/media/doc.png',
+ 'media_flash'=>WT_THEME_DIR.'images/media/flash.png',
+ 'media_ged'=>WT_THEME_DIR.'images/media/ged.png',
+ 'media_globe'=>WT_THEME_DIR.'images/media/globe.png',
+ 'media_html'=>WT_THEME_DIR.'images/media/html.pmg',
+ 'media_picasa'=>WT_THEME_DIR.'images/media/picasa.png',
+ 'media_pdf'=>WT_THEME_DIR.'images/media/pdf.png',
+ 'media_tex'=>WT_THEME_DIR.'images/media/tex.png',
+ 'media_wmv'=>WT_THEME_DIR.'images/media/wmv.png',
+);
+
diff --git a/themes/clouds/header.php b/themes/clouds/header.php
index 4be5c2d7c0..b03d426ac9 100644
--- a/themes/clouds/header.php
+++ b/themes/clouds/header.php
@@ -112,7 +112,7 @@ if (empty($SEARCH_SPIDER)) {
if (WT_USER_ID) {
echo '<a href="edituser.php" class="link">', i18n::translate('Logged in as '), ' (', WT_USER_NAME, ')</a><br />';
if (WT_USER_GEDCOM_ADMIN) {
- echo '<a href="admin.php" class="link">', i18n::translate('Administration'), '</a> | ';
+ echo '<a href="administration.php" class="link">', i18n::translate('Administration'), '</a> | ';
}
echo logout_link();
} elseif (empty($SEARCH_SPIDER)) {
diff --git a/themes/colors/header.php b/themes/colors/header.php
index e70b419402..b3a9179718 100644
--- a/themes/colors/header.php
+++ b/themes/colors/header.php
@@ -118,7 +118,7 @@ if ($view!='simple') {
'<li><a href="edituser.php" class="icon_color">', getUserFullName(WT_USER_ID), '</a></li>',
' | <li>', logout_link('class="icon_color"'), '</li>';
if (WT_USER_GEDCOM_ADMIN) {
- echo ' | <li><a href="admin.php" class="icon_color">', i18n::translate('Administration'), '</a></li>';
+ echo ' | <li><a href="administration.php" class="icon_color">', i18n::translate('Administration'), '</a></li>';
}
if (WT_USER_CAN_ACCEPT && exists_pending_change()) {
echo ' | <li><a href="javascript:;" onclick="window.open(\'edit_changes.php\',\'_blank\',\'width=600,height=500,resizable=1,scrollbars=1\'); return false;" style="color:red;">', i18n::translate('Pending changes'), '</a></li>';
diff --git a/themes/fab/header.php b/themes/fab/header.php
index 429f14fd82..7eda69033d 100644
--- a/themes/fab/header.php
+++ b/themes/fab/header.php
@@ -98,7 +98,7 @@ if ($view!='simple') { // Use "simple" headers for popup windows
'<li><a href="edituser.php" class="link">', getUserFullName(WT_USER_ID), '</a></li>',
' | <li>', logout_link(), '</li>';
if (WT_USER_GEDCOM_ADMIN) {
- echo ' | <li><a href="admin.php" class="link">', i18n::translate('Administration'), '</a></li>';
+ echo ' | <li><a href="administration.php" class="link">', i18n::translate('Administration'), '</a></li>';
}
if (WT_USER_CAN_ACCEPT && exists_pending_change()) {
echo ' | <li><a href="javascript:;" onclick="window.open(\'edit_changes.php\',\'_blank\',\'width=600,height=500,resizable=1,scrollbars=1\'); return false;" style="color:red;">', i18n::translate('Pending changes'), '</a></li>';
diff --git a/themes/minimal/header.php b/themes/minimal/header.php
index a931897ac3..72ff0d117c 100644
--- a/themes/minimal/header.php
+++ b/themes/minimal/header.php
@@ -96,7 +96,7 @@ if (!defined('WT_WEBTREES')) {
if (WT_USER_ID) {
echo '<a href="edituser.php" class="link">', i18n::translate('Logged in as '), ' (', WT_USER_NAME, ')</a><br />';
if (WT_USER_GEDCOM_ADMIN) {
- echo '<a href="admin.php" class="link">', i18n::translate('Administration'), '</a> | ';
+ echo '<a href="administration.php" class="link">', i18n::translate('Administration'), '</a> | ';
}
echo logout_link();
} elseif (empty($SEARCH_SPIDER)) {
diff --git a/themes/webtrees/header.php b/themes/webtrees/header.php
index 974bfc9725..a053034c9b 100644
--- a/themes/webtrees/header.php
+++ b/themes/webtrees/header.php
@@ -96,7 +96,7 @@ if ($view!='simple') {
if (WT_USER_ID) {
echo '<a href="edituser.php" class="link">', i18n::translate('Logged in as '), ' (', WT_USER_NAME, ')</a><br />';
if (WT_USER_GEDCOM_ADMIN) {
- echo '<a href="admin.php" class="link">', i18n::translate('Administration'), '</a> | ';
+ echo '<a href="administration.php" class="link">', i18n::translate('Administration'), '</a> | ';
}
echo logout_link();
} elseif (empty($SEARCH_SPIDER)) {
diff --git a/themes/xenea/header.php b/themes/xenea/header.php
index c60320443e..3203d5fa18 100644
--- a/themes/xenea/header.php
+++ b/themes/xenea/header.php
@@ -133,7 +133,7 @@ $displayDate=timestamp_to_gedcom_date(client_time())->Display(false, $DATE_FORMA
if (WT_USER_ID) {
echo '<a href="edituser.php" class="link">', i18n::translate('Logged in as '), ' (', WT_USER_NAME, ')</a><br />';
if (WT_USER_GEDCOM_ADMIN) {
- echo '<a href="admin.php" class="link">', i18n::translate('Administration'), '</a> | ';
+ echo '<a href="administration.php" class="link">', i18n::translate('Administration'), '</a> | ';
}
echo logout_link();
} elseif (empty($SEARCH_SPIDER)) {