diff --git a/admin/inc/class.admin_prefs_sidebox_hooks.inc.php b/admin/inc/class.admin_prefs_sidebox_hooks.inc.php new file mode 100644 index 0000000000..be205891dd --- /dev/null +++ b/admin/inc/class.admin_prefs_sidebox_hooks.inc.php @@ -0,0 +1,172 @@ + + * @author Ralf Becker + * @package admin + * @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License + * @version $Id$ + */ + +/** + * Static hooks for admin application + */ +class admin_prefs_sidebox_hooks +{ + /** + * Functions callable via menuaction + * + * @var unknown_type + */ + var $public_functions = array( + 'register_all_hooks' => True, + 'fsck' => true, + ); + + /** + * hooks to build projectmanager's sidebox-menu plus the admin and preferences sections + * + * @param string/array $args hook args + */ + static function all_hooks($args) + { + if (!isset($_GET['menuaction']) && substr($_SERVER['PHP_SELF'],-16) == '/admin/index.php') + { + admin_statistics::check(); + } + $appname = 'admin'; + $location = is_array($args) ? $args['location'] : $args; + + if ($GLOBALS['egw_info']['user']['apps']['admin'] && $location != 'admins') + { + + if (! $GLOBALS['egw']->acl->check('site_config_access',1,'admin')) + { + $file['Site Configuration'] = egw::link('/index.php','menuaction=admin.uiconfig.index&appname=admin'); + } + + /* disabled it, til it does something useful + if (! $GLOBALS['egw']->acl->check('peer_server_access',1,'admin')) + { + $file['Peer Servers'] = egw::link('/index.php','menuaction=admin.uiserver.list_servers'); + } + */ + if (! $GLOBALS['egw']->acl->check('account_access',1,'admin')) + { + $file['User Accounts'] = egw::link('/index.php','menuaction=admin.uiaccounts.list_users'); + } + + if (! $GLOBALS['egw']->acl->check('group_access',1,'admin')) + { + $file['User Groups'] = egw::link('/index.php','menuaction=admin.uiaccounts.list_groups'); + } + + if (! $GLOBALS['egw']->acl->check('applications_access',1,'admin')) + { + $file['Applications'] = egw::link('/index.php','menuaction=admin.admin_applications.index'); + } + if (! $GLOBALS['egw']->acl->check('global_categories_access',1,'admin')) + { + $file['Global Categories'] = egw::link('/index.php','menuaction=admin.admin_categories.index&appname=phpgw'); + } + + if (!$GLOBALS['egw']->acl->check('mainscreen_message_access',1,'admin') || !$GLOBALS['egw']->acl->check('mainscreen_message_access',2,'admin')) + { + $file['Change Main Screen Message'] = egw::link('/index.php','menuaction=admin.uimainscreen.index'); + } + + if (! $GLOBALS['egw']->acl->check('current_sessions_access',1,'admin')) + { + $file['View Sessions'] = egw::link('/index.php','menuaction=admin.uicurrentsessions.list_sessions'); + } + + if (! $GLOBALS['egw']->acl->check('access_log_access',1,'admin')) + { + $file['View Access Log'] = egw::link('/index.php','menuaction=admin.admin_accesslog.index'); + } + + if (! $GLOBALS['egw']->acl->check('error_log_access',1,'admin')) + { + $file['View Error Log'] = egw::link('/index.php','menuaction=admin.uilog.list_log'); + } + + if (! $GLOBALS['egw']->acl->check('applications_access',16,'admin')) + { + $file['Find and Register all Application Hooks'] = egw::link('/index.php','menuaction=admin.admin_prefs_sidebox_hooks.register_all_hooks'); + } + + //if (! $GLOBALS['egw']->acl->check('applications_access',16,'admin')) + { + $file['Check virtual filesystem'] = egw::link('/index.php','menuaction=admin.admin_prefs_sidebox_hooks.fsck'); + } + + if (! $GLOBALS['egw']->acl->check('asyncservice_access',1,'admin')) + { + $file['Asynchronous timed services'] = egw::link('/index.php','menuaction=admin.uiasyncservice.index'); + } + + if (! $GLOBALS['egw']->acl->check('db_backup_access',1,'admin')) + { + $file['DB backup and restore'] = egw::link('/index.php','menuaction=admin.admin_db_backup.index'); + } + + if (! $GLOBALS['egw']->acl->check('info_access',1,'admin')) + { + $file['phpInfo'] = "javascript:openwindow('" . egw::link('/admin/phpinfo.php') . "')"; //egw::link('/admin/phpinfo.php'); + } + $file['Admin queue and history'] = egw::link('/index.php','menuaction=admin.admin_cmds.index'); + $file['Remote administration instances'] = egw::link('/index.php','menuaction=admin.admin_cmds.remotes'); + + $file['Submit statistic information'] = egw::link('/index.php','menuaction=admin.admin_statistics.submit'); + + if ($location == 'admin') + { + display_section($appname,$file); + } + else + { + display_sidebox($appname,lang('Admin'),$file); + } + } + } + + /** + * Register all hooks + */ + function register_all_hooks() + { + if ($GLOBALS['egw']->acl->check('applications_access',16,'admin')) + { + $GLOBALS['egw']->redirect_link('/index.php'); + } + $GLOBALS['egw']->hooks->register_all_hooks(); + + if (method_exists($GLOBALS['egw'],'invalidate_session_cache')) // egw object in setup is limited + { + $GLOBALS['egw']->invalidate_session_cache(); // in case with cache the egw_info array in the session + } + $GLOBALS['egw']->redirect_link('/admin/index.php'); + } + + /** + * Run fsck on sqlfs + */ + function fsck() + { + $check_only = !isset($_POST['fix']); + + if (!($msgs = sqlfs_utils::fsck($check_only))) + { + $msgs = lang('Filesystem check reported no problems.'); + } + $content = '

'.implode("

\n

", (array)$msgs)."

\n"; + + $content .= html::form('

'.($check_only&&is_array($msgs)?html::submit_button('fix', lang('Fix reported problems')):''). + html::submit_button('cancel', lang('Cancel'), "window.location.href='".egw::link('/admin/index.php')."'; return false;").'

', + '',egw::link('/index.php',array('menuaction'=>'admin.admin_prefs_sidebox_hooks.fsck'))); + + $GLOBALS['egw']->framework->render($content, lang('Admin').' - '.lang('Check virtual filesystem'), true); + } +} diff --git a/admin/lang/egw_de.lang b/admin/lang/egw_de.lang index e543825070..79d6d24179 100644 --- a/admin/lang/egw_de.lang +++ b/admin/lang/egw_de.lang @@ -111,6 +111,8 @@ change password for %1 admin de Ändern des Passworts für %1 check acl for entries of not (longer) existing accounts admin de Prüfe ACL Einträge auf Bezüge zu nicht (mehr) existierenden Benutzerkonten check ip address of all sessions admin de IP-Adresse für alle Sessions überprüfen check items to %1 to %2 for %3 admin de Durch Abhaken %3 in %2 %1 +check virtual filesystem admin de Virtuelles Dateisystem überprüfen +children admin de Kinder click to select a color admin de Anclicken um eine Farbe auszuwählen color admin de Farbe command scheduled to run at %1 admin de Ausführung des Befehls eingeplant am/um %1 @@ -251,6 +253,7 @@ false admin de Falsch field '%1' already exists !!! admin de Feld '%1' existiert bereits !!! file space admin de Speicherplatz file space must be an integer admin de Speicherplatz muss eine Zahl sein +filesystem check reported no problems. admin de Überprüfung des Dateisystem ergab keine Probleme. find and register all application hooks admin de Suchen und registrieren der "Hooks" aller Anwendungen for the times above admin de für die oben angegebenen Zeiten for the times below (empty values count as '*', all empty = every minute) admin de für die darunter angegebenen Zeiten (leere Felder zählen als "*", alles leer = jede Minute) diff --git a/admin/lang/egw_en.lang b/admin/lang/egw_en.lang index d1e35bbf5a..a09483bbeb 100644 --- a/admin/lang/egw_en.lang +++ b/admin/lang/egw_en.lang @@ -113,6 +113,8 @@ change password for %1 admin en change password for %1 check acl for entries of not (longer) existing accounts admin en Check ACL for entries of not (longer) existing accounts check ip address of all sessions admin en Check IP address of all sessions check items to %1 to %2 for %3 admin en Check items to %1 to %2 for %3 +check virtual filesystem admin en Check virtual filesystem +children admin en Children click to select a color admin en Click to select a color color admin en Color command scheduled to run at %1 admin en Command scheduled to run at %1 @@ -253,14 +255,18 @@ false admin en false field '%1' already exists !!! admin en Field '%1' already exists !!! file space admin en File space file space must be an integer admin en File space must be an integer -find and register all application hooks admin en Find and Register all Application Hooks -for the times above admin en for the times above -for the times below (empty values count as '*', all empty = every minute) admin en for the times below (empty values count as '*', all empty = every minute) -force selectbox admin en Force Selectbox -forward also to admin en forward also to -forward emails to admin en forward emails to -forward only admin en forward only -global categories common en Global Categories +filesystem check reported no problems. admin en Filesystem check reported no problems. +find and register all application hooks admin en Find and register all application hooks +for the times above admin en For the times above +for the times below (empty values count as '*', all empty = every minute) admin en For the times below: empty values count as '*', all empty = every minute. +force password strength (1-5, default empty: no check against rules for a strong password)? admin en Set required password strength. 1 = weak, up to 5 = very strong. Default = empty, no password strength checked +force selectbox admin en Force select box +force users to change their password regularily?(empty for no,number for after that number of days admin en Set recurrent forced password change. Set a number of days. Empty = No +forward also to admin en Forward also to +forward emails to admin en Forward emails to +forward only admin en Forward only +full name admin en Full name +global categories common en Global categories go directly to admin menu, returning here the next time you click on administration. admin en Go directly to admin menu, returning here the next time you click on administration. governmental: incl. state or municipal authorities or services admin en Governmental: incl. state or municipal authorities or services grant admin en Grant diff --git a/filemanager/cli.php b/filemanager/cli.php index 66564f4c17..d0abf5b86e 100755 --- a/filemanager/cli.php +++ b/filemanager/cli.php @@ -267,7 +267,7 @@ switch($cmd) } die("\n/ NOT mounted with 'storage=db' --> no need to convert!\n\n"); } - $num_files = sqlfs_stream_wrapper::migrate_db2fs(); // throws exception on error + $num_files = sqlfs_utils::migrate_db2fs(); // throws exception on error echo "\n$num_files files migrated from DB to filesystem.\n"; $new_url = preg_replace('/storage=db&?/','',$fstab['/']); if (substr($new_url,-1) == '?') $new_url = substr($new_url,0,-1); diff --git a/phpgwapi/inc/class.sqlfs_stream_wrapper.inc.php b/phpgwapi/inc/class.sqlfs_stream_wrapper.inc.php index eec0d432e0..21dedaa788 100644 --- a/phpgwapi/inc/class.sqlfs_stream_wrapper.inc.php +++ b/phpgwapi/inc/class.sqlfs_stream_wrapper.inc.php @@ -1,13 +1,13 @@ - * @copyright (c) 2008-9 by Ralf Becker + * @copyright (c) 2008-12 by Ralf Becker * @version $Id$ */ @@ -700,7 +700,7 @@ class sqlfs_stream_wrapper implements iface_stream_wrapper unset(self::$stat_cache[$path]); $stmt = self::$pdo->prepare('INSERT INTO '.self::TABLE.' (fs_name,fs_dir,fs_mode,fs_uid,fs_gid,fs_size,fs_mime,fs_created,fs_modified,fs_creator'. ') VALUES (:fs_name,:fs_dir,:fs_mode,:fs_uid,:fs_gid,:fs_size,:fs_mime,:fs_created,:fs_modified,:fs_creator)'); - return $stmt->execute(array( + if (($ok = $stmt->execute(array( 'fs_name' => egw_vfs::basename($path), 'fs_dir' => $parent['ino'], 'fs_mode' => $parent['mode'], @@ -711,7 +711,25 @@ class sqlfs_stream_wrapper implements iface_stream_wrapper 'fs_created' => self::_pdo_timestamp(time()), 'fs_modified' => self::_pdo_timestamp(time()), 'fs_creator' => egw_vfs::$user, - )); + )))) + { + // check if some other process created the directory parallel to us (sqlfs would gives SQL errors later!) + $new_fs_id = self::$pdo->lastInsertId('egw_sqlfs_fs_id_seq'); + + unset($stmt); // free statement object, on some installs a new prepare fails otherwise! + + $stmt = self::$pdo->prepare($q='SELECT COUNT(*) FROM '.self::TABLE. + ' WHERE fs_dir=:fs_dir AND fs_active=:fs_active AND fs_name'.self::$case_sensitive_equal.':fs_name'); + if ($stmt->execute(array( + 'fs_dir' => $parent['ino'], + 'fs_active' => self::_pdo_boolean(true), + 'fs_name' => egw_vfs::basename($path), + )) && $stmt->fetchColumn() > 1) // if there's more then one --> remove our new dir + { + self::$pdo->query('DELETE FROM '.self::TABLE.' WHERE fs_id='.$new_fs_id); + } + } + return $ok; } /** @@ -1794,80 +1812,6 @@ class sqlfs_stream_wrapper implements iface_stream_wrapper if (self::LOG_LEVEL > 1) foreach((array)$props as $k => $v) error_log(__METHOD__."($path_ids,$ns) $k => ".array2string($v)); return $props; } - - /** - * Migrate SQLFS content from DB to filesystem - * - * @param boolean $debug true to echo a message for each copied file - */ - static function migrate_db2fs($debug=false) - { - if (!is_object(self::$pdo)) - { - self::_pdo(); - } - $query = 'SELECT fs_id,fs_name,fs_size,fs_content'. - ' FROM '.self::TABLE.' WHERE fs_content IS NOT NULL'; - - $stmt = self::$pdo->prepare($query); - $stmt->bindColumn(1,$fs_id); - $stmt->bindColumn(2,$fs_name); - $stmt->bindColumn(3,$fs_size); - $stmt->bindColumn(4,$fs_content,PDO::PARAM_LOB); - - if ($stmt->execute()) - { - foreach($stmt as $row) - { - // hack to work around a current php bug (http://bugs.php.net/bug.php?id=40913) - // PDOStatement::bindColumn(,,PDO::PARAM_LOB) is not working for MySQL, content is returned as string :-( - if (is_string($fs_content)) - { - $name = md5($fs_name.$fs_id); - $GLOBALS[$name] =& $fs_content; - require_once(EGW_API_INC.'/class.global_stream_wrapper.inc.php'); - $content = fopen('global://'.$name,'r'); - if (!$content) echo "fopen('global://$name','w' failed, strlen(\$GLOBALS['$name'])=".strlen($GLOBALS[$name]).", \$GLOBALS['$name']=".substr($GLOBALS['$name'],0,100)."...\n"; - unset($GLOBALS[$name]); // unset it, so it does not use up memory, once the stream is closed - } - else - { - $content = $fs_content; - } - if (!is_resource($content)) - { - throw new egw_exception_assertion_failed(__METHOD__."(): fs_id=$fs_id ($fs_name, $fs_size bytes) content is NO resource! ".array2string($content)); - } - $filename = self::_fs_path($fs_id); - if (!file_exists($fs_dir=egw_vfs::dirname($filename))) - { - self::mkdir_recursive($fs_dir,0700,true); - } - if (!($dest = fopen($filename,'w'))) - { - throw new egw_exception_assertion_failed(__METHOD__."(): fopen($filename,'w') failed!"); - } - if (($bytes = stream_copy_to_stream($content,$dest)) != $fs_size) - { - throw new egw_exception_assertion_failed(__METHOD__."(): fs_id=$fs_id ($fs_name) $bytes bytes copied != size of $fs_size bytes!"); - } - if ($debug) echo "$fs_id: $fs_name: $bytes bytes copied to fs\n"; - fclose($dest); - fclose($content); unset($content); - - ++$n; - } - unset($stmt); - - if ($n) // delete all content in DB, if there was some AND no error (exception thrown!) - { - $query = 'UPDATE '.self::TABLE.' SET fs_content=NULL WHERE fs_content IS NOT NULL'; - $stmt = self::$pdo->prepare($query); - $stmt->execute(); - } - } - return $n; - } } stream_register_wrapper(sqlfs_stream_wrapper::SCHEME ,'sqlfs_stream_wrapper'); diff --git a/phpgwapi/inc/class.sqlfs_utils.inc.php b/phpgwapi/inc/class.sqlfs_utils.inc.php new file mode 100644 index 0000000000..b4bc41518c --- /dev/null +++ b/phpgwapi/inc/class.sqlfs_utils.inc.php @@ -0,0 +1,352 @@ + + * @copyright (c) 2008-12 by Ralf Becker + * @version $Id$ + */ + +require_once 'class.iface_stream_wrapper.inc.php'; +require_once 'class.sqlfs_stream_wrapper.inc.php'; + +/** + * sqlfs stream wrapper utilities: migration db-fs, fsck + */ +class sqlfs_utils extends sqlfs_stream_wrapper +{ + /** + * Migrate SQLFS content from DB to filesystem + * + * @param boolean $debug true to echo a message for each copied file + */ + static function migrate_db2fs($debug=false) + { + if (!is_object(self::$pdo)) + { + self::_pdo(); + } + $query = 'SELECT fs_id,fs_name,fs_size,fs_content'. + ' FROM '.self::TABLE.' WHERE fs_content IS NOT NULL'; + + $stmt = self::$pdo->prepare($query); + $stmt->bindColumn(1,$fs_id); + $stmt->bindColumn(2,$fs_name); + $stmt->bindColumn(3,$fs_size); + $stmt->bindColumn(4,$fs_content,PDO::PARAM_LOB); + + if ($stmt->execute()) + { + foreach($stmt as $row) + { + // hack to work around a current php bug (http://bugs.php.net/bug.php?id=40913) + // PDOStatement::bindColumn(,,PDO::PARAM_LOB) is not working for MySQL, content is returned as string :-( + if (is_string($fs_content)) + { + $name = md5($fs_name.$fs_id); + $GLOBALS[$name] =& $fs_content; + require_once(EGW_API_INC.'/class.global_stream_wrapper.inc.php'); + $content = fopen('global://'.$name,'r'); + if (!$content) echo "fopen('global://$name','w' failed, strlen(\$GLOBALS['$name'])=".strlen($GLOBALS[$name]).", \$GLOBALS['$name']=".substr($GLOBALS['$name'],0,100)."...\n"; + unset($GLOBALS[$name]); // unset it, so it does not use up memory, once the stream is closed + } + else + { + $content = $fs_content; + } + if (!is_resource($content)) + { + throw new egw_exception_assertion_failed(__METHOD__."(): fs_id=$fs_id ($fs_name, $fs_size bytes) content is NO resource! ".array2string($content)); + } + $filename = self::_fs_path($fs_id); + if (!file_exists($fs_dir=egw_vfs::dirname($filename))) + { + self::mkdir_recursive($fs_dir,0700,true); + } + if (!($dest = fopen($filename,'w'))) + { + throw new egw_exception_assertion_failed(__METHOD__."(): fopen($filename,'w') failed!"); + } + if (($bytes = stream_copy_to_stream($content,$dest)) != $fs_size) + { + throw new egw_exception_assertion_failed(__METHOD__."(): fs_id=$fs_id ($fs_name) $bytes bytes copied != size of $fs_size bytes!"); + } + if ($debug) echo "$fs_id: $fs_name: $bytes bytes copied to fs\n"; + fclose($dest); + fclose($content); unset($content); + + ++$n; + } + unset($stmt); + + if ($n) // delete all content in DB, if there was some AND no error (exception thrown!) + { + $query = 'UPDATE '.self::TABLE.' SET fs_content=NULL WHERE fs_content IS NOT NULL'; + $stmt = self::$pdo->prepare($query); + $stmt->execute(); + } + } + return $n; + } + + /** + * Check and optionaly fix corruption in sqlfs + * + * @param boolean $check_only=true + * @return array with messages / found problems + */ + public static function fsck($check_only=true) + { + if (!is_object(self::$pdo)) + { + self::_pdo(); + } + $msgs = self::fsck_fix_multiple_active($check_only); + $msgs = array_merge($msgs, self::fsck_fix_unconnected($check_only)); + $msgs = array_merge($msgs, self::fsck_fix_no_content($check_only)); + + return $msgs; + } + + /** + * Check and optionally remove files without content part in physical filesystem + * + * @param boolean $check_only=true + * @return array with messages / found problems + */ + private static function fsck_fix_no_content($check_only=true) + { + $msgs = array(); + + foreach(self::$pdo->query('SELECT fs_id FROM '.self::TABLE. + " WHERE fs_mime!='httpd/unix-directory' AND fs_content IS NULL AND fs_link IS NULL") as $row) + { + if (!file_exists($phy_path=self::_fs_path($row['fs_id']))) + { + egw_vfs::$is_root = true; + $path = self::id2path($row['fs_id']); + if ($check_only) + { + $msgs[] = lang('File %1 has no content in physical filesystem %2!', + $path.' (#'.$row['fs_id'].')',$phy_path); + } + elseif (self::unlink($path.'?storage=db')) // storage=db to not try to delete not existing phy. file + { + $msgs[] = lang('File %1 has no content in physical filesystem %2 --> file removed!',$path,$phy_path); + } + else + { + $msgs[] = lang('File %1 has no content in physical filesystem %2 --> failed to remove file!', + $path.' (#'.$row['fs_id'].')',$phy_path); + } + egw_vfs::$is_root = false; + } + } + if ($check_only && $msgs) + { + $msgs[] = lang('Files without content in physical filesystem will be removed.'); + } + return $msgs; + } + + /** + * Name of lost+found directory for unconnected nodes + */ + const LOST_N_FOUND = '/lost+found'; + const LOST_N_FOUND_MOD = 070; + const LOST_N_FOUND_GRP = 'Admins'; + + /** + * Check and optionally fix unconnected nodes - parent directory does not (longer) exists: + * + * SELECT fs.* + * FROM egw_sqlfs fs + * LEFT JOIN egw_sqlfs dir ON dir.fs_id=fs.fs_dir + * WHERE fs.fs_id > 1 && dir.fs_id IS NULL + * + * @param boolean $check_only=true + * @return array with messages / found problems + */ + private static function fsck_fix_unconnected($check_only=true) + { + $msgs = array(); + foreach(self::$pdo->query('SELECT fs.* FROM '.self::TABLE.' fs'. + ' LEFT JOIN '.self::TABLE.' dir ON dir.fs_id=fs.fs_dir'. + ' WHERE fs.fs_id > 1 && dir.fs_id IS NULL') as $row) + { + if ($check_only) + { + $msgs[] = lang('Found unconnected %1 %2!', + mime_magic::mime2label($row['fs_mime']), + egw_vfs::decodePath($row['fs_name']).' (#'.$row['fs_id'].')'); + continue; + } + if (!isset($lostnfound)) + { + // check if we already have /lost+found, create it if not + if (!($lostnfound = self::url_stat(self::LOST_N_FOUND, STREAM_URL_STAT_QUIET))) + { + egw_vfs::$is_root = true; + if (!self::mkdir(self::LOST_N_FOUND, self::LOST_N_FOUND_MOD, 0) || + !(!($admins = $GLOBALS['egw']->accounts->name2id(self::LOST_N_FOUND_GRP)) || + self::chgrp(self::LOST_N_FOUND, $admins) && self::chmod(self::LOST_N_FOUND,self::LOST_N_FOUND_MOD)) || + !($lostnfound = self::url_stat(self::LOST_N_FOUND, STREAM_URL_STAT_QUIET))) + { + $msgs[] = lang("Can't create directory %1 to connect found unconnected nodes to it!",self::LOST_N_FOUND); + } + else + { + $msgs[] = lang('Successful created new directory %1 for unconnected nods.',self::LOST_N_FOUND); + } + egw_vfs::$is_root = false; + if (!$lostnfound) break; + } + $stmt = self::$pdo->prepare('UPDATE '.self::TABLE.' SET fs_dir=:fs_dir WHERE fs_id=:fs_id'); + } + if ($stmt->execute(array( + 'fs_dir' => $lostnfound['ino'], + 'fs_id' => $row['fs_id'], + ))) + { + $msgs[] = lang('Moved unconnected %1 %2 to %3.', + mime_magic::mime2label($row['fs_mime']), + egw_vfs::decodePath($row['fs_name']).' (#'.$row['fs_id'].')', + self::LOST_N_FOUND); + } + else + { + $msgs[] = lang('Faild to move unconnected %1 %2 to %3!', + mime_magic::mime2label($row['fs_mime']), egw_vfs::decodePath($row['fs_name']), self::LOST_N_FOUND); + } + } + if ($check_only && $msgs) + { + $msgs[] = lang('Unconnected nodes will be moved to %1.',self::LOST_N_FOUND); + continue; + } + return $msgs; + } + + /** + * Check and optionally fix multiple active files and directories with identical path + * + * @param boolean $check_only=true + * @return array with messages / found problems + */ + private static function fsck_fix_multiple_active($check_only=true) + { + $msgs = array(); + foreach(self::$pdo->query('SELECT fs_dir,fs_name,COUNT(*) FROM '.self::TABLE. + ' WHERE fs_active='.self::_pdo_boolean(true). + ' GROUP BY fs_dir,fs_name'. + ' HAVING COUNT(*) > 1') as $row) + { + if (!isset($stmt)) + { + $stmt = self::$pdo->prepare('SELECT *,(SELECT COUNT(*) FROM '.self::TABLE.' sub WHERE sub.fs_dir=fs.fs_id) AS children'. + ' FROM '.self::TABLE.' fs'. + ' WHERE fs.fs_dir=:fs_dir AND fs.fs_active='.self::_pdo_boolean(true).' AND fs.fs_name'.self::$case_sensitive_equal.':fs_name'. + " ORDER BY fs.fs_mime='httpd/unix-directory' DESC,children DESC,fs.fs_modified DESC"); + $inactivate_stmt = self::$pdo->prepare('UPDATE '.self::TABLE. + ' SET fs_active='.self::_pdo_boolean(false). + ' WHERE fs_dir=:fs_dir AND fs_active='.self::_pdo_boolean(true). + ' AND fs_name'.self::$case_sensitive_equal.':fs_name AND fs_id!=:fs_id'); + } + //$msgs[] = array2string($row); + $cnt = 0; + $stmt->execute(array( + 'fs_dir' => $row['fs_dir'], + 'fs_name' => $row['fs_name'], + )); + foreach($stmt as $entry) + { + if ($entry['fs_mime'] == 'httpd/unix-directory') + { + if (!$n) + { + $dir = $entry; // directory to keep + $msgs[] = lang('%1 directories %2 found!', $row[2], self::id2path($entry['fs_id'])); + if ($check_only) break; + } + else + { + if ($entry['children']) + { + $msgs[] = lang('Moved %1 children from directory fs_id=%2 to %3', + $children = self::$pdo->exec('UPDATE '.self::TABLE.' SET fs_dir='.(int)$dir['fs_id']. + ' WHERE fs_dir='.(int)$entry['fs_id']), + $entry['fs_id'], $dir['fs_id']); + + $dir['children'] += $children; + } + self::$pdo->query('DELETE FROM '.self::TABLE.' WHERE fs_id='.(int)$entry['fs_id']); + $msgs[] = lang('Removed (now) empty directory fs_id=%1',$entry['fs_id']); + } + } + elseif (isset($dir)) // file and directory with same name exist! + { + if (!$check_only) + { + $inactivate_stmt->execute(array( + 'fs_dir' => $row['fs_dir'], + 'fs_name' => $row['fs_name'], + 'fs_id' => $dir['fs_id'], + )); + $cnt = $inactivate_stmt->rowCount(); + } + else + { + $cnt = ucfirst(lang('none of %1', $row[2]-1)); + } + $msgs[] = lang('%1 active file(s) with same name as directory inactivated!',$cnt); + break; + } + else // newest file --> set for all other fs_active=false + { + if (!$check_only) + { + $inactivate_stmt->execute(array( + 'fs_dir' => $row['fs_dir'], + 'fs_name' => $row['fs_name'], + 'fs_id' => $entry['fs_id'], + )); + $cnt = $inactivate_stmt->rowCount(); + } + else + { + $cnt = lang('none of %1', $row[2]-1); + } + $msgs[] = lang('More then one active file %1 found, inactivating %2 older revisions!', + self::id2path($entry['fs_id']), $cnt); + break; + } + } + unset($dir); + if ($cnt && !isset($inactivate_msg_added)) + { + $msgs[] = lang('To examine or reinstate inactived files, you might need to turn versioning on.'); + $inactivate_msg_added = true; + } + } + return $msgs; + } +} + +// fsck testcode, if this file is called via it's URL (you need to uncomment it!) +/*if (isset($_SERVER['SCRIPT_FILENAME']) && $_SERVER['SCRIPT_FILENAME'] == __FILE__) +{ + $GLOBALS['egw_info'] = array( + 'flags' => array( + 'currentapp' => 'admin', + 'nonavbar' => true, + ), + ); + include_once '../../header.inc.php'; + + $msgs = sqlfs_utils::fsck(!isset($_GET['check_only']) || $_GET['check_only']); + echo '

'.implode("

\n

", (array)$msgs)."

\n"; +}*/ \ No newline at end of file