From b88102618b2d3c38fe0164135582a76c3f54a24b Mon Sep 17 00:00:00 2001 From: Hadi Nategh Date: Tue, 7 Oct 2014 16:21:19 +0000 Subject: [PATCH 01/97] Fix calendar entries mess up after editing an integrated entry (eg. infolog) while the calendar is not the active tab (happens only in F.F.) --- calendar/js/app.js | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/calendar/js/app.js b/calendar/js/app.js index 4ab04ba118..a0f4fce8e5 100644 --- a/calendar/js/app.js +++ b/calendar/js/app.js @@ -85,7 +85,7 @@ app.classes.calendar = AppJS.extend( this._init_sidebox(sidebox); var content = this.et2.getArrayMgr('content'); - + switch (_name) { case 'calendar.list': @@ -173,7 +173,20 @@ app.classes.calendar = AppJS.extend( } else { - window.location.reload(); + var iframe = parent.jQuery(parent.document).find('.egw_fw_content_browser_iframe'); + var calTab = iframe.parentsUntil(jQuery('.egw_fw_ui_tab_content'),'.egw_fw_ui_tab_content'); + + if (!calTab.is(':visible')) + { + // F.F can not handle to style correctly an iframe which is hidden (display:none), therefore we need to + // bind a handler to refresh the calendar views after it shows up + iframe.one('show',function(){egw_refresh('','calendar');}) + } + else + { + //window.location.reload(); + window.egw_refresh('refreshing calendar','calendar'); + } } } } From 26261e080dba93e0a8650255e25f3afd4081a352 Mon Sep 17 00:00:00 2001 From: Nathan Gray Date: Tue, 7 Oct 2014 20:47:54 +0000 Subject: [PATCH 02/97] Fix bad logic breaking historylog --- etemplate/inc/class.etemplate_widget_nextmatch.inc.php | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/etemplate/inc/class.etemplate_widget_nextmatch.inc.php b/etemplate/inc/class.etemplate_widget_nextmatch.inc.php index 4ba23c7af5..587e730d3c 100644 --- a/etemplate/inc/class.etemplate_widget_nextmatch.inc.php +++ b/etemplate/inc/class.etemplate_widget_nextmatch.inc.php @@ -398,12 +398,16 @@ class etemplate_widget_nextmatch extends etemplate_widget $row_template->run('set_row_value', array('',array('row' => 1), &$_row), true); $result['data'][$id] = $row; } - else if (!$template && !get_class($template) != 'etemplate_widget_historylog') + else if (!$template || get_class($template) != 'etemplate_widget_historylog') { // Fallback based on widget names error_log(self::$request->template['name'] . ' had to fallback to run_beforeSendToClient() because it could not find the row' ); $result['data'][$id] = self::run_beforeSendToClient($row); } + else + { + $result['data'][$id] = $row; + } } if ($kUkey !== false) unset($knownUids[$kUkey]); From 3af8b4806bf88f560f82cc47c05afaba7c94a1c2 Mon Sep 17 00:00:00 2001 From: Ralf Becker Date: Tue, 7 Oct 2014 21:04:58 +0000 Subject: [PATCH 03/97] insert/update timezones without sqlite extension --- calendar/inc/class.calendar_timezones.inc.php | 91 +++-- calendar/setup/default_records.inc.php | 7 +- calendar/setup/timezones.db_backup | 176 ---------- phpgwapi/inc/class.db_backup.inc.php | 321 ++++++++++-------- phpgwapi/inc/class.egw_db.inc.php | 2 +- 5 files changed, 243 insertions(+), 354 deletions(-) diff --git a/calendar/inc/class.calendar_timezones.inc.php b/calendar/inc/class.calendar_timezones.inc.php index dd19243d24..71a8c0ae6c 100644 --- a/calendar/inc/class.calendar_timezones.inc.php +++ b/calendar/inc/class.calendar_timezones.inc.php @@ -84,7 +84,7 @@ class calendar_timezones * - calendar_timezone::tz2id('Europe/Berlin','component') returns VTIMEZONE component for given TZID * * @param string $tzid TZID - * @param string $what='id' what to return, default id, null for whole array + * @param string $what ='id' what to return, default id, null for whole array * @return int tz_id or null if not found */ public static function tz2id($tzid,$what='id') @@ -105,7 +105,7 @@ class calendar_timezones if (!isset($id) && stripos($tzid, 'America/') === 0 && count($parts = explode('/', $tzid)) == 2) { if (($data = $GLOBALS['egw']->db->select(self::TABLE,'*',array( - 'tz_tzid LIKE '.$GLOBALS['egw']->db->quote($parts[0].'/%/'.$part[1]), + 'tz_tzid LIKE '.$GLOBALS['egw']->db->quote($parts[0].'/%/'.$parts[1]), ),__LINE__,__FILE__,false,'','calendar')->fetch())) { $id = $data['tz_id']; @@ -129,7 +129,7 @@ class calendar_timezones * - calendar_timezone::id2tz($id,'component') returns VTIMEZONE component for the given id * * @param int $id - * @param string $what='tzid' what data to return or null for whole data array, with keys 'id', 'tzid', 'component', 'alias', 'latitude', 'longitude' + * @param string $what ='tzid' what data to return or null for whole data array, with keys 'id', 'tzid', 'component', 'alias', 'latitude', 'longitude' * @return mixed false: if not found */ public static function id2tz($id,$what='tzid') @@ -177,17 +177,21 @@ class calendar_timezones // check for updated timezones once per session if (!egw_cache::getSession(__CLASS__, 'tzs_checked')) { + $updated = false; try { $msg = self::import_sqlite($updated); if ($updated) error_log($msg); // log that timezones have been updated - $msg = self::import_tz_aliases($updated); - if ($updated) error_log($msg); // log that timezone aliases have been updated } - catch (Exception $e) + catch (egw_exception_wrong_userinput $e) { - _egw_log_exception($e); // log the exception to error_log, but do not stall program execution + unset($e); + $msg = self::import_db_backup($updated); + if ($updated) error_log($msg); // log that timezones have been updated } + $alias_msg = self::import_tz_aliases($updated); + if ($updated) error_log($alias_msg); // log that timezone aliases have been updated + egw_cache::setSession(__CLASS__, 'tzs_checked', true); } } @@ -196,10 +200,10 @@ class calendar_timezones * Import timezones from sqlite file * * @param boolean &$updated=null on return true if update was neccessary, false if tz's were already up to date - * @param string $file='calendar/setup/timezones.sqlite' filename relative to EGW_SERVER_ROOT + * @param string $file ='calendar/setup/timezones.sqlite' filename relative to EGW_SERVER_ROOT * @return string message about update * @throws egw_exception_wrong_parameter if $file is not readable or wrong format/version - * @throws egw_exception_assertion_failed if no PDO sqlite support + * @throws egw_exception_wrong_userinput if no PDO sqlite support * @throws egw_exception_wrong_userinput for broken sqlite extension */ public static function import_sqlite(&$updated=null, $file='calendar/setup/timezones.sqlite') @@ -271,15 +275,46 @@ class calendar_timezones } /** - * Import timezone aliases + * Import timezone via db_backup of egw_cal_timezones * * @param boolean &$updated=null on return true if update was neccessary, false if tz's were already up to date - * @param string $file='calendar/setup/tz_aliases.inc.php' filename relative to EGW_SERVER_ROOT - * @param boolean $check_mtime=true true: check version and only act, if it's different + * @param string $file ='calendar/setup/tz_aliases.inc.php' filename relative to EGW_SERVER_ROOT * @return string message about update * @throws egw_exception_wrong_parameter if $file is not readable or wrong format/version */ - public static function import_tz_aliases(&$updated=null,$file='calendar/setup/tz_aliases.inc.php',$check_mtime=true) + public static function import_db_backup(&$updated=null,$file='calendar/setup/timezones.db_backup') + { + $path = EGW_SERVER_ROOT.'/'.$file; + + if (!file_exists($path) || !is_readable($path)) + { + throw new egw_exception_wrong_parameter(__METHOD__."('$file') not found or readable!"); + } + $config = config::read('phpgwapi'); + $tz_version = date('Y-m-d H:i:s', filemtime($path)); + if ($tz_version === $config['tz_version']) + { + $updated = false; + return lang('Nothing to update, version is already %1.',$tz_version); + } + $db_backup = new db_backup(); + $rows = $db_backup->db_restore($db_backup->fopen_backup($path, true), 'tz_tzid'); + + config::save_value('tz_version', $tz_version, 'phpgwapi'); + + $updated = true; + return lang('Timezones updated to version %1 (%2 records updated).', $tz_version, $rows-8); // -8 because of header-lines + } + + /** + * Import timezone aliases + * + * @param boolean &$updated=null on return true if update was neccessary, false if tz's were already up to date + * @param string $file ='calendar/setup/tz_aliases.inc.php' filename relative to EGW_SERVER_ROOT + * @return string message about update + * @throws egw_exception_wrong_parameter if $file is not readable or wrong format/version + */ + public static function import_tz_aliases(&$updated=null,$file='calendar/setup/tz_aliases.inc.php') { $path = EGW_SERVER_ROOT.'/'.$file; @@ -294,6 +329,7 @@ class calendar_timezones $updated = false; return lang('Nothing to update, version is already %1.',$tz_aliases_mtime); } + $tz_aliases = array(); include($path); // sets $tz_aliases $updates = 0; @@ -328,10 +364,17 @@ class calendar_timezones { throw new egw_exception_no_permission_admin(); } - $GLOBALS['egw']->framework->render( - '

'.self::import_sqlite()."

\n". - '

'.self::import_tz_aliases()."

\n", - lang('Update timezones'),true); + try { + $output = '

'.self::import_sqlite()."

\n"; + } + catch (egw_exception_wrong_userinput $e) + { + unset($e); + $output = '

'.self::import_db_backup()."

\n"; + } + $output .= '

'.self::import_tz_aliases()."

\n"; + + $GLOBALS['egw']->framework->render($output, lang('Update timezones'), true); } /** @@ -347,7 +390,7 @@ class calendar_timezones // checking type of $val, now we included the object definition (no need to always include it!) if (!$vcal instanceof Horde_iCalendar) { - throw new egw_exception_wrong_parameter(__METHOD__.'('.array2string($val).", '$tzid') no Horde_iCalendar!"); + throw new egw_exception_wrong_parameter(__METHOD__.'('.array2string($vcal).", '$tzid') no Horde_iCalendar!"); } // check if we have vtimezone component data for $tzid if (!($vtimezone = calendar_timezones::tz2id($tzid, 'component'))) @@ -362,16 +405,16 @@ class calendar_timezones $standard = $horde_vtimezone->findComponent('STANDARD'); if (is_a($standard, 'Horde_iCalendar')) { - $dtstart = $standard->getAttribute('DTSTART'); - $dtstart = new egw_time($dtstart, egw_time::$server_timezone); + $time = $standard->getAttribute('DTSTART'); + $dtstart = new egw_time($time, egw_time::$server_timezone); $dtstart->setTimezone(egw_time::$server_timezone); $standard->setAttribute('DTSTART', $dtstart->format('Ymd\THis'), array(), false); } $daylight = $horde_vtimezone->findComponent('DAYLIGHT'); if (is_a($daylight, 'Horde_iCalendar')) { - $dtstart = $daylight->getAttribute('DTSTART'); - $dtstart = new egw_time($dtstart, egw_time::$server_timezone); + $time = $daylight->getAttribute('DTSTART'); + $dtstart = new egw_time($time, egw_time::$server_timezone); $dtstart->setTimezone(egw_time::$server_timezone); $daylight->setAttribute('DTSTART', $dtstart->format('Ymd\THis'), array(), false); } @@ -384,8 +427,8 @@ class calendar_timezones /** * Query timezone of a given user, returns 'tzid' or VTIMEZONE 'component' * - * @param int $user=null - * @param string $type='vcalendar' 'tzid' or everything tz2id supports, default 'vcalendar' = full vcalendar component + * @param int $user =null + * @param string $type ='vcalendar' 'tzid' or everything tz2id supports, default 'vcalendar' = full vcalendar component * @return string */ public static function user_timezone($user=null, $type='vcalendar') diff --git a/calendar/setup/default_records.inc.php b/calendar/setup/default_records.inc.php index 5c9177ee86..ffb493d5d1 100644 --- a/calendar/setup/default_records.inc.php +++ b/calendar/setup/default_records.inc.php @@ -27,11 +27,10 @@ foreach(array( try { calendar_timezones::import_sqlite(); - calendar_timezones::import_tz_aliases(); } // catch missing or broken sqlite support and use timezones.db_backup to install timezones catch (egw_exception_wrong_userinput $e) // all other exceptions are fatal { - $db_backup = new db_backup(); - $db_backup->restore($db_backup->fopen_backup(EGW_SERVER_ROOT.'/calendar/setup/timezones.db_backup', true), true, '', false); -} \ No newline at end of file + calendar_timezones::import_db_backup(); +} +calendar_timezones::import_tz_aliases(); diff --git a/calendar/setup/timezones.db_backup b/calendar/setup/timezones.db_backup index 0c9a24a783..7c51673a01 100644 --- a/calendar/setup/timezones.db_backup +++ b/calendar/setup/timezones.db_backup @@ -439,179 +439,3 @@ tz_id,tz_tzid,tz_alias,tz_latitude,tz_longitude,tz_component 431,"Europe/Belfast",338,NULL,NULL,NULL 432,"Atlantic/Jan_Mayen",347,NULL,NULL,NULL 433,"Pacific/Yap",421,NULL,NULL,NULL -434,"AUS Central Standard Time",307,NULL,NULL,NULL -435,"AUS Eastern Standard Time",314,NULL,NULL,NULL -436,"Afghanistan Standard Time",246,NULL,NULL,NULL -437,"Alaskan Standard Time",54,NULL,NULL,NULL -438,"Arab Standard Time",273,NULL,NULL,NULL -439,"Arabian Standard Time",233,NULL,NULL,NULL -440,"Arabic Standard Time",220,NULL,NULL,NULL -441,"Argentina Standard Time",58,NULL,NULL,NULL -442,"Atlantic Standard Time",113,NULL,NULL,NULL -443,"Azerbaijan Standard Time",222,NULL,NULL,NULL -444,"Azores Standard Time",293,NULL,NULL,NULL -445,"Bahia Standard Time",73,NULL,NULL,NULL -446,"Bangladesh Standard Time",231,NULL,NULL,NULL -447,"Canada Central Standard Time",172,NULL,NULL,NULL -448,"Cape Verde Standard Time",296,NULL,NULL,NULL -449,"Caucasus Standard Time",292,NULL,NULL,NULL -450,"Cen. Australia Standard Time",303,NULL,NULL,NULL -451,"Central America Standard Time",110,NULL,NULL,NULL -452,"Central Asia Standard Time",214,NULL,NULL,NULL -453,"Central Brazilian Standard Time",92,NULL,NULL,NULL -454,"Central Europe Standard Time",323,NULL,NULL,NULL -455,"Central European Standard Time",368,NULL,NULL,NULL -456,"Central Pacific Standard Time",395,NULL,NULL,NULL -457,"Central Standard Time",88,NULL,NULL,NULL -458,"Central Standard Time (Mexico)",145,NULL,NULL,NULL -459,"China Standard Time",277,NULL,NULL,NULL -460,"E. Africa Standard Time",43,NULL,NULL,NULL -461,"E. Australia Standard Time",304,NULL,NULL,NULL -462,"E. Europe Standard Time",262,NULL,NULL,NULL -463,"E. South America Standard Time",179,NULL,NULL,NULL -464,"Eastern Standard Time",153,NULL,NULL,NULL -465,"Egypt Standard Time",13,NULL,NULL,NULL -466,"Ekaterinburg Standard Time",291,NULL,NULL,NULL -467,"FLE Standard Time",335,NULL,NULL,NULL -468,"Fiji Standard Time",391,NULL,NULL,NULL -469,"GMT Standard Time",338,NULL,NULL,NULL -470,"GTB Standard Time",322,NULL,NULL,NULL -471,"Georgian Standard Time",281,NULL,NULL,NULL -472,"Greenland Standard Time",105,NULL,NULL,NULL -473,"Greenwich Standard Time",299,NULL,NULL,NULL -474,"Hawaiian Standard Time",397,NULL,NULL,NULL -475,"India Standard Time",252,NULL,NULL,NULL -476,"Iran Standard Time",282,NULL,NULL,NULL -477,"Israel Standard Time",245,NULL,NULL,NULL -478,"Jordan Standard Time",215,NULL,NULL,NULL -479,"Kaliningrad Standard Time",334,NULL,NULL,NULL -480,"Korea Standard Time",276,NULL,NULL,NULL -481,"Magadan Standard Time",258,NULL,NULL,NULL -482,"Mauritius Standard Time",380,NULL,NULL,NULL -483,"Middle East Standard Time",224,NULL,NULL,NULL -484,"Montevideo Standard Time",149,NULL,NULL,NULL -485,"Morocco Standard Time",14,NULL,NULL,NULL -486,"Mountain Standard Time",97,NULL,NULL,NULL -487,"Mountain Standard Time (Mexico)",89,NULL,NULL,NULL -488,"Myanmar Standard Time",272,NULL,NULL,NULL -489,"N. Central Asia Standard Time",264,NULL,NULL,NULL -490,"Namibia Standard Time",52,NULL,NULL,NULL -491,"Nepal Standard Time",250,NULL,NULL,NULL -492,"New Zealand Standard Time",384,NULL,NULL,NULL -493,"Newfoundland Standard Time",184,NULL,NULL,NULL -494,"North Asia East Standard Time",241,NULL,NULL,NULL -495,"North Asia Standard Time",253,NULL,NULL,NULL -496,"Pacific SA Standard Time",177,NULL,NULL,NULL -497,"Pacific Standard Time",133,NULL,NULL,NULL -498,"Pacific Standard Time (Mexico)",175,NULL,NULL,NULL -499,"Pakistan Standard Time",248,NULL,NULL,NULL -500,"Paraguay Standard Time",71,NULL,NULL,NULL -501,"Romance Standard Time",348,NULL,NULL,NULL -502,"Russian Standard Time",345,NULL,NULL,NULL -503,"SA Eastern Standard Time",86,NULL,NULL,NULL -504,"SA Pacific Standard Time",80,NULL,NULL,NULL -505,"SA Western Standard Time",131,NULL,NULL,NULL -506,"SE Asia Standard Time",223,NULL,NULL,NULL -507,"Samoa Standard Time",383,NULL,NULL,NULL -508,"Singapore Standard Time",278,NULL,NULL,NULL -509,"South Africa Standard Time",25,NULL,NULL,NULL -510,"Sri Lanka Standard Time",229,NULL,NULL,NULL -511,"Syria Standard Time",230,NULL,NULL,NULL -512,"Taipei Standard Time",279,NULL,NULL,NULL -513,"Tasmania Standard Time",309,NULL,NULL,NULL -514,"Tokyo Standard Time",284,NULL,NULL,NULL -515,"Tonga Standard Time",418,NULL,NULL,NULL -516,"Turkey Standard Time",332,NULL,NULL,NULL -517,"US Mountain Standard Time",164,NULL,NULL,NULL -518,"Ulaanbaatar Standard Time",285,NULL,NULL,NULL -519,"Venezuela Standard Time",85,NULL,NULL,NULL -520,"Vladivostok Standard Time",289,NULL,NULL,NULL -521,"W. Australia Standard Time",313,NULL,NULL,NULL -522,"W. Central Africa Standard Time",31,NULL,NULL,NULL -523,"W. Europe Standard Time",319,NULL,NULL,NULL -524,"West Asia Standard Time",280,NULL,NULL,NULL -525,"West Pacific Standard Time",413,NULL,NULL,NULL -526,"Yakutsk Standard Time",290,NULL,NULL,NULL -527,"Universal Coordinated Time",-1,NULL,NULL,NULL -528,"Casablanca, Monrovia",14,NULL,NULL,NULL -529,"Greenwich Mean Time: Dublin, Edinburgh, Lisbon, London",336,NULL,NULL,NULL -530,"Greenwich Mean Time; Dublin, Edinburgh, London",338,NULL,NULL,NULL -531,"Amsterdam, Berlin, Bern, Rome, Stockholm, Vienna",319,NULL,NULL,NULL -532,"Belgrade, Pozsony, Budapest, Ljubljana, Prague",350,NULL,NULL,NULL -533,"Brussels, Copenhagen, Madrid, Paris",348,NULL,NULL,NULL -534,"Paris, Madrid, Brussels, Copenhagen",348,NULL,NULL,NULL -535,"Prague, Central Europe",350,NULL,NULL,NULL -536,"Sarajevo, Skopje, Sofija, Vilnius, Warsaw, Zagreb",355,NULL,NULL,NULL -537,"West Central Africa",34,NULL,NULL,NULL -538,"Athens, Istanbul, Minsk",317,NULL,NULL,NULL -539,"Bucharest",322,NULL,NULL,NULL -540,"Cairo",13,NULL,NULL,NULL -541,"Harare, Pretoria",24,NULL,NULL,NULL -542,"Helsinki, Riga, Tallinn",330,NULL,NULL,NULL -543,"Israel, Jerusalem Standard Time",245,NULL,NULL,NULL -544,"Baghdad",220,NULL,NULL,NULL -545,"Arab, Kuwait, Riyadh",256,NULL,NULL,NULL -546,"Moscow, St. Petersburg, Volgograd",345,NULL,NULL,NULL -547,"East Africa, Nairobi",43,NULL,NULL,NULL -548,"Tehran",282,NULL,NULL,NULL -549,"Abu Dhabi, Muscat",261,NULL,NULL,NULL -550,"Baku, Tbilisi, Yerevan",222,NULL,NULL,NULL -551,"Kabul",246,NULL,NULL,NULL -552,"Ekaterinburg",291,NULL,NULL,NULL -553,"Islamabad, Karachi, Tashkent",248,NULL,NULL,NULL -554,"Kolkata, Chennai, Mumbai, New Delhi, India Standard Time",429,NULL,NULL,NULL -555,"Kathmandu, Nepal",250,NULL,NULL,NULL -556,"Almaty, Novosibirsk, North Central Asia",214,NULL,NULL,NULL -557,"Astana, Dhaka",231,NULL,NULL,NULL -558,"Sri Jayawardenepura, Sri Lanka",229,NULL,NULL,NULL -559,"Rangoon",272,NULL,NULL,NULL -560,"Bangkok, Hanoi, Jakarta",223,NULL,NULL,NULL -561,"Krasnoyarsk",253,NULL,NULL,NULL -562,"Beijing, Chongqing, Hong Kong SAR, Urumqi",277,NULL,NULL,NULL -563,"Irkutsk, Ulaan Bataar",241,NULL,NULL,NULL -564,"Kuala Lumpur, Singapore",278,NULL,NULL,NULL -565,"Perth, Western Australia",313,NULL,NULL,NULL -566,"Taipei",279,NULL,NULL,NULL -567,"Osaka, Sapporo, Tokyo",284,NULL,NULL,NULL -568,"Seoul, Korea Standard time",276,NULL,NULL,NULL -569,"Yakutsk",290,NULL,NULL,NULL -570,"Adelaide, Central Australia",303,NULL,NULL,NULL -571,"Darwin",307,NULL,NULL,NULL -572,"Brisbane, East Australia",304,NULL,NULL,NULL -573,"Canberra, Melbourne, Sydney, Hobart (year 2000 only)",314,NULL,NULL,NULL -574,"Guam, Port Moresby",396,NULL,NULL,NULL -575,"Hobart, Tasmania",309,NULL,NULL,NULL -576,"Vladivostok",289,NULL,NULL,NULL -577,"Magadan, Solomon Is., New Caledonia",258,NULL,NULL,NULL -578,"Auckland, Wellington",384,NULL,NULL,NULL -579,"Fiji Islands, Kamchatka, Marshall Is.",391,NULL,NULL,NULL -580,"Nuku'alofa, Tonga",418,NULL,NULL,NULL -581,"Azores",293,NULL,NULL,NULL -582,"Cape Verde Is.",296,NULL,NULL,NULL -583,"Mid-Atlantic",156,NULL,NULL,NULL -584,"Brasilia",179,NULL,NULL,NULL -585,"Buenos Aires",58,NULL,NULL,NULL -586,"Greenland",105,NULL,NULL,NULL -587,"Newfoundland",184,NULL,NULL,NULL -588,"Atlantic Time (Canada)",113,NULL,NULL,NULL -589,"Caracas, La Paz",85,NULL,NULL,NULL -590,"Santiago",177,NULL,NULL,NULL -591,"Bogota, Lima, Quito",80,NULL,NULL,NULL -592,"Eastern Time (US & Canada)",153,NULL,NULL,NULL -593,"Indiana (East)",116,NULL,NULL,NULL -594,"Central America",110,NULL,NULL,NULL -595,"Central Time (US & Canada)",88,NULL,NULL,NULL -596,"Mexico City, Tegucigalpa",145,NULL,NULL,NULL -597,"Saskatchewan",100,NULL,NULL,NULL -598,"Arizona",164,NULL,NULL,NULL -599,"Mountain Time (US & Canada)",97,NULL,NULL,NULL -600,"Pacific Time (US & Canada); Tijuana",133,NULL,NULL,NULL -601,"Alaska",54,NULL,NULL,NULL -602,"Hawaii",397,NULL,NULL,NULL -603,"Midway Island, Samoa",404,NULL,NULL,NULL -604,"Eniwetok, Kwajalein, Dateline Time",401,NULL,NULL,NULL -605,"Armenian Standard Time",292,NULL,NULL,NULL -606,"Mexico Standard Time",145,NULL,NULL,NULL -607,"Mexico Standard Time 2",89,NULL,NULL,NULL -608,"Mid-Atlantic Standard Time",300,NULL,NULL,NULL -609,"US/Eastern",153,NULL,NULL,NULL diff --git a/phpgwapi/inc/class.db_backup.inc.php b/phpgwapi/inc/class.db_backup.inc.php index dc69ac860c..d349ce4dbd 100644 --- a/phpgwapi/inc/class.db_backup.inc.php +++ b/phpgwapi/inc/class.db_backup.inc.php @@ -416,155 +416,7 @@ class db_backup $backup_db_halt_on_error = $this->db->Halt_On_Error; $this->db->Halt_On_Error = 'no'; } - $table = False; - $n = 0; - $rows = array(); - while(!feof($f)) - { - $line = trim(fgets($f)); ++$n; - - if (empty($line)) continue; - - if (substr($line,0,9) == 'version: ') - { - $api_version = trim(substr($line,9)); - continue; - } - if (substr($line,0,9) == 'charset: ') - { - $charset = trim(substr($line,9)); - // needed if mbstring.func_overload > 0, else eg. substr does not work with non ascii chars - @ini_set('mbstring.internal_encoding',$charset); - - // check if we really need to convert the charset, as it's not perfect and can do some damage - if ($convert_to_system_charset && !strcasecmp($this->schema_proc->system_charset, $charset)) - { - $convert_to_system_charset = false; // no conversation necessary - } - // set the DB's client encoding (for mysql only if api_version >= 1.0.1.019) - if ((!$convert_to_system_charset || $this->db->capabilities['client_encoding']) && - (substr($this->db->Type,0,5) != 'mysql' || !is_object($GLOBALS['egw_setup']) || - $api_version && !$GLOBALS['egw_setup']->alessthanb($api_version,'1.0.1.019'))) - { - $this->db->Link_ID->SetCharSet($charset); - if (!$convert_to_system_charset) - { - $this->schema_proc->system_charset = $charset; // so schema_proc uses it for the creation of the tables - } - } - continue; - } - if (substr($line,0,8) == 'schema: ') - { - // create the tables in the backup set - $this->schemas = json_php_unserialize(trim(substr($line,8))); - foreach($this->schemas as $table_name => $schema) - { - // if column is longtext in current schema, convert text to longtext, in case user already updated column - foreach($schema['fd'] as $col => &$def) - { - if ($def['type'] == 'text' && $this->db->get_column_attribute($col, $table_name, true, 'type') == 'longtext') - { - $def['type'] = 'longtext'; - } - } - //echo "
$table_name => ".self::write_array($schema,1)."
\n"; - $this->schema_proc->CreateTable($table_name, $schema); - } - continue; - } - if (substr($line,0,7) == 'table: ') - { - if ($rows) // flush pending rows of last table - { - $this->db->insert($table,$rows,False,__LINE__,__FILE__,false,false,$this->schemas[$table]); - } - $rows = array(); - $table = substr($line,7); - if (!isset($this->schemas[$table])) $this->schemas[$table] = $this->db->get_table_definitions(true, $table); - - $cols = self::csv_split($line=fgets($f)); ++$n; - $blobs = array(); - foreach($this->schemas[$table]['fd'] as $col => $data) - { - if ($data['type'] == 'blob') $blobs[] = $col; - } - - if (feof($f)) break; - continue; - } - if ($convert_to_system_charset && !$this->db->capabilities['client_encoding']) - { - if ($GLOBALS['egw_setup']) - { - if (!is_object($GLOBALS['egw_setup']->translation->sql)) - { - $GLOBALS['egw_setup']->translation->setup_translation_sql(); - } - } - } - if ($table) // do we already reached the data part - { - $import = true; - $data = self::csv_split($line, $cols, $blobs); - - if ($table == 'egw_async' && in_array('##last-check-run##',$data)) - { - //echo '

'.lang("Line %1: '%2'
csv data does contain ##last-check-run## of table %3 ==> ignored",$n,$line,$table)."

\n"; - //echo 'data=
'.print_r($data,true)."
\n"; - $import = false; - } - if (in_array($table,$this->exclude_tables)) - { - echo '

'.lang("Table %1 is excluded from backup and restore. Data will not be restored.",$table)."

\n"; - $import = false; // dont restore data of excluded tables - } - if ($import) - { - if (count($data) == count($cols)) - { - if ($convert_to_system_charset && !$this->db->capabilities['client_encoding']) - { - $data = translation::convert($data,$charset); - } - if ($insert_n_rows > 1) - { - $rows[] = $data; - if (count($rows) == $insert_n_rows) - { - $this->db->insert($table,$rows,False,__LINE__,__FILE__,false,false,$this->schemas[$table]); - $rows = array(); - } - } - else - { - $this->db->insert($table,$data,False,__LINE__,__FILE__,false,false,$this->schemas[$table]); - } - } - else - { - echo '

'.lang("Line %1: '%2'
csv data does not match column-count of table %3 ==> ignored",$n,$line,$table)."

\n"; - echo 'data=
'.print_r($data,true)."
\n"; - } - } - } - } - if ($rows) // flush pending rows - { - $this->db->insert($table,$rows,False,__LINE__,__FILE__,false,false,$this->schemas[$table]); - } - // updated the sequences, if the DB uses them - foreach($this->schemas as $table => $schema) - { - foreach($schema['fd'] as $column => $definition) - { - if ($definition['type'] == 'auto') - { - $this->schema_proc->UpdateSequence($table,$column); - break; // max. one per table - } - } - } + $this->db_restore($f, $insert_n_rows); if ($convert_to_system_charset) // store the changed charset { @@ -652,6 +504,177 @@ class db_backup return ''; } + /** + * Restore data from a (compressed) csv file + * + * @param resource $f file opened with fopen for reading + * @param int|string $insert_n_rows =10 how many rows to insert in one sql statement, or string with column-name used as unique key for insert + * @returns int number of rows read from csv file + */ + function db_restore($f, $insert_n_rows=10) + { + $convert_to_system_charset = true; + $table = False; + $n = 0; + $rows = array(); + while(!feof($f)) + { + $line = trim(fgets($f)); ++$n; + + if (empty($line)) continue; + + if (substr($line,0,9) == 'version: ') + { + $api_version = trim(substr($line,9)); + continue; + } + if (substr($line,0,9) == 'charset: ') + { + $charset = trim(substr($line,9)); + // needed if mbstring.func_overload > 0, else eg. substr does not work with non ascii chars + @ini_set('mbstring.internal_encoding',$charset); + + // check if we really need to convert the charset, as it's not perfect and can do some damage + if ($convert_to_system_charset && !strcasecmp($this->schema_proc->system_charset, $charset)) + { + $convert_to_system_charset = false; // no conversation necessary + } + // set the DB's client encoding (for mysql only if api_version >= 1.0.1.019) + if ((!$convert_to_system_charset || $this->db->capabilities['client_encoding']) && + (substr($this->db->Type,0,5) != 'mysql' || !is_object($GLOBALS['egw_setup']) || + $api_version && !$GLOBALS['egw_setup']->alessthanb($api_version,'1.0.1.019'))) + { + $this->db->Link_ID->SetCharSet($charset); + if (!$convert_to_system_charset) + { + $this->schema_proc->system_charset = $charset; // so schema_proc uses it for the creation of the tables + } + } + continue; + } + if (substr($line,0,8) == 'schema: ') + { + // create the tables in the backup set + $this->schemas = json_php_unserialize(trim(substr($line,8))); + foreach($this->schemas as $table_name => $schema) + { + // if column is longtext in current schema, convert text to longtext, in case user already updated column + foreach($schema['fd'] as $col => &$def) + { + if ($def['type'] == 'text' && $this->db->get_column_attribute($col, $table_name, true, 'type') == 'longtext') + { + $def['type'] = 'longtext'; + } + } + //echo "
$table_name => ".self::write_array($schema,1)."
\n"; + $this->schema_proc->CreateTable($table_name, $schema); + } + continue; + } + if (substr($line,0,7) == 'table: ') + { + if ($rows) // flush pending rows of last table + { + $this->db->insert($table,$rows,False,__LINE__,__FILE__,false,false,$this->schemas[$table]); + } + $rows = array(); + $table = substr($line,7); + if (!isset($this->schemas[$table])) $this->schemas[$table] = $this->db->get_table_definitions(true, $table); + $auto_id = count($this->schemas[$table]['pk']) == 1 ? $this->schemas[$table]['pk'][0] : null; + + $cols = self::csv_split($line=fgets($f)); ++$n; + $blobs = array(); + foreach($this->schemas[$table]['fd'] as $col => $data) + { + if ($data['type'] == 'blob') $blobs[] = $col; + } + + if (feof($f)) break; + continue; + } + if ($convert_to_system_charset && !$this->db->capabilities['client_encoding']) + { + if ($GLOBALS['egw_setup']) + { + if (!is_object($GLOBALS['egw_setup']->translation->sql)) + { + $GLOBALS['egw_setup']->translation->setup_translation_sql(); + } + } + } + if ($table) // do we already reached the data part + { + $import = true; + $data = self::csv_split($line, $cols, $blobs); + + if ($table == 'egw_async' && in_array('##last-check-run##',$data)) + { + //echo '

'.lang("Line %1: '%2'
csv data does contain ##last-check-run## of table %3 ==> ignored",$n,$line,$table)."

\n"; + //echo 'data=
'.print_r($data,true)."
\n"; + $import = false; + } + if (in_array($table,$this->exclude_tables)) + { + echo '

'.lang("Table %1 is excluded from backup and restore. Data will not be restored.",$table)."

\n"; + $import = false; // dont restore data of excluded tables + } + if ($import) + { + if (count($data) == count($cols)) + { + if ($convert_to_system_charset && !$this->db->capabilities['client_encoding']) + { + $data = translation::convert($data,$charset); + } + if ($insert_n_rows > 1) + { + $rows[] = $data; + if (count($rows) == $insert_n_rows) + { + $this->db->insert($table,$rows,False,__LINE__,__FILE__,false,false,$this->schemas[$table]); + $rows = array(); + } + } + // update existing table using given unique key in $insert_n_rows (also removing auto-id/sequence) + elseif(!is_numeric($insert_n_rows)) + { + $where = array($insert_n_rows => $data[$insert_n_rows]); + unset($data[$insert_n_rows]); + if ($auto_id) unset($data[$auto_id]); + $this->db->insert($table,$data,$where,__LINE__,__FILE__,false,false,$this->schemas[$table]); + } + else + { + $this->db->insert($table,$data,False,__LINE__,__FILE__,false,false,$this->schemas[$table]); + } + } + else + { + echo '

'.lang("Line %1: '%2'
csv data does not match column-count of table %3 ==> ignored",$n,$line,$table)."

\n"; + echo 'data=
'.print_r($data,true)."
\n"; + } + } + } + } + if ($rows) // flush pending rows + { + $this->db->insert($table,$rows,False,__LINE__,__FILE__,false,false,$this->schemas[$table]); + } + // updated the sequences, if the DB uses them + foreach($this->schemas as $table => $schema) + { + foreach($schema['fd'] as $column => $definition) + { + if ($definition['type'] == 'auto') + { + $this->schema_proc->UpdateSequence($table,$column); + break; // max. one per table + } + } + } + return $n; + } + /** * Removes a dir, no matter whether it is empty or full * diff --git a/phpgwapi/inc/class.egw_db.inc.php b/phpgwapi/inc/class.egw_db.inc.php index aca8154484..8cb611a875 100644 --- a/phpgwapi/inc/class.egw_db.inc.php +++ b/phpgwapi/inc/class.egw_db.inc.php @@ -1750,7 +1750,7 @@ class egw_db $this->select($table,'count(*)',$where,$line,$file); if ($this->next_record() && $this->f(0)) { - return !!$this->update($table,$data,$where,$line,$file,$app); + return !!$this->update($table,$data,$where,$line,$file,$app,$use_prepared_statement,$table_def); } break; } From e674c5aa5bf9c9f6fe16f7f3d536fda34c3c9331 Mon Sep 17 00:00:00 2001 From: Nathan Gray Date: Tue, 7 Oct 2014 21:16:18 +0000 Subject: [PATCH 04/97] Partially revert r48812, seems it didn't help entry widgets but it did cause problems with preferences --- etemplate/js/et2_core_widget.js | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/etemplate/js/et2_core_widget.js b/etemplate/js/et2_core_widget.js index 9473d6d05d..cc1fdf5fcf 100644 --- a/etemplate/js/et2_core_widget.js +++ b/etemplate/js/et2_core_widget.js @@ -666,8 +666,7 @@ var et2_widget = ClassWithAttributes.extend( } if(entry && entry.type) { - _nodeName = entry.type; - _node.setAttribute("type", entry.type); + _nodeName = attributes["type"] = entry.type; } entry = null; } @@ -676,8 +675,7 @@ var et2_widget = ClassWithAttributes.extend( // we need to expand it now as it defines the constructor and by that attributes parsed via parseXMLAttrs! if (_nodeName.charAt(0) == '@' || _nodeName.indexOf('$') >= 0) { - _nodeName = this.getArrayMgr('content').expandName(_nodeName); - _node.setAttribute("type", _nodeName); + _nodeName = attributes["type"] = this.getArrayMgr('content').expandName(_nodeName); } // Get the constructor - if the widget is readonly, use the special "_ro" From af522ab692b7298b81d70a42e3a814226f0d54fe Mon Sep 17 00:00:00 2001 From: Ralf Becker Date: Tue, 7 Oct 2014 21:41:16 +0000 Subject: [PATCH 05/97] copy 14.1 changelog to trunk to satisfy update checker --- doc/rpm-build/debian.changes | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/doc/rpm-build/debian.changes b/doc/rpm-build/debian.changes index a2c6433156..3b1f92396b 100644 --- a/doc/rpm-build/debian.changes +++ b/doc/rpm-build/debian.changes @@ -1,3 +1,37 @@ +egroupware-epl (14.1.20141007-1) hardy; urgency=low + + * Mail: handle (and correct if needed) charset for subject on import of messages + * ProjectManager: fix an other SQL error in summary + * Apache 2.4 and RHEL 7 installation fixes + * Knowledgebase: restore capability to send articles via mail + + -- Ralf Becker Tue, 07 Oct 2014 09:54:28 +0200 + +egroupware-epl (14.1.20141002-1) hardy; urgency=low + + * All apps: fixed diverse regressions found while testing + * Addressbook/Tracker/other apps: custom-fields were not saved (existing custom-fields and InfoLog worked) + + -- Ralf Becker Thu, 02 Oct 2014 11:37:36 +0200 + +egroupware-epl (14.1.20141001-1) hardy; urgency=low + + * Mail: decoding of TNEF/winmail.dat as attachments, requires installation of PEAR packages Horde_Compress, Horde_Icalendar, Horde_Mapi and PHP bcmath extension + * Mail: fetch all subscribed folders for a given account in a single pass + * Mail: vaction notice indicator in mail showed result of other users + * Admin: New setting for admin users with available administrator password to be able to modify mail ACL rights and vacation notices (no longer in admin context menu but under edit account->forward... tab) of each accounts via both mail and admin app. + * Samabaadmin: fix fatal error when calling app + * Preferences: opening forced preferences set selectboxes for not set values to first real value not "Users Choice" + * Timesheet: fix SQL error when searching and NO custom fields defined + * Calendar: user without edit access to event could not delete own alarm + * Filemanager: New styling and access to list of uploading files in progress + * PostgreSQL/Admin: adding new accounts failed + * PostgreSQL/Mail: changing password gave SQL error + * PostgreSQL/Projectmanager: fix various SQL errors around filtering by app-name in element list + * Mail/Calendar/eTemplate2: fix timezone problems of times in grid or lists, if server_timezone differs from php.ini date.timezone + + -- Ralf Becker Wed, 01 Oct 2014 22:04:59 +0200 + egroupware-epl (14.1.20140923-1) hardy; urgency=low * Preferences: non-admin user was able to give himself run-rights to any app incl. admin From 7111e0bb83a74b25c0119d0179d3597f46a08469 Mon Sep 17 00:00:00 2001 From: Nathan Gray Date: Tue, 7 Oct 2014 22:27:27 +0000 Subject: [PATCH 06/97] Change email regex to allow email addresses of the form "Ralf Becker " or "rb@stylite.de" * + '"Becker, Ralf" ' * + "'Becker, Ralf' " + * + "Ralf Becker " (contains comma outside " or ' enclosed block) * - "Becker < Ralf " (contains < ----------- " ---------------) @@ -33,7 +34,7 @@ class etemplate_widget_url extends etemplate_widget * * Same preg is in et2_widget_url Javascript class, but no \x00 allowed and /u modifier for utf8! */ - const EMAIL_PREG = "/^(([^\042',<][^,<]+|\042[^\042]+\042|\'[^\']+\'|)\s?<)?[^\x01-\x20()<>@,;:\042\[\]]+@([a-z0-9ÄÖÜäöüß](|[a-z0-9ÄÖÜäöüß_-]*[a-z0-9ÄÖÜäöüß])\.)+[a-z]{2,6}>?$/iu"; + const EMAIL_PREG = "/^(([^\042',<][^,<]+|\042[^\042]+\042|\'[^\']+\'|\042[^\042]+)\s?<)?[^\x00-\x20()<>@,;:\042\[\]]+@([a-z0-9ÄÖÜäöüß](|[a-z0-9ÄÖÜäöüß_-]*[a-z0-9ÄÖÜäöüß])\.)+[a-z]{2,6}>?\042?$/iu"; /** * Validate input diff --git a/etemplate/js/et2_widget_url.js b/etemplate/js/et2_widget_url.js index 23915b99e2..e0a6f2a1ca 100644 --- a/etemplate/js/et2_widget_url.js +++ b/etemplate/js/et2_widget_url.js @@ -38,6 +38,7 @@ var et2_url = et2_textbox.extend( * + "" or "rb@stylite.de" * + '"Becker, Ralf" ' * + "'Becker, Ralf' " + * + "Ralf Becker " (contains comma outside " or ' enclosed block) * - "Becker < Ralf " (contains < ----------- " ---------------) @@ -48,7 +49,7 @@ var et2_url = et2_textbox.extend( * * Same preg is in etemplate_widget_url PHP class! */ - EMAIL_PREG: new RegExp(/^(([^\042',<][^,<]+|\042[^\042]+\042|\'[^\']+\'|)\s?<)?[^\x00-\x20()<>@,;:\042\[\]]+@([a-z0-9ÄÖÜäöüß](|[a-z0-9ÄÖÜäöüß_-]*[a-z0-9ÄÖÜäöüß])\.)+[a-z]{2,6}>?$/i), + EMAIL_PREG: new RegExp(/^(([^\042',<][^,<]+|\042[^\042]+\042|\'[^\']+\'|\042[^\042]+)\s?<)?[^\x00-\x20()<>@,;:\042\[\]]+@([a-z0-9ÄÖÜäöüß](|[a-z0-9ÄÖÜäöüß_-]*[a-z0-9ÄÖÜäöüß])\.)+[a-z]{2,6}>?\042?$/i), /** * @memberOf et2_url */ From 7fe36b289b583854c07537826e3a58446141396e Mon Sep 17 00:00:00 2001 From: Nathan Gray Date: Tue, 7 Oct 2014 22:59:55 +0000 Subject: [PATCH 07/97] Fix relative columns width preference not saved as relative width --- etemplate/js/et2_dataview.js | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/etemplate/js/et2_dataview.js b/etemplate/js/et2_dataview.js index c92f7c0b19..ba5da99e8c 100644 --- a/etemplate/js/et2_dataview.js +++ b/etemplate/js/et2_dataview.js @@ -404,7 +404,8 @@ var et2_dataview = Class.extend({ // Set to selected width this.set_width(_w + "px"); self.columnMgr.updated = true; - self.updateColumns(); + // Just triggers recalculation + self.columnMgr.getColumnWidth(0); // Set relative widths to match var relative = self.columnMgr.totalWidth - self.columnMgr.totalFixed + _w; @@ -415,8 +416,8 @@ var et2_dataview = Class.extend({ if(col == this || col.fixedWidth) continue; col.set_width(self.columns[i].width / relative); } - // Don't update now, or columns might change a little. - // Save it for next time. + // Triggers column change callback, which saves + self.updateColumns(); } else { From cdae84a3bab03c5951478fbb4115eea379261a81 Mon Sep 17 00:00:00 2001 From: Nathan Gray Date: Tue, 7 Oct 2014 23:20:33 +0000 Subject: [PATCH 08/97] If user changes the account or location, this changes the ACL ID. Remove the previous ID if it is an edit. --- admin/js/app.js | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/admin/js/app.js b/admin/js/app.js index 93d064bf17..4c892a9883 100644 --- a/admin/js/app.js +++ b/admin/js/app.js @@ -552,6 +552,13 @@ app.classes.admin = AppJS.extend( if(_value.acl_account && (_value.acl_appname && _value.acl_location || _value.apps)) { var id = _value.acl_appname+':'+_value.acl_account+':'+_value.acl_location; + if(content && content.id && id != content.id) + { + // Changed the account or location, remove previous or we + // get a new line instead of an edit + this.egw.json(className+'::ajax_change_acl', [content.id, 0], null,this,false,this) + .sendRequest(); + } var rights = 0; for(var i in _value.acl) { From 7bcf736f09d370e394030378436b502ef37f2f1b Mon Sep 17 00:00:00 2001 From: Nathan Gray Date: Tue, 7 Oct 2014 23:26:25 +0000 Subject: [PATCH 09/97] Increase popup padding by 20x30 pixels --- phpgwapi/js/jsapi/egw.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/phpgwapi/js/jsapi/egw.js b/phpgwapi/js/jsapi/egw.js index 0df00e5f37..cd944e40a6 100644 --- a/phpgwapi/js/jsapi/egw.js +++ b/phpgwapi/js/jsapi/egw.js @@ -224,7 +224,7 @@ { // Resize popup when et2 load is done jQuery(node).one("load",function() { - window.resizeTo(jQuery(document).width()+25,jQuery(document).height()+70); + window.resizeTo(jQuery(document).width()+45,jQuery(document).height()+100); }); } var et2 = new etemplate2(node, currentapp+".etemplate_new.ajax_process_content.etemplate"); From 1c9a14159e0852666d557bed0242eb17f4443e40 Mon Sep 17 00:00:00 2001 From: Ralf Becker Date: Wed, 8 Oct 2014 09:01:51 +0000 Subject: [PATCH 10/97] * Mail: allow to enter name+mail eg. "Ralf Becker " in compose, automatic fix unquoted commas in entered mail addresses --- etemplate/js/et2_widget_taglist.js | 59 ++++++++++++++++++- .../magicsuggest/src/magicsuggest-1.3.1.js | 5 +- 2 files changed, 61 insertions(+), 3 deletions(-) diff --git a/etemplate/js/et2_widget_taglist.js b/etemplate/js/et2_widget_taglist.js index 48c36c2ca9..17e143957f 100644 --- a/etemplate/js/et2_widget_taglist.js +++ b/etemplate/js/et2_widget_taglist.js @@ -99,7 +99,13 @@ var et2_taglist = et2_selectbox.extend( // Selectbox attributes that are not applicable "multiple": { ignore: true}, "rows": { ignore: true}, - "tags": { ignore: true} + "tags": { ignore: true}, + useCommaKey: { + name: "comma will start validation", + type: "boolean", + "default": true, + description: "Set to false to allow comma in entered content" + } }, // Allows sub-widgets to override options to the library @@ -160,6 +166,7 @@ var et2_taglist = et2_selectbox.extend( required: this.options.required, allowFreeEntries: this.options.allowFreeEntries, useTabKey: true, + useCommaKey: this.options.useCommaKey, disabled: this.options.disabled || this.options.readonly, editable: !(this.options.disabled || this.options.readonly), selectionRenderer: jQuery.proxy(this.options.tagRenderer || this.selectionRenderer,this), @@ -474,8 +481,14 @@ var et2_taglist_email = et2_taglist.extend( description:"Include mailing lists in search results", default: false, type: "boolean" - } }, + useCommaKey: { + name: "comma will start validation", + type: "boolean", + "default": false, + description: "Set to false to allow comma in entered content" + } + }, lib_options: { // Search function limits to 3 anyway minChars: 3 @@ -507,6 +520,48 @@ var et2_taglist_email = et2_taglist.extend( // We check free entries for valid email, and render as invalid if it's not. var valid = item.id != item.label || et2_url.prototype.EMAIL_PREG.test(item.id || ''); + if (!valid && item.id) + { + // automatic quote 'Becker, Ralf ' as '"Becker, Ralf" ' + var matches = item.id.match(/^(.*) ?<(.*)>$/); + if (matches && et2_url.prototype.EMAIL_PREG.test('"'+matches[1].trim()+'" <'+matches[2].trim()+'>')) + { + item.id = item.label = '"'+matches[1].trim()+'" <'+matches[2].trim()+'>'; + valid = true; + } + // automatic insert multiple comma-separated emails like "rb@stylite.de, hn@stylite.de" + if (!valid) + { + var parts = item.id.split(/, */); + if (parts.length > 1) + { + valid = true; + for(var i=0; i < parts.length; ++i) + { + parts[i] = parts[i].trim(); + if (!et2_url.prototype.EMAIL_PREG.test(parts[i])) + { + valid = false; + break; + } + } + if (valid) + { + item.id = item.label = parts.shift(); + // insert further parts into taglist, after validation first one + var taglist = this.taglist; + window.setTimeout(function() + { + for(var i=0; i < parts.length; ++i) + { + taglist.addToSelection({id: parts[i], label: parts[i]}); + } + }, 10); + } + } + } + } + var label = jQuery('').text(item.label); if (item.class) label.addClass(item.class); if (typeof item.title != 'undefined') label.attr('title', item.title); diff --git a/phpgwapi/js/jquery/magicsuggest/src/magicsuggest-1.3.1.js b/phpgwapi/js/jquery/magicsuggest/src/magicsuggest-1.3.1.js index 20a4b91c86..2bd590aba7 100644 --- a/phpgwapi/js/jquery/magicsuggest/src/magicsuggest-1.3.1.js +++ b/phpgwapi/js/jquery/magicsuggest/src/magicsuggest-1.3.1.js @@ -1291,7 +1291,9 @@ } break; case 188: // comma + if(!cfg.useCommaKey) break; if(e.shiftKey) break; // Shift + , = < on some keyboards + if(e.originalEvent && e.originalEvent.keyIdentifier && e.originalEvent.keyIdentifier != "U+002C") break; case 9: // tab case 13: // enter e.preventDefault(); @@ -1348,7 +1350,8 @@ break; case 13:case 9:case 188:// enter, tab, comma // Shift + comma = < on English keyboard - if(e.keyCode !== 188 || (cfg.useCommaKey === true && !e.shiftKey)) { + if(e.keyCode !== 188 || (cfg.useCommaKey === true && + !(e.shiftKey || e.originalEvent && e.originalEvent.keyIdentifier && e.originalEvent.keyIdentifier != "U+002C"))) { e.preventDefault(); if(cfg.expanded === true){ // if a selection is performed, select it and reset field selected = ms.combobox.find('.ms-res-item-active:first'); From 3edc0f30804f0e1a0eb1fdcddfa4c32304cbe01b Mon Sep 17 00:00:00 2001 From: Ralf Becker Date: Wed, 8 Oct 2014 09:07:01 +0000 Subject: [PATCH 11/97] revert r48945, as it break for some conditions --- etemplate/inc/class.etemplate_widget_url.inc.php | 3 +-- etemplate/js/et2_widget_url.js | 3 +-- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/etemplate/inc/class.etemplate_widget_url.inc.php b/etemplate/inc/class.etemplate_widget_url.inc.php index 1078e31e9e..639d4ead8f 100644 --- a/etemplate/inc/class.etemplate_widget_url.inc.php +++ b/etemplate/inc/class.etemplate_widget_url.inc.php @@ -25,7 +25,6 @@ class etemplate_widget_url extends etemplate_widget * + "" or "rb@stylite.de" * + '"Becker, Ralf" ' * + "'Becker, Ralf' " - * + "Ralf Becker " (contains comma outside " or ' enclosed block) * - "Becker < Ralf " (contains < ----------- " ---------------) @@ -34,7 +33,7 @@ class etemplate_widget_url extends etemplate_widget * * Same preg is in et2_widget_url Javascript class, but no \x00 allowed and /u modifier for utf8! */ - const EMAIL_PREG = "/^(([^\042',<][^,<]+|\042[^\042]+\042|\'[^\']+\'|\042[^\042]+)\s?<)?[^\x00-\x20()<>@,;:\042\[\]]+@([a-z0-9ÄÖÜäöüß](|[a-z0-9ÄÖÜäöüß_-]*[a-z0-9ÄÖÜäöüß])\.)+[a-z]{2,6}>?\042?$/iu"; + const EMAIL_PREG = "/^(([^\042',<][^,<]+|\042[^\042]+\042|\'[^\']+\'|)\s?<)?[^\x01-\x20()<>@,;:\042\[\]]+@([a-z0-9ÄÖÜäöüß](|[a-z0-9ÄÖÜäöüß_-]*[a-z0-9ÄÖÜäöüß])\.)+[a-z]{2,6}>?$/iu"; /** * Validate input diff --git a/etemplate/js/et2_widget_url.js b/etemplate/js/et2_widget_url.js index e0a6f2a1ca..23915b99e2 100644 --- a/etemplate/js/et2_widget_url.js +++ b/etemplate/js/et2_widget_url.js @@ -38,7 +38,6 @@ var et2_url = et2_textbox.extend( * + "" or "rb@stylite.de" * + '"Becker, Ralf" ' * + "'Becker, Ralf' " - * + "Ralf Becker " (contains comma outside " or ' enclosed block) * - "Becker < Ralf " (contains < ----------- " ---------------) @@ -49,7 +48,7 @@ var et2_url = et2_textbox.extend( * * Same preg is in etemplate_widget_url PHP class! */ - EMAIL_PREG: new RegExp(/^(([^\042',<][^,<]+|\042[^\042]+\042|\'[^\']+\'|\042[^\042]+)\s?<)?[^\x00-\x20()<>@,;:\042\[\]]+@([a-z0-9ÄÖÜäöüß](|[a-z0-9ÄÖÜäöüß_-]*[a-z0-9ÄÖÜäöüß])\.)+[a-z]{2,6}>?\042?$/i), + EMAIL_PREG: new RegExp(/^(([^\042',<][^,<]+|\042[^\042]+\042|\'[^\']+\'|)\s?<)?[^\x00-\x20()<>@,;:\042\[\]]+@([a-z0-9ÄÖÜäöüß](|[a-z0-9ÄÖÜäöüß_-]*[a-z0-9ÄÖÜäöüß])\.)+[a-z]{2,6}>?$/i), /** * @memberOf et2_url */ From 76dbe08207393677d80e6a69b15e38316163fb9b Mon Sep 17 00:00:00 2001 From: Ralf Becker Date: Wed, 8 Oct 2014 10:45:55 +0000 Subject: [PATCH 12/97] * all apps: custom fields of type "float" allow to specify maxlength,size,min,max comma-separated in length field --- etemplate/js/et2_extension_customfields.js | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/etemplate/js/et2_extension_customfields.js b/etemplate/js/et2_extension_customfields.js index bf05e29cc1..c55dec5da1 100644 --- a/etemplate/js/et2_extension_customfields.js +++ b/etemplate/js/et2_extension_customfields.js @@ -416,6 +416,22 @@ var et2_customfields_list = et2_valueWidget.extend([et2_IDetachedDOM, et2_IInput } return true; }, + _setup_float: function(field_name, field, attrs) { + // No label on the widget itself + delete(attrs.label); + + field.type = 'float'; + + if(field.len) + { + var size = field.len.split(','); + attrs.maxlength = size[0]; + attrs.size = size.length > 1 && size[1] !== '' ? size[1] : size[0]; + if (size.length > 2 && size[2] !== '') attrs.min = size[2]; + if (size.length > 3 && size[3] !== '') attrs.max = size[3]; + } + return true; + }, _setup_select: function(field_name, field, attrs) { // No label on the widget itself delete(attrs.label); @@ -486,7 +502,7 @@ var et2_customfields_list = et2_valueWidget.extend([et2_IDetachedDOM, et2_IInput delete(attrs.label); attrs.label = field.label; - + // Simple case, one widget for a custom field if(Object.keys(field.values).length == 1) { From fcd03a54c801c4c4eacb75e5bfe8fe3453eeb15e Mon Sep 17 00:00:00 2001 From: Ralf Becker Date: Wed, 8 Oct 2014 12:16:13 +0000 Subject: [PATCH 13/97] refactored code to always run Nathans fix (before it was only run for ajax requests, not for initial request) --- .../class.etemplate_widget_nextmatch.inc.php | 88 +++++++++---------- 1 file changed, 41 insertions(+), 47 deletions(-) diff --git a/etemplate/inc/class.etemplate_widget_nextmatch.inc.php b/etemplate/inc/class.etemplate_widget_nextmatch.inc.php index 587e730d3c..2de4c4c732 100644 --- a/etemplate/inc/class.etemplate_widget_nextmatch.inc.php +++ b/etemplate/inc/class.etemplate_widget_nextmatch.inc.php @@ -164,7 +164,7 @@ class etemplate_widget_nextmatch extends etemplate_widget } if($value['num_rows'] != 0) { - $total = self::call_get_rows($send_value, $send_value['rows'], self::$request->readonlys); + $total = self::call_get_rows($send_value, $send_value['rows'], self::$request->readonlys, null, null, $this); } if (true) $value =& self::get_array(self::$request->content, $form_name, true); @@ -344,8 +344,7 @@ class etemplate_widget_nextmatch extends etemplate_widget $value['csv_export'] = 'refresh'; } $rows = $result['data'] = $result['order'] = array(); - // we can NOT run run_beforeSendToClient in call_get_rows, as it would modify row_modified timestamp, which is used below - $result['total'] = self::call_get_rows($value, $rows, $result['readonlys'], null, null, false); + $result['total'] = self::call_get_rows($value, $rows, $result['readonlys'], null, null, $template); $result['lastModification'] = egw_time::to('now', 'ts')-1; if (isset($GLOBALS['egw_info']['flags']['app_header']) && self::$request->app_header != $GLOBALS['egw_info']['flags']['app_header']) @@ -356,24 +355,6 @@ class etemplate_widget_nextmatch extends etemplate_widget $row_id = isset($value['row_id']) ? $value['row_id'] : 'id'; $row_modified = $value['row_modified']; - - if($template && $template->attrs['template']) - { - $row_template = $template->getElementById($template->attrs['template']); - if(!$row_template) - { - $row_template = etemplate_widget_template::instance($template->attrs['template']); - } - - // Try to find just the repeating part - $repeating_child = null; - // First child should be a grid, we want last row - foreach($row_template->children[0]->children[1]->children as $child) - { - if($child->type == 'row') $repeating_child = $child; - } - $row_template = $repeating_child ? $repeating_child : null; - } foreach($rows as $n => $row) { @@ -386,30 +367,20 @@ class etemplate_widget_nextmatch extends etemplate_widget $id = $row_id ? $row[$row_id] : $n; $result['order'][] = $id; + $modified = $row[$row_modified]; + if (isset($modified) && !(is_int($modified) || is_string($modified) && is_numeric($modified))) + { + $modified = egw_time::to(str_replace('Z', '', $modified), 'ts'); + } + // check if we need to send the data //error_log("$id Known: " . (array_search($id, $knownUids) !== false ? 'Yes' : 'No') . ' Modified: ' . egw_time::to($row[$row_modified]) . ' > ' . egw_time::to($lastModified).'? ' . ($row[$row_modified] > $lastModified ? 'Yes' : 'No')); if (!$row_id || !$knownUids || ($kUkey = array_search($id, $knownUids)) === false || - !$lastModified || !isset($row[$row_modified]) || $row[$row_modified] > $lastModified) + !$lastModified || !isset($modified) || $modified > $lastModified) { - if($row_template) - { - // Change anything by widget for each row ($row set to 1) - $_row = array(1 => &$row); - $row_template->run('set_row_value', array('',array('row' => 1), &$_row), true); - $result['data'][$id] = $row; - } - else if (!$template || get_class($template) != 'etemplate_widget_historylog') - { - // Fallback based on widget names - error_log(self::$request->template['name'] . ' had to fallback to run_beforeSendToClient() because it could not find the row' ); - $result['data'][$id] = self::run_beforeSendToClient($row); - } - else - { - $result['data'][$id] = $row; - } + $result['data'][$id] = $row; } - + if ($kUkey !== false) unset($knownUids[$kUkey]); } else // non-row data set by get_rows method @@ -564,9 +535,10 @@ class etemplate_widget_nextmatch extends etemplate_widget * @param array &$readonlys =null * @param object $obj =null (internal) * @param string|array $method =null (internal) + * @param etemplate_widget $widget =null instanciated nextmatch widget to let it's widgets transform each row * @return int|boolean total items found of false on error ($value['get_rows'] not callable) */ - private static function call_get_rows(array &$value,array &$rows,array &$readonlys=null,$obj=null,$method=null, $run_beforeSendToClient=true) + private static function call_get_rows(array &$value,array &$rows,array &$readonlys=null,$obj=null,$method=null, etemplate_widget $widget=null) { if (is_null($method)) $method = $value['get_rows']; @@ -618,13 +590,23 @@ class etemplate_widget_nextmatch extends etemplate_widget { $total = false; // method not callable } - /* no automatic fallback to start=0 - if ($method && $total && $value['start'] >= $total) + // if we have a nextmatch widget, find the repeating row + if ($widget && $widget->attrs['template']) { - $value['start'] = 0; - $total = self::call_get_rows($value,$rows,$readonlys,$obj,$method); + $row_template = $widget->getElementById($widget->attrs['template']); + if(!$row_template) + { + $row_template = etemplate_widget_template::instance($widget->attrs['template']); + } + + // Try to find just the repeating part + $repeating_row = null; + // First child should be a grid, we want last row + foreach($row_template->children[0]->children[1]->children as $child) + { + if($child->type == 'row') $repeating_row = $child; + } } - */ // otherwise we might get stoped by max_excutiontime if ($total > 200) @set_time_limit(0); @@ -648,7 +630,19 @@ class etemplate_widget_nextmatch extends etemplate_widget $row['parent_id'] = $row[$parent_id]; // seems NOT used on client! } // run beforeSendToClient methods of widgets in row on row-data - $rows[$n-$first+$value['start']] = $run_beforeSendToClient ? self::run_beforeSendToClient($row) : $row; + if($repeating_row) + { + // Change anything by widget for each row ($row set to 1) + $_row = array(1 => &$row); + $repeating_row->run('set_row_value', array('',array('row' => 1), &$_row), true); + } + else if (!$widget || get_class($widget) != 'etemplate_widget_historylog') + { + // Fallback based on widget names + error_log(self::$request->template['name'] . ' had to fallback to run_beforeSendToClient() because it could not find the row'); + $row = self::run_beforeSendToClient($row); + } + $rows[$n-$first+$value['start']] = $row; } elseif(!is_numeric($n)) // rows with string-keys, after numeric rows { From 5a888ec8124d514e90f95b867e48a54198aae2e4 Mon Sep 17 00:00:00 2001 From: Ralf Becker Date: Wed, 8 Oct 2014 12:57:04 +0000 Subject: [PATCH 14/97] pending translations from our translation server --- infolog/lang/egw_de.lang | 2 +- mail/lang/egw_nl.lang | 12 ++++++------ 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/infolog/lang/egw_de.lang b/infolog/lang/egw_de.lang index 7ad017c679..557f8ecdd2 100644 --- a/infolog/lang/egw_de.lang +++ b/infolog/lang/egw_de.lang @@ -40,7 +40,7 @@ add a new entry infolog de einen neuen Eintrag anlegen add a new note infolog de eine neue Notiz anlegen add a new phonecall infolog de einen neuen Telefonanruf anlegen add a new sub-task, -note, -call to this entry infolog de einen neuen Untereintrag erstellen. -add a new todo infolog de einen neuen Aufgage anlegen +add a new todo infolog de eine neue Aufgabe anlegen add file infolog de Datei hinzufügen add or delete links infolog de Verknüpfungen hinzufügen oder löschen add sub infolog de neuen Untereintrag anlegen diff --git a/mail/lang/egw_nl.lang b/mail/lang/egw_nl.lang index 388a6cb9c3..d4b83ef86f 100644 --- a/mail/lang/egw_nl.lang +++ b/mail/lang/egw_nl.lang @@ -187,11 +187,11 @@ get acl rights failed from imap server! mail nl Ophalen ACL rechten van IMAP ser greater than mail nl groter dan header mail nl Kop header lines mail nl Kop lijnen -home page folders mail nl Thuis pagina mappen +home page folders mail nl Thuis mappen hostname or ip mail nl Hostnaam of IP how often to check with the server for new mail mail nl Hoe vaak moet de server controleren op nieuwe mail how should the available information on identities be displayed admin nl Hoe moet de informatie voor identiteiten worden getoond -how to forward messages mail nl hoe berichten doorgestuurd moeten worden +how to forward messages mail nl hoe berichten moeten worden doorgestuurd html mail nl HTML html mode mail nl HTML mode identity mail nl Identiteit @@ -218,7 +218,7 @@ import of message %1 failed. could not save message to folder %2 due to: %3 mail import of message %1 failed. destination folder %2 does not exist. mail nl Importeren van bericht %1 mislukt. Opgegeven map %2 bestaat niet. import of message %1 failed. destination folder not set. mail nl Importeren van bericht %1 mislukt. Geen map gekozen om bericht op te slaan. import of message %1 failed. no contacts to merge and send to specified. mail nl Importeren van bericht %1 mislukt. Geen contacten gevonden om mee samen te voegen. -importance mail nl Belangrijkheid +importance mail nl Mate van belang important mail nl belangrijk in mailbox: %1, with id: %2, and partid: %3 mail nl In Berichtenmap: %1 met ID:%@ en PartID: %3 inbox mail nl POSTVAK IN @@ -399,7 +399,7 @@ subject(z->a) mail nl Onderwerp (Z->A) subscribe folder mail nl Abonneer op map subscribe folder ... mail nl Abonneer op map.... subscription folders mail nl Gabonneerde mappen -subscription successfully saved. mail nl Abonenment succesvol bewaard. +subscription successfully saved. mail nl Het abonnerren op deze map is succesvol bewaard. successfully connected mail nl Succesvol verbonden template folder mail nl Sjabloon folder templates mail nl Sjablonen @@ -417,7 +417,7 @@ the message sender has requested a response to indicate that you have read this the mimeparser can not parse this message. mail nl De mimeparsers kan dit bericht niet lezen. the rule with priority %1 successfully saved! mail nl De regel met prioriteit %1 is succesvol bewaard! then mail nl DAN -there is no imap server configured. mail nl Er is geen Imap server geconfigureerd +there is no imap server configured. mail nl Er is geen IMAP server geconfigureerd timeout on connections to your imap server mail nl Timeout op connecties met de IMAP server to do mail nl te doen toggle all folders view for %1 mail nl Toogle alle mappen in overzicht %1 @@ -427,7 +427,7 @@ trust servers seen / unseen info mail nl Vertrouw GETOONDE / NIET GETOONDE info trust the server when retrieving the folder status. if you select no, we will search for the unseen messages and count them ourselves mail nl Vertrouw de server als de status van een map wordt opgehaald. Als u nee selecteert, zullen wij naar de nog niet bekeken berichten zoeken en deze zelf tellen. trying to recover from session data mail nl probeer vanaf sessie data te herstellen. turn off horizontal line between signature and composed message (this is not according to rfc).
if you use templates, this option is only applied to the text part of the message. mail nl Zet de horizontale liniaal uit tussen de handtekening en het geschreven bericht (Dit is niet volgens RFC). Als u sjablonen gebruikt, is deze optie alleen van toepassing op het gedeelte van de tekst van het bericht. -unable to fetch vacation! mail nl Niet mogelijk afwezigheidsbericht te laden. +unable to fetch vacation! mail nl Niet mogelijk afwezigheidsbericht te tonen. undelete mail nl Verwijderen ongedaan maken unflagged mail nl Niet gemarkeerd unread mail nl ongelezen From 5e9676e77021d1240fd7cdf48e061e2d8be99a04 Mon Sep 17 00:00:00 2001 From: Nathan Gray Date: Wed, 8 Oct 2014 17:10:58 +0000 Subject: [PATCH 15/97] Fix broken relative width calculation after r48947. --- etemplate/js/et2_dataview.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/etemplate/js/et2_dataview.js b/etemplate/js/et2_dataview.js index ba5da99e8c..bd5353c395 100644 --- a/etemplate/js/et2_dataview.js +++ b/etemplate/js/et2_dataview.js @@ -414,7 +414,7 @@ var et2_dataview = Class.extend({ { var col = self.columnMgr.columns[i]; if(col == this || col.fixedWidth) continue; - col.set_width(self.columns[i].width / relative); + col.set_width(self.columnMgr.columnWidths[i] / relative); } // Triggers column change callback, which saves self.updateColumns(); From 2ca305b5cc67d0bf2bdfaca2ee3face01a1ce0d6 Mon Sep 17 00:00:00 2001 From: Nathan Gray Date: Wed, 8 Oct 2014 17:27:39 +0000 Subject: [PATCH 16/97] Remove auto refresh options for 30 seconds and 1 minute, as apparently they cause problems with mail. Add auto refresh options for 15 and 30 minutes. --- etemplate/js/et2_extension_nextmatch.js | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/etemplate/js/et2_extension_nextmatch.js b/etemplate/js/et2_extension_nextmatch.js index b22bee2446..3fdc557b3e 100644 --- a/etemplate/js/et2_extension_nextmatch.js +++ b/etemplate/js/et2_extension_nextmatch.js @@ -1251,9 +1251,12 @@ var et2_nextmatch = et2_DOMWidget.extend([et2_IResizeable, et2_IInput], autoRefresh.set_id("nm_autorefresh"); autoRefresh.set_select_options({ 0: "off", - 30: "30 seconds", - 60: "1 Minute", - 300: "5 Minutes" + // Cause [unknown] problems with mail + //30: "30 seconds", + //60: "1 Minute", + 300: "5 Minutes", + 900: "15 Minutes", + 1800: "30 Minutes" }); autoRefresh.set_value(this._get_autorefresh()); autoRefresh.set_statustext(egw.lang("Automatically refresh list")); From 178bca7f7a23a4661f0cdb50e4f46c4f7256d343 Mon Sep 17 00:00:00 2001 From: Ralf Becker Date: Wed, 8 Oct 2014 20:02:59 +0000 Subject: [PATCH 17/97] fix redirect loop initiated eg. by saving a mail, caused by document.location=url triggering etemplate unload handler and destroying et2 request, identical redirect url detected by jdots framework causes refresh via nextmatch --> next redirect --- etemplate/inc/class.etemplate_request.inc.php | 2 ++ etemplate/js/et2_extension_nextmatch.js | 26 +++++++++++-------- etemplate/js/etemplate2.js | 18 ++++++++++++- mail/js/app.js | 20 ++++---------- 4 files changed, 39 insertions(+), 27 deletions(-) diff --git a/etemplate/inc/class.etemplate_request.inc.php b/etemplate/inc/class.etemplate_request.inc.php index 0b297ed2fd..7f36dd36b9 100644 --- a/etemplate/inc/class.etemplate_request.inc.php +++ b/etemplate/inc/class.etemplate_request.inc.php @@ -177,6 +177,8 @@ class etemplate_request list($app) = explode('.', $_GET['menuaction']); $index_url = isset($GLOBALS['egw_info']['apps'][$app]['index']) ? '/index.php?menuaction='.$GLOBALS['egw_info']['apps'][$app]['index'] : '/'.$app.'/index.php'; + // add a unique token to redirect to avoid client-side framework tries refreshing via nextmatch + $index_url .= (strpos($index_url, '?') ? '&' : '?').'redirect='.microtime(true); error_log(__METHOD__."('$id', ...) eT2 request not found / expired --> redirecting app $app to $index_url (_GET[menuaction]=$_GET[menuaction], isJSONRequest()=".array2string(egw_json_request::isJSONRequest()).')'); if (egw_json_request::isJSONRequest()) { diff --git a/etemplate/js/et2_extension_nextmatch.js b/etemplate/js/et2_extension_nextmatch.js index 3fdc557b3e..cc2b13f2ba 100644 --- a/etemplate/js/et2_extension_nextmatch.js +++ b/etemplate/js/et2_extension_nextmatch.js @@ -113,7 +113,7 @@ var et2_nextmatch = et2_DOMWidget.extend([et2_IResizeable, et2_IInput], "description": "Hide the second filter", "default": et2_no_init, }, - + "onselect": { "name": "onselect", "type": "js", @@ -206,7 +206,7 @@ var et2_nextmatch = et2_DOMWidget.extend([et2_IResizeable, et2_IInput], // Unbind handler used for toggling autorefresh $j(this.getInstanceManager().DOMContainer.parentNode).off('show.et2_nextmatch'); $j(this.getInstanceManager().DOMContainer.parentNode).off('hide.et2_nextmatch'); - + // Free the grid components this.dataview.free(); this.rowProvider.free(); @@ -303,10 +303,14 @@ var et2_nextmatch = et2_DOMWidget.extend([et2_IResizeable, et2_IInput], * Implements the et2_IResizeable interface - lets the dynheight manager * update the width and height and then update the dataview container. */ - resize: function() { - this.dynheight.update(function(_w, _h) { - this.dataview.resize(_w, _h); - }, this); + resize: function() + { + if (this.dynheight) + { + this.dynheight.update(function(_w, _h) { + this.dataview.resize(_w, _h); + }, this); + } }, /** @@ -731,7 +735,7 @@ var et2_nextmatch = et2_DOMWidget.extend([et2_IResizeable, et2_IInput], if(this.options.settings.columnselection_pref && app) { var size_pref = this.options.settings.columnselection_pref +"-size"; - + // If columnselection pref is missing prefix, add it in if(size_pref.indexOf('nextmatch') == -1) { @@ -902,7 +906,7 @@ var et2_nextmatch = et2_DOMWidget.extend([et2_IResizeable, et2_IInput], var oldCols = this.activeFilters.selectcols ? this.activeFilters.selectcols : []; this.activeFilters.selectcols = colDisplay; - + // We don't need to re-query if they've removed a column var changed = []; ColLoop: @@ -1094,7 +1098,7 @@ var et2_nextmatch = et2_DOMWidget.extend([et2_IResizeable, et2_IInput], this.options.settings.dataStorePrefix = list[0]; } this.controller.setPrefix(this.options.settings.dataStorePrefix); - + // Load the initial order /*this.controller.loadInitialOrder(this._getInitialOrder( this.options.settings.rows, this.options.settings.row_id @@ -1437,7 +1441,7 @@ var et2_nextmatch = et2_DOMWidget.extend([et2_IResizeable, et2_IInput], this._autorefresh_timer = setTimeout(jQuery.proxy(function() { // Check in case it was stopped / destroyed since if(!this._autorefresh_timer || !this.getInstanceManager()) return; - + $j(this.getInstanceManager().DOMContainer.parentNode).one('show.et2_nextmatch', // Important to use anonymous function instead of just 'this.refresh' because // of the parameters passed @@ -1621,7 +1625,7 @@ var et2_nextmatch = et2_DOMWidget.extend([et2_IResizeable, et2_IInput], this.resize(); } }, - + /** * Actions are handled by the controller, so ignore these during init. * diff --git a/etemplate/js/etemplate2.js b/etemplate/js/etemplate2.js index 5507f3bd30..46bce852d6 100644 --- a/etemplate/js/etemplate2.js +++ b/etemplate/js/etemplate2.js @@ -124,7 +124,7 @@ etemplate2.prototype.resize = function() etemplate2.prototype.clear = function() { $j(this.DOMContainer).trigger('clear'); - + // Remove any handlers on window (resize) if(this.uniqueId) { @@ -246,6 +246,22 @@ etemplate2.prototype.unbind_unload = function() delete this.destroy_session; }; +/** + * Download a URL not triggering our unload handler and therefore destroying our et2 request + * + * @param {string} _url + */ +etemplate2.prototype.download = function(_url) +{ + // need to unbind unload handler to NOT destroy et2 session + this.unbind_unload(); + + document.location = _url; + + // bind unload handler again (can NOT do it direct, as this would be quick enough to be still triggered!) + window.setTimeout(jQuery.proxy(this.bind_unload, this), 100); +}; + /** * Loads the template from the given URL and sets the data object * diff --git a/mail/js/app.js b/mail/js/app.js index 69f86e98aa..b8228889c7 100644 --- a/mail/js/app.js +++ b/mail/js/app.js @@ -2174,7 +2174,7 @@ app.classes.mail = AppJS.extend( url += 'menuaction=mail.mail_ui.saveMessage'; // todo compose for Draft folder url += '&id='+_elems[0].id; //window.open(url,'_blank','dependent=yes,width=100,height=100,scrollbars=yes,status=yes'); - document.location = url; + this.et2._inst.download(url); }, /** @@ -2310,8 +2310,7 @@ app.classes.mail = AppJS.extend( windowName = windowName+'displayAttachment_'+mailid+'_'+attgrid.partID; width = 870; height = 600; - //document.location = url; - //return; + break; } egw_openWindowCentered(url,windowName,width,height); }, @@ -2418,8 +2417,7 @@ app.classes.mail = AppJS.extend( windowName = windowName+'displayAttachment_'+attgrid.file.replace(/\//g,"_"); width = 870; height = 600; - //document.location = url; - //return; + break; } egw_openWindowCentered(url,windowName,width,height); }, @@ -2441,15 +2439,12 @@ app.classes.mail = AppJS.extend( attgrid = this.et2.getArrayMgr("content").getEntry('mail_displayattachments')[widget.id.replace(/\[save\]/,'')]; } var url = window.egw_webserverUrl+'/index.php?'; - var width; - var height; - var windowName ='mail'; url += 'menuaction=mail.mail_ui.getAttachment'; // todo compose for Draft folder url += '&mode=save'; url += '&id='+mailid; url += '&part='+attgrid.partID; url += '&is_winmail='+attgrid.winmailFlag; - document.location = url; + this.et2._inst.download(url); }, saveAllAttachmentsToZip: function(tag_info, widget) @@ -2469,13 +2464,10 @@ app.classes.mail = AppJS.extend( attgrid = this.et2.getArrayMgr("content").getEntry('mail_displayattachments')[widget.id.replace(/\[save\]/,'')]; } var url = window.egw_webserverUrl+'/index.php?'; - var width; - var height; - var windowName ='mail'; url += 'menuaction=mail.mail_ui.download_zip'; // todo compose for Draft folder url += '&mode=save'; url += '&id='+mailid; - document.location = url; + this.et2._inst.download(url); }, saveAttachmentToVFS: function(tag_info, widget) @@ -2573,8 +2565,6 @@ app.classes.mail = AppJS.extend( url += '&method=mail.mail_ui.vfsSaveMessage'; url += '&id='+_elems[0].id; url += '&label=Save'; - //window.open(url,'_blank','dependent=yes,width=100,height=100,scrollbars=yes,status=yes') - //document.location = url; egw_openWindowCentered(url,'vfs_save_message_'+_elems[0].id,'640','570',window.outerWidth/2,window.outerHeight/2); }, From 4e89ed6520adf60b4dee24e92945fcc18cdde6d7 Mon Sep 17 00:00:00 2001 From: Ralf Becker Date: Thu, 9 Oct 2014 07:45:59 +0000 Subject: [PATCH 18/97] allow to set $query[session_list]="active" to filter out sync sessions --- admin/inc/class.admin_accesslog.inc.php | 48 +++++++++++++++---------- 1 file changed, 29 insertions(+), 19 deletions(-) diff --git a/admin/inc/class.admin_accesslog.inc.php b/admin/inc/class.admin_accesslog.inc.php index 57b7bf97eb..0b1b4ad52a 100644 --- a/admin/inc/class.admin_accesslog.inc.php +++ b/admin/inc/class.admin_accesslog.inc.php @@ -5,7 +5,7 @@ * @link http://www.egroupware.org * @author Ralf Becker * @package admin - * @copyright (c) 2009-11 by Ralf Becker + * @copyright (c) 2009-14 by Ralf Becker * @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License * @version $Id$ */ @@ -48,13 +48,14 @@ class admin_accesslog function __construct() { $this->so = new so_sql(self::APP,self::TABLE,null,'',true); - $this->so->timestamps = array('li', 'lo', 'session_dla', 'notification_hartbeat'); + $this->so->timestamps = array('li', 'lo', 'session_dla', 'notification_heartbeat'); } /** * query rows for the nextmatch widget * - * @param array $query with keys 'start', 'search', 'order', 'sort', 'col_filter' + * @param array $query with keys 'start', 'search', 'order', 'sort', 'col_filter' and + * 'session_list' true: all sessions, false: whole access-log, 'active': only sessions with session-status active (browser, no sync) * @param array &$rows returned rows/competitions * @param array &$readonlys eg. to disable buttons based on acl, not use here, maybe in a derived class * @return int total number of rows @@ -67,23 +68,32 @@ class admin_accesslog { $query['col_filter']['lo'] = null; // not logged out $query['col_filter'][0] = 'session_dla > '.(int)(time() - $GLOBALS['egw_info']['server']['sessions_timeout']); - $query['col_filter'][1] = "(notification_heartbeat IS NULL OR notification_heartbeat > $heartbeat_limit)"; + switch((string)$query['session_list']) + { + case 'active': // remove status != 'active', eg. CalDAV/eSync + $query['col_filter'][1] = "notification_heartbeat > $heartbeat_limit"; + $query['col_filter'][3] = "session_php NOT LIKE '% %'"; // remove blocked, bad login, etc + break; + default: + $query['col_filter'][1] = "(notification_heartbeat IS NULL OR notification_heartbeat > $heartbeat_limit)"; + break; + } $query['col_filter'][2] = 'account_id>0'; } $total = $this->so->get_rows($query,$rows,$readonlys); - $no_kill = !$GLOBALS['egw']->acl->check('current_sessions_access',8,'admin') && !$query['session_list']; + $heartbeat_limit_user = egw_time::server2user($heartbeat_limit, 'ts'); foreach($rows as &$row) { $row['sessionstatus'] = lang('success'); - if ($row['notification_heartbeat'] > $heartbeat_limit) + if ($row['notification_heartbeat'] > $heartbeat_limit_user) { $row['sessionstatus'] = lang('active'); } if (stripos($row['session_php'],'blocked') !== false || stripos($row['session_php'],'bad login') !== false || - strpos($row['sessioin_php'],' ') !== false) + strpos($row['session_php'],' ') !== false) { $row['sessionstatus'] = $row['session_php']; } @@ -120,9 +130,9 @@ class admin_accesslog /** * Display the access log or session list * - * @param array $content=null - * @param string $msg='' - * @param boolean $sessions_list=false + * @param array $content =null + * @param string $msg ='' + * @param boolean $sessions_list =false */ function index(array $content=null, $msg='', $sessions_list=false) { @@ -183,7 +193,8 @@ class admin_accesslog @set_time_limit(0); // switch off the execution time limit, as it's for big selections to small $query['num_rows'] = -1; // all - $total = $this->get_rows($query,$all,$readonlys); + $all = $readonlys = array(); + $this->get_rows($query,$all,$readonlys); $content['nm']['selected'] = array(); foreach($all as $session) { @@ -196,7 +207,7 @@ class admin_accesslog } else { - + $success = $failed = $action = $action_msg = null; if ($this->action($content['nm']['action'],$content['nm']['selected'] ,$success,$failed,$action_msg,$msg)) { // In case of action success @@ -229,7 +240,7 @@ class admin_accesslog __LINE__,__FILE__)->fetchColumn(); $tmpl = new etemplate_new('admin.accesslog'); - $tmpl->exec('admin.admin_accesslog.index',$content,$sel_options,$readonlys,array( + $tmpl->exec('admin.admin_accesslog.index', $content, array(), $readonlys, array( 'nm' => $content['nm'], )); } @@ -243,10 +254,9 @@ class admin_accesslog * @param type $success * @param int $failed * @param type $action_msg - * @param type $msg * @return type number of failed */ - function action($action,$checked,&$success,&$failed,&$action_msg,&$msg) + function action($action,$checked,&$success,&$failed,&$action_msg) { $success = $failed = 0; //error_log(__METHOD__.'selected:' . array2string($checked). 'action:' . $action); @@ -297,7 +307,7 @@ class admin_accesslog */ private static function get_actions($sessions_list) { - + $group = 0; if ($sessions_list) { // error_log(__METHOD__. $sessions_list); @@ -343,11 +353,11 @@ class admin_accesslog /** * Display session list * - * @param array $content=null - * @param string $msg='' + * @param array $content =null + * @param string $msg ='' */ function sessions(array $content=null, $msg='') { - return $this->index(null,$msg,true); + return $this->index($content, $msg, true); } } From 659ca5eb4f7c016a3c4b86c56ae14329202426c3 Mon Sep 17 00:00:00 2001 From: Ralf Becker Date: Thu, 9 Oct 2014 08:21:40 +0000 Subject: [PATCH 19/97] query new notifications right after login and therefore also show up as "active" in session-status --- .../inc/class.notifications_email.inc.php | 11 +-- notifications/js/notificationajaxpopup.js | 68 +++++++++++-------- 2 files changed, 45 insertions(+), 34 deletions(-) diff --git a/notifications/inc/class.notifications_email.inc.php b/notifications/inc/class.notifications_email.inc.php index e49a96d2fe..0ea12d31fe 100644 --- a/notifications/inc/class.notifications_email.inc.php +++ b/notifications/inc/class.notifications_email.inc.php @@ -108,11 +108,14 @@ class notifications_email implements notifications_iface { $this->mail->AltExtended = $_attachments[0]->string; $this->mail->AltExtendedContentType = $_attachments[0]->type; unset($_attachments[0]); + $this->mail->Body = $body_plain; + } + else + { + $this->mail->IsHTML(); + $this->mail->Body = $body_html; + $this->mail->AltBody = $body_plain; } - $this->mail->IsHTML(($isMeetingRequestNotif?false:true)); - $this->mail->Body = $body_html; - $this->mail->AltBody = $body_plain; - if(is_array($_attachments) && count($_attachments) > 0) { foreach($_attachments as $attachment) diff --git a/notifications/js/notificationajaxpopup.js b/notifications/js/notificationajaxpopup.js index 0b2519b008..0aca6d5d48 100644 --- a/notifications/js/notificationajaxpopup.js +++ b/notifications/js/notificationajaxpopup.js @@ -1,6 +1,6 @@ /** * EGroupware Notifications - clientside javascript - * + * * @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License * @package notifications * @subpackage ajaxpoup @@ -13,10 +13,10 @@ * Installs app.notifications used to poll notifications from server and display them */ (function() -{ +{ var notifymessages = {}; - var EGW_BROWSER_NOTIFY_ALLOWED = 0; - + var EGW_BROWSER_NOTIFY_ALLOWED = 0; + /** * Constructor inits polling and installs handlers, polling frequence is passed via data-poll-interval of script tag */ @@ -24,10 +24,22 @@ var notification_script = document.getElementById('notifications_script_id'); var popup_poll_interval = notification_script && notification_script.getAttribute('data-poll-interval'); this.setTimeout(popup_poll_interval || 60); - var self = this; - jQuery('#egwpopup_ok_button').click(function() { self.button_ok.apply(self); }); - jQuery('#egwpopup_close_button').click(function() { self.button_close.apply(self); }); - jQuery('#notificationbell').click(function() { self.display.apply(self); }); + jQuery('#egwpopup_ok_button').click(jQuery.proxy(this.button_ok, this)); + jQuery('#egwpopup_close_button').click(jQuery.proxy(this.button_close, this)); + jQuery('#notificationbell').click(jQuery.proxy(this.display, this)); + // query notifictions now + this.get_notifications(); + }; + + /** + * Poll server for new notifications + */ + notifications.prototype.get_notifications = function() + { + egw.json( + "notifications.notifications_ajax.get_notifications", + this.check_browser_notify() + ).sendRequest(); }; /** @@ -37,22 +49,18 @@ notifications.prototype.setTimeout = function(_i) { var self = this; window.setTimeout(function(){ - var request = egw.json( - "notifications.notifications_ajax.get_notifications", - self.check_browser_notify() - ); - request.sendRequest(); + self.get_notifications(); self.setTimeout(_i); }, _i*1000); }; - + /** * Check to see if browser supports / allows desktop notifications */ notifications.prototype.check_browser_notify = function() { return window.webkitNotifications && window.webkitNotifications.checkPermission() == EGW_BROWSER_NOTIFY_ALLOWED; }; - + /** * Display notifications window */ @@ -91,17 +99,17 @@ } catch(err) {} var desktop_button = jQuery('') .click(function() { - window.webkitNotifications.requestPermission(); + window.webkitNotifications.requestPermission(); jQuery(this).hide(); }); desktop_button.appendTo(jQuery(egwpopup_ok_button).parent()); } }; - + /** * Display or hide notifcation-bell - * - * @param String mode "active" + * + * @param {string} mode "active" */ notifications.prototype.bell = function(mode) { var notificationbell; @@ -112,7 +120,7 @@ notificationbell.style.display = "none"; } }; - + /** * Callback for OK button: confirms message on server and hides display */ @@ -122,22 +130,22 @@ egwpopup = document.getElementById("egwpopup"); egwpopup_message = document.getElementById("egwpopup_message"); egwpopup_message.scrollTop = 0; - + for(var confirmed in notifymessages) break; var request = egw.json("notifications.notifications_ajax.confirm_message", [confirmed]); request.sendRequest(); delete notifymessages[confirmed]; - + for(var id in notifymessages) break; if (id == undefined) { egwpopup.style.display = "none"; egwpopup_message.innerHTML = ""; this.bell("inactive"); - } else { + } else { this.display(); } }; - + /** * Callback for close button: close and mark all as read */ @@ -155,10 +163,10 @@ egwpopup_message.innerHTML = ""; this.bell("inactive"); }; - + /** * Add message to internal display-queue - * + * * @param _id * @param _message * @param _browser_notify @@ -171,7 +179,7 @@ } // Prevent the same thing popping up multiple times notifymessages[_id] = _message; - + // Notification API if(_browser_notify) { @@ -193,7 +201,7 @@ _message = _message.replace(/<(?:.|\n)*?>/gm, ''); } notice = webkitNotifications.createNotification('', "Egroupware",_message); - + // When they click, bring up the popup for full info notice.onclick = function() { window.focus(); @@ -205,7 +213,7 @@ { notice.ondisplay = function() { // Confirm when user gets to see it - no close needed - // Wait a bit to let it load first, or it might not be there when requested. + // Wait a bit to let it load first, or it might not be there when requested. window.setTimeout( function() { var request = egw.json("notifications.notifications_ajax.confirm_message", _id); request.sendRequest(); @@ -215,7 +223,7 @@ } } }; - + var lab = egw_LAB || $LAB; var self = notifications; lab.wait(function(){ From 29cae751785df40435969d9ab1723806175b3b23 Mon Sep 17 00:00:00 2001 From: Ralf Becker Date: Thu, 9 Oct 2014 09:34:24 +0000 Subject: [PATCH 20/97] fix IDE warnings / documentation and move "use strict" from global scope to object itself --- phpgwapi/js/jsapi/egw_links.js | 49 ++++++++++++++--------------- phpgwapi/js/jsapi/egw_open.js | 57 +++++++++++++++++----------------- 2 files changed, 52 insertions(+), 54 deletions(-) diff --git a/phpgwapi/js/jsapi/egw_links.js b/phpgwapi/js/jsapi/egw_links.js index f84f68849d..06f1df02e9 100644 --- a/phpgwapi/js/jsapi/egw_links.js +++ b/phpgwapi/js/jsapi/egw_links.js @@ -10,8 +10,6 @@ * @version $Id$ */ -"use strict"; - /*egw:uses egw_core; */ @@ -19,7 +17,9 @@ /** * @augments Class */ -egw.extend('links', egw.MODULE_GLOBAL, function() { +egw.extend('links', egw.MODULE_GLOBAL, function() +{ + "use strict"; /** * Link registry @@ -54,9 +54,9 @@ egw.extend('links', egw.MODULE_GLOBAL, function() { /** * Check if $app is in the registry and has an entry for $name * - * @param string $app app-name - * @param string $name name / key in the registry, eg. 'view' - * @return boolean|string false if $app is not registered, otherwise string with the value for $name + * @param {string} _app app-name + * @param {string} _name name / key in the registry, eg. 'view' + * @return {boolean|string} false if $app is not registered, otherwise string with the value for $name * @memberOf egw */ link_get_registry: function(_app, _name) @@ -105,8 +105,8 @@ egw.extend('links', egw.MODULE_GLOBAL, function() { /** * Get mime-type information from app-registry * - * @param string _type - * @return array with values for keys 'menuaction', 'mime_id' (path) or 'mime_url' and options 'mime_popup' and other values to pass one + * @param {string} _type + * @return {object} with values for keys 'menuaction', 'mime_id' (path) or 'mime_url' and options 'mime_popup' and other values to pass one */ get_mime_info: function(_type) { @@ -127,9 +127,9 @@ egw.extend('links', egw.MODULE_GLOBAL, function() { /** * Get handler (link-data) for given path and mime-type * - * @param string|object _path vfs path or object with attr path or id, app2 and id2 (path=/apps/app2/id2/id) - * @param string _type mime-type, if not given in _path object - * @return string|object string with EGw relative link, array with get-parameters for '/index.php' or null (directory and not filemanager access) + * @param {string|object} _path vfs path or object with attr path or id, app2 and id2 (path=/apps/app2/id2/id) + * @param {string} _type mime-type, if not given in _path object + * @return {string|object} string with EGw relative link, array with get-parameters for '/index.php' or null (directory and not filemanager access) */ mime_open: function(_path, _type) { @@ -182,8 +182,8 @@ egw.extend('links', egw.MODULE_GLOBAL, function() { /** * Get list of link-aware apps the user has rights to use * - * @param string $must_support capability the apps need to support, eg. 'add', default ''=list all apps - * @return array with app => title pairs + * @param {string} _must_support capability the apps need to support, eg. 'add', default ''=list all apps + * @return {object} with app => title pairs */ link_app_list: function(_must_support) { @@ -218,8 +218,8 @@ egw.extend('links', egw.MODULE_GLOBAL, function() { /** * Set link registry * - * @param object _registry whole registry or entries for just one app - * @param string _app + * @param {object} _registry whole registry or entries for just one app + * @param {string} _app */ set_link_registry: function (_registry, _app) { @@ -238,10 +238,10 @@ egw.extend('links', egw.MODULE_GLOBAL, function() { * * Please note, the values of the query get url encoded! * - * @param string _url a url relative to the egroupware install root, it can contain a query too - * @param array|string _extravars query string arguements as string or array (prefered) + * @param {string} _url a url relative to the egroupware install root, it can contain a query too + * @param {object|string} _extravars query string arguements as string or array (prefered) * if string is used ambersands in vars have to be already urlencoded as '%26', function ensures they get NOT double encoded - * @return string generated url + * @return {string} generated url */ link: function(_url, _extravars) { @@ -332,11 +332,11 @@ egw.extend('links', egw.MODULE_GLOBAL, function() { * Query a title of _app/_id * * @param {string} _app - * @param {(string|int)} _id + * @param {string|number} _id * @param {function} _callback optinal callback, required if for responses from the server * @param {object} _context context for the callback * @param {boolean} _force_reload true load again from server, even if already cached - * @return string|boolean|null string with title if it exist in local cache or null if not + * @return {string|boolean|null} string with title if it exist in local cache or null if not */ link_title: function(_app, _id, _callback, _context, _force_reload) { @@ -374,7 +374,7 @@ egw.extend('links', egw.MODULE_GLOBAL, function() { /** * Callback to add all current title requests * - * @param array of parameters, only first parameter is used + * @param {object} _params of parameters, only first parameter is used */ link_title_before_send: function(_params) { @@ -396,7 +396,7 @@ egw.extend('links', egw.MODULE_GLOBAL, function() { /** * Callback for server response * - * @param object _response _app => _id => title + * @param {object} _response _app => _id => title */ link_title_callback: function(_response) { @@ -432,8 +432,7 @@ egw.extend('links', egw.MODULE_GLOBAL, function() { /** * Create quick add selectbox * - * @param _parent parent to create selectbox in - * @returns + * @param {DOMnode} _parent parent to create selectbox in */ link_quick_add: function(_parent) { @@ -461,6 +460,4 @@ egw.extend('links', egw.MODULE_GLOBAL, function() { }); } }; - }); - diff --git a/phpgwapi/js/jsapi/egw_open.js b/phpgwapi/js/jsapi/egw_open.js index 004adb0f31..9ddc90b824 100644 --- a/phpgwapi/js/jsapi/egw_open.js +++ b/phpgwapi/js/jsapi/egw_open.js @@ -10,8 +10,6 @@ * @version $Id$ */ -"use strict"; - /*egw:uses egw_core; egw_links; @@ -19,9 +17,12 @@ /** * @augments Class + * @param {object} _egw + * @param {DOMwindow} _wnd */ -egw.extend('open', egw.MODULE_WND_LOCAL, function(_egw, _wnd) { - +egw.extend('open', egw.MODULE_WND_LOCAL, function(_egw, _wnd) +{ + "use strict"; /** * Magic handling for mailto: uris using mail application. @@ -52,8 +53,8 @@ egw.extend('open', egw.MODULE_WND_LOCAL, function(_egw, _wnd) { var content = { to: mailto[1] || [], cc: match['cc'] || [], - bcc: match['bcc'] || [], - } + bcc: match['bcc'] || [] + }; // Get open compose windows var compose = egw.getOpenWindows("mail", /^compose_/); @@ -61,7 +62,7 @@ egw.extend('open', egw.MODULE_WND_LOCAL, function(_egw, _wnd) { { // No compose windows, might be no mail app.js // We really want to use mail_compose() here - egw.open('','mail','add',{'preset[mailto]': uri},'compose__','mail') + egw.open('','mail','add',{'preset[mailto]': uri},'compose__','mail'); } if(compose.length == 1) { @@ -120,13 +121,13 @@ egw.extend('open', egw.MODULE_WND_LOCAL, function(_egw, _wnd) { * - egw.open(123,'addressbook','view') opens addressbook view for entry 123 (showing linked infologs) * - egw.open('','addressbook','view_list',{ search: 'Becker' }) opens list of addresses containing 'Becker' * - * @param string|int|object id_data either just the id or if app=="" "app:id" or object with all data + * @param {string}|int|object id_data either just the id or if app=="" "app:id" or object with all data * to be able to open files you need to give: (mine-)type, path or id, app2 and id2 (path=/apps/app2/id2/id" - * @param string app app-name or empty (app is part of id) - * @param string type default "edit", possible "view", "view_list", "edit" (falls back to "view") and "add" - * @param object|string extra extra url parameters to append as object or string - * @param string target target of window to open - * @param string target_app target application to open in that tab + * @param {string} app app-name or empty (app is part of id) + * @param {string} type default "edit", possible "view", "view_list", "edit" (falls back to "view") and "add" + * @param {object|string} extra extra url parameters to append as object or string + * @param {string} target target of window to open + * @param {string} target_app target application to open in that tab * @memberOf egw */ open: function(id_data, app, type, extra, target, target_app) @@ -234,11 +235,11 @@ egw.extend('open', egw.MODULE_WND_LOCAL, function(_egw, _wnd) { /** * Open a link, which can be either a menuaction, a EGroupware relative url or a full url * - * @param string _link menuaction, EGroupware relative url or a full url (incl. "mailto:" or "javascript:") - * @param string _target optional target / window name - * @param string _popup widthxheight, if a popup should be used - * @param string _target_app app-name for opener - * @param boolean _check_popup_blocker TRUE check if browser pop-up blocker is on/off, FALSE no check + * @param {string} _link menuaction, EGroupware relative url or a full url (incl. "mailto:" or "javascript:") + * @param {string} _target optional target / window name + * @param {string} _popup widthxheight, if a popup should be used + * @param {string} _target_app app-name for opener + * @param {boolean} _check_popup_blocker TRUE check if browser pop-up blocker is on/off, FALSE no check * - This option only makes sense to be enabled when the open_link requested without user interaction */ open_link: function(_link, _target, _popup, _target_app, _check_popup_blocker) @@ -253,7 +254,7 @@ egw.extend('open', egw.MODULE_WND_LOCAL, function(_egw, _wnd) { if (_check_popup_blocker) { if (this._check_popupBlocker(_link, _target, _popup, _target_app)) return; - } + } var url = _link; if (url.indexOf('javascript:') == 0) { @@ -319,18 +320,18 @@ egw.extend('open', egw.MODULE_WND_LOCAL, function(_egw, _wnd) { _wnd.location.href = _url; } }, - + /** * Check if browser pop-up blocker is on/off - * - * @param string _link menuaction, EGroupware relative url or a full url (incl. "mailto:" or "javascript:") - * @param string _target optional target / window name - * @param string _popup widthxheight, if a popup should be used - * @param string _target_app app-name for opener - * + * + * @param {string} _link menuaction, EGroupware relative url or a full url (incl. "mailto:" or "javascript:") + * @param {string} _target optional target / window name + * @param {string} _popup widthxheight, if a popup should be used + * @param {string} _target_app app-name for opener + * * @return boolean returns false if pop-up blocker is off - * - returns true if pop-up blocker is on, - * - and re-call the open_link with provided parameters, after user interaction. + * - returns true if pop-up blocker is on, + * - and re-call the open_link with provided parameters, after user interaction. */ _check_popupBlocker: function(_link, _target, _popup, _target_app) { From 7348a62f619adb69ceb9610edcda0491d6de74f8 Mon Sep 17 00:00:00 2001 From: Ralf Becker Date: Thu, 9 Oct 2014 12:03:14 +0000 Subject: [PATCH 21/97] use implicit nameing after type for custom templates "addressbook.edit.$type" like in InfoLog --- addressbook/inc/class.addressbook_ui.inc.php | 7 ++----- addressbook/lang/egw_en.lang | 2 -- 2 files changed, 2 insertions(+), 7 deletions(-) diff --git a/addressbook/inc/class.addressbook_ui.inc.php b/addressbook/inc/class.addressbook_ui.inc.php index 625c2802ba..9aecc49323 100644 --- a/addressbook/inc/class.addressbook_ui.inc.php +++ b/addressbook/inc/class.addressbook_ui.inc.php @@ -2144,12 +2144,9 @@ window.egw_LAB.wait(function() { if ($content['private']) $content['owner'] .= 'p'; - //$GLOBALS['egw_info']['flags']['include_xajax'] = true; - - if (!$this->tmpl->read($this->content_types[$content['tid']]['options']['template'] ? $this->content_types[$content['tid']]['options']['template'] : 'addressbook.edit')) + // for custom types, check if we have a custom edit template named "addressbook.edit.$type", $type is the name + if (in_array($content['tid'], array('n',self::DELETED_TYPE)) || !$this->tmpl->read('addressbook.edit.'.$this->content_types[$content['tid']]['name'])) { - $content['msg'] = lang('WARNING: Template "%1" not found, using default template instead.', $this->content_types[$content['tid']]['options']['template'])."\n"; - $content['msg'] .= lang('Please update the templatename in your customfields section!'); $this->tmpl->read('addressbook.edit'); } diff --git a/addressbook/lang/egw_en.lang b/addressbook/lang/egw_en.lang index 0efeecaf35..c5c01e7b95 100644 --- a/addressbook/lang/egw_en.lang +++ b/addressbook/lang/egw_en.lang @@ -368,7 +368,6 @@ phone numbers common en Phone numbers photo addressbook en Photo please enter a name for that field ! addressbook en Enter a name to that field! please select only one category addressbook en Select only one category -please update the templatename in your customfields section! addressbook en Update the template name in custom fields section! postal common en Postal pref addressbook en Pref preferred email address to use in distribution lists addressbook en Preferred email address @@ -484,7 +483,6 @@ verification addressbook en Verification view linked infolog entries addressbook en View linked InfoLog entries warning!! ldap is valid only if you are not using contacts for accounts storage! admin en WARNING!! LDAP is valid only if you are NOT using contacts for accounts storage! warning: all contacts found will be deleted! addressbook en WARNING: All contacts found will be deleted! -warning: template "%1" not found, using default template instead. addressbook en WARNING: Template "%1" not found, using default template instead. weekday addressbook en Weekday what should links to the addressbook display in other applications. empty values will be left out. you need to log in anew, if you change this setting! addressbook en Address book links displayed in other applications. Empty values will be left out. You need to log in anew, if you change this setting! when viewing a contact, show linked entries from the selected application addressbook en When viewing a contact, show linked entries from the selected application From a35590b8fe305b0facc78cbcc105e26bf1d45048 Mon Sep 17 00:00:00 2001 From: Ralf Becker Date: Thu, 9 Oct 2014 13:52:14 +0000 Subject: [PATCH 22/97] fix replacementes in update messages --- calendar/inc/class.calendar_boupdate.inc.php | 1 + calendar/lang/egw_de.lang | 1 + 2 files changed, 2 insertions(+) diff --git a/calendar/inc/class.calendar_boupdate.inc.php b/calendar/inc/class.calendar_boupdate.inc.php index 4568f5007e..9e4318d4f4 100644 --- a/calendar/inc/class.calendar_boupdate.inc.php +++ b/calendar/inc/class.calendar_boupdate.inc.php @@ -1648,6 +1648,7 @@ class calendar_boupdate extends calendar_bo $event_arr = $this->event2array($event); foreach($event_arr as $key => $val) { + if ($key == 'recur_type') $key = 'repetition'; $details[$key] = $val['data']; } $details['participants'] = $details['participants'] ? implode("\n",$details['participants']) : ''; diff --git a/calendar/lang/egw_de.lang b/calendar/lang/egw_de.lang index ab1abae199..9d7dbcdfcf 100644 --- a/calendar/lang/egw_de.lang +++ b/calendar/lang/egw_de.lang @@ -305,6 +305,7 @@ last changed calendar de letzte Änderung lastname of person to notify calendar de Nachname der zu benachrichtigenden Person length of the time interval calendar de Länge des Zeitintervalls limit number of description lines in list view (default 5, 0 for no limit) calendar de Anzahl Zeilen der Beschreibung in der Listenansicht (voreingestellt 5, 0 für alle) +link calendar de Termin-URL link to view the event calendar de Verweis (Weblink) um den Termin anzuzeigen links calendar de Verknüpfungen links and attached files calendar de Verknüpfungen zu anderen Datensätzen und angefügten Dateien From db6e2c1ed6ecdb74f34eaf1881d2ff6409441ddf Mon Sep 17 00:00:00 2001 From: Ralf Becker Date: Thu, 9 Oct 2014 20:32:59 +0000 Subject: [PATCH 23/97] allow to set CSP connect-src and fix all IDE warnings --- phpgwapi/inc/class.egw_framework.inc.php | 150 ++++++++++++++--------- 1 file changed, 89 insertions(+), 61 deletions(-) diff --git a/phpgwapi/inc/class.egw_framework.inc.php b/phpgwapi/inc/class.egw_framework.inc.php index 566ee0147c..735aa5a44c 100644 --- a/phpgwapi/inc/class.egw_framework.inc.php +++ b/phpgwapi/inc/class.egw_framework.inc.php @@ -157,6 +157,33 @@ abstract class egw_framework return implode(' ', self::$csp_style_src_attrs); } + /** + * Additional attributes or urls for CSP connect-src 'self' + * + * @var array + */ + private static $csp_connect_src_attrs = array(); + + /** + * Set/get Content-Security-Policy attributes for connect-src: + * + * @param string|array $set =array() URL (incl. protocol!) + * @return string with attributes eg. "'unsafe-inline'" + */ + public static function csp_connect_src_attrs($set=null) + { + foreach((array)$set as $attr) + { + if (!in_array($attr, self::$csp_connect_src_attrs)) + { + self::$csp_connect_src_attrs[] = $attr; + //error_log(__METHOD__."() setting CSP script-src $attr ".function_backtrace()); + } + } + //error_log(__METHOD__."(".array2string($set).") returned ".array2string(implode(' ', self::$csp_script_src_attrs)).' '.function_backtrace()); + return implode(' ', self::$csp_connect_src_attrs); + } + /** * Query additional CSP frame-src from current app * @@ -184,7 +211,7 @@ abstract class egw_framework if (($additional = $this->_get_csp_frame_src())) $frame_src = array_unique(array_merge($frame_src, $additional)); $csp = "script-src 'self' ".self::csp_script_src_attrs(). - "; connect-src 'self'". + "; connect-src 'self'".self::csp_connect_src_attrs(). "; style-src 'self' ".self::csp_style_src_attrs(). "; frame-src ".implode(' ', $frame_src); @@ -229,11 +256,12 @@ abstract class egw_framework * * @param string $url The url the link is for * @param string|array $extravars Extra params to be passed to the url - * @param string $link_app=null if appname or true, some templates generate a special link-handler url + * @param string $link_app =null if appname or true, some templates generate a special link-handler url * @return string The full url after processing */ static function link($url, $extravars = '', $link_app=null) { + unset($link_app); // not used by required by function signature return $GLOBALS['egw']->session->link($url, $extravars); } @@ -242,7 +270,7 @@ abstract class egw_framework * * @param string $url The url the link is for * @param string|array $extravars Extra params to be passed to the url - * @param string $link_app=null if appname or true, some templates generate a special link-handler url + * @param string $link_app =null if appname or true, some templates generate a special link-handler url * @return string The full url after processing */ static function redirect_link($url, $extravars='', $link_app=null) @@ -256,8 +284,8 @@ abstract class egw_framework * This is the (new) prefered way to render a page in eGW! * * @param string $content html of the main application area - * @param string $app_header=null application header, default what's set in $GLOBALS['egw_info']['flags']['app_header'] - * @param string $navbar=null show the navigation, default !$GLOBALS['egw_info']['flags']['nonavbar'], false gives a typical popu + * @param string $app_header =null application header, default what's set in $GLOBALS['egw_info']['flags']['app_header'] + * @param string $navbar =null show the navigation, default !$GLOBALS['egw_info']['flags']['nonavbar'], false gives a typical popu * */ function render($content,$app_header=null,$navbar=null) @@ -294,8 +322,8 @@ abstract class egw_framework * * @param string $msg message (already translated) to show, eg. 'Entry deleted' * @param string $app application name - * @param string|int $id=null id of entry to refresh - * @param string $type=null either 'update', 'edit', 'delete', 'add' or null + * @param string|int $id =null id of entry to refresh + * @param string $type =null either 'update', 'edit', 'delete', 'add' or null * - update: request just modified data from given rows. * Sorting and filtering are not considered, so if the sort field is changed, * the row will not be moved. If the current filtering could include or exclude @@ -304,13 +332,14 @@ abstract class egw_framework * - delete: just delete the given rows clientside (no server interaction neccessary) * - add: requires full reload for proper sorting * - null: full reload - * @param string $targetapp=null which app's window should be refreshed, default current - * @param string|RegExp $replace=null regular expression to replace in url - * @param string $with=null - * @param string $msg_type=null 'error', 'warning' or 'success' (default) + * @param string $targetapp =null which app's window should be refreshed, default current + * @param string|RegExp $replace =null regular expression to replace in url + * @param string $with =null + * @param string $msg_type =null 'error', 'warning' or 'success' (default) */ public static function refresh_opener($msg, $app, $id=null, $type=null, $targetapp=null, $replace=null, $with=null, $msg_type=null) { + unset($msg, $app, $id, $type, $targetapp, $replace, $with, $msg_type); // used only via func_get_args(); //error_log(__METHOD__.'('.array2string(func_get_args()).')'); self::$extra['refresh-opener'] = func_get_args(); } @@ -321,10 +350,11 @@ abstract class egw_framework * Calls egw_message on client-side in a content security save way * * @param string $msg message to show - * @param string $type='success' 'error', 'warning' or 'success' (default) + * @param string $type ='success' 'error', 'warning' or 'success' (default) */ public static function message($msg, $type='success') { + unset($msg, $type); // used only via func_get_args(); self::$extra['message'] = func_get_args(); } @@ -337,6 +367,7 @@ abstract class egw_framework */ public static function popup($link, $target='_blank', $popup='640x480') { + unset($link, $target, $popup); // used only via func_get_args() // default params are not returned by func_get_args! $args = func_get_args()+array(null, '_blank', '640x480'); @@ -353,7 +384,7 @@ abstract class egw_framework /** * Close (popup) window, use to replace egw_framework::onload('window.close()') in a content security save way * - * @param string $alert_msg='' optional message to display as alert, before closing the window + * @param string $alert_msg ='' optional message to display as alert, before closing the window */ public static function window_close($alert_msg='') { @@ -463,7 +494,7 @@ abstract class egw_framework * @param string $appname * @param string $menu_title * @param array $file - * @param string $type=null 'admin', 'preferences', 'favorites', ... + * @param string $type =null 'admin', 'preferences', 'favorites', ... */ abstract function sidebox($appname,$menu_title,$file,$type=null); @@ -495,7 +526,7 @@ abstract class egw_framework if($GLOBALS['egw_info']['server']['show_domain_selectbox']) { - foreach($GLOBALS['egw_domain'] as $domain => $data) + foreach(array_keys($GLOBALS['egw_domain']) as $domain) { $domains[$domain] = $domain; } @@ -704,7 +735,7 @@ abstract class egw_framework $GLOBALS['egw_info']['flags']['currentapp'] != 'logout' && !@$GLOBALS['egw_info']['flags']['noappfooter']) { - list($app,$class,$method) = explode('.',(string)$_GET['menuaction']); + list(, $class) = explode('.',(string)$_GET['menuaction']); if ($class && is_object($GLOBALS[$class]) && is_array($GLOBALS[$class]->public_functions) && isset($GLOBALS[$class]->public_functions['footer'])) { @@ -724,7 +755,7 @@ abstract class egw_framework /** * Get header as array to eg. set as vars for a template (from idots' head.inc.php) * - * @param array $extra=array() extra attributes passed as data-attribute to egw.js + * @param array $extra =array() extra attributes passed as data-attribute to egw.js * @return array */ protected function _get_header(array $extra=array()) @@ -831,12 +862,6 @@ abstract class egw_framework $api_messages = lang('it has been more then %1 days since you changed your password',$GLOBALS['egw_info']['server']['change_pwd_every_x_days']); } - // This is gonna change - if(isset($cd)) - { - $var['messages'] = $api_messages . '
' . checkcode($cd); - } - if (substr($GLOBALS['egw_info']['server']['login_logo_file'],0,4) == 'http' || $GLOBALS['egw_info']['server']['login_logo_file'][0] == '/') { @@ -1113,7 +1138,7 @@ abstract class egw_framework * * This is similar to the former common::navbar() method - though it returns the vars and does not place them in global scope. * - * @param boolean $svg=false should svg images be returned or not: + * @param boolean $svg =false should svg images be returned or not: * true: always return svg, false: never return svg (current default), null: browser dependent, see svg_usable() * @return array */ @@ -1285,7 +1310,7 @@ if ($app == 'home') continue; $base_path = $GLOBALS['egw_info']['server']['webserver_url']; if ($base_path[0] != '/') $base_path = parse_url($base_path, PHP_URL_PATH); $css_files = ''; - foreach(self::$css_include_files as $n => $path) + foreach(self::$css_include_files as $path) { foreach(self::resolve_css_includes($path) as $path) { @@ -1324,6 +1349,7 @@ if ($app == 'home') continue; */ protected static function resolve_css_includes($path, &$pathes=array()) { + $matches = null; if (($to_check = file_get_contents (EGW_SERVER_ROOT.$path, false, null, -1, 1024)) && stripos($to_check, '/*@import') !== false && preg_match_all('|/\*@import url\("([^"]+)"|i', $to_check, $matches)) { @@ -1354,7 +1380,7 @@ if ($app == 'home') continue; * in eGW. One change then all templates will support it (as long as they * include a call to this method). * - * @param array $extra=array() extra data to pass to egw.js as data-parameter + * @param array $extra =array() extra data to pass to egw.js as data-parameter * @return string the javascript to be included */ public static function _get_js(array $extra=array()) @@ -1407,7 +1433,7 @@ if ($app == 'home') continue; if(@isset($_GET['menuaction'])) { - list($app,$class,$method) = explode('.',$_GET['menuaction']); + list(, $class) = explode('.',$_GET['menuaction']); if(is_array($GLOBALS[$class]->public_functions) && $GLOBALS[$class]->public_functions['java_script']) { @@ -1432,7 +1458,7 @@ if ($app == 'home') continue; * * Themes are css file in the template directory * - * @param string $themes_dir='css' + * @param string $themes_dir ='css' */ function list_themes() { @@ -1455,7 +1481,7 @@ if ($app == 'home') continue; /** * List available templates * - * @param boolean $full_data=false true: value is array with values for keys 'name', 'title', ... + * @param boolean $full_data =false true: value is array with values for keys 'name', 'title', ... * @returns array alphabetically sorted list of templates */ static function list_templates($full_data=false) @@ -1484,8 +1510,8 @@ if ($app == 'home') continue; } $d->close(); // templates packaged like apps in own directories (containing as setup/setup.inc.php file!) - $d = dir(EGW_SERVER_ROOT); - while (($entry=$d->read())) + $dr = dir(EGW_SERVER_ROOT); + while (($entry=$dr->read())) { if ($entry != '..' && !isset($GLOBALS['egw_info']['apps'][$entry]) && is_dir(EGW_SERVER_ROOT.'/'.$entry) && file_exists($f = EGW_SERVER_ROOT . '/' . $entry .'/setup/setup.inc.php')) @@ -1498,7 +1524,7 @@ if ($app == 'home') continue; } } } - $d->close(); + $dr->close(); return array_filter($list); } @@ -1569,7 +1595,7 @@ if ($app == 'home') continue; */ protected function add_preferences_topmenu($type='prefs') { - static $memberships; + static $memberships=null; if (!isset($memberships)) $memberships = $GLOBALS['egw']->accounts->memberships($GLOBALS['egw_info']['user']['account_id'], true); static $types = array( 'prefs' => array( @@ -1628,7 +1654,7 @@ if ($app == 'home') continue; * Add info items to the topmenu template class to be displayed * * @param string $content html of item - * @param string $id=null + * @param string $id =null * @access protected * @return void */ @@ -1706,8 +1732,8 @@ if ($app == 'home') continue; /** * Sets an onLoad action for a page * - * @param string $code='' javascript to be used - * @param boolean $replace=false false: append to existing, true: replace existing tag + * @param string $code ='' javascript to be used + * @param boolean $replace =false false: append to existing, true: replace existing tag * @return string content of onXXX tag after adding code */ static function set_onload($code='',$replace=false) @@ -1726,8 +1752,8 @@ if ($app == 'home') continue; /** * Sets an onUnload action for a page * - * @param string $code='' javascript to be used - * @param boolean $replace=false false: append to existing, true: replace existing tag + * @param string $code ='' javascript to be used + * @param boolean $replace =false false: append to existing, true: replace existing tag * @return string content of onXXX tag after adding code */ static function set_onunload($code='',$replace=false) @@ -1746,8 +1772,8 @@ if ($app == 'home') continue; /** * Sets an onBeforeUnload action for a page * - * @param string $code='' javascript to be used - * @param boolean $replace=false false: append to existing, true: replace existing tag + * @param string $code ='' javascript to be used + * @param boolean $replace =false false: append to existing, true: replace existing tag * @return string content of onXXX tag after adding code */ static function set_onbeforeunload($code='',$replace=false) @@ -1766,8 +1792,8 @@ if ($app == 'home') continue; /** * Sets an onResize action for a page * - * @param string $code='' javascript to be used - * @param boolean $replace=false false: append to existing, true: replace existing tag + * @param string $code ='' javascript to be used + * @param boolean $replace =false false: append to existing, true: replace existing tag * @return string content of onXXX tag after adding code */ static function set_onresize($code='',$replace=false) @@ -1821,9 +1847,9 @@ if ($app == 'home') continue; * --> /phpgwapi/inc/calendar-setup.js?lang=de * * @param string $package package or complete path (relative to EGW_SERVER_ROOT) to be included - * @param string|array $file=null file to be included - no ".js" on the end or array with get params - * @param string $app='phpgwapi' application directory to search - default = phpgwapi - * @param boolean $append=true should the file be added + * @param string|array $file =null file to be included - no ".js" on the end or array with get params + * @param string $app ='phpgwapi' application directory to search - default = phpgwapi + * @param boolean $append =true should the file be added * * @discuss The browser specific option loads the file which is in the correct * browser folder. Supported folder are those supported by class.browser.inc.php @@ -1838,8 +1864,8 @@ if ($app == 'home') continue; /** * Set or return all javascript files set via validate_file, optionally clear all files * - * @param array $files=null array with pathes relative to EGW_SERVER_ROOT, eg. /phpgwapi/js/jquery/jquery.js - * @param boolean $clear_files=false true clear files after returning them + * @param array $files =null array with pathes relative to EGW_SERVER_ROOT, eg. /phpgwapi/js/jquery/jquery.js + * @param boolean $clear_files =false true clear files after returning them * @return array with pathes relative to EGW_SERVER_ROOT */ static function js_files(array $files=null, $clear_files=false) @@ -1857,8 +1883,8 @@ if ($app == 'home') continue; * NOTE: This method should only be called by the template class. * The validation is done when the file is added so we don't have to worry now * - * @param boolean $return_pathes=false false: return html script tags, true: return array of file pathes relative to webserver_url - * @param boolean $clear_files=false true clear files after returning them + * @param boolean $return_pathes =false false: return html script tags, true: return array of file pathes relative to webserver_url + * @param boolean $clear_files =false true clear files after returning them * @return string|array see $return_pathes parameter */ static public function get_script_links($return_pathes=false, $clear_files=false) @@ -1896,6 +1922,7 @@ if ($app == 'home') continue; } } $to_include = $included_bundles = array(); + $query = null; foreach($js_includes as $file) { if (!isset($to_include[$file])) @@ -1918,7 +1945,7 @@ if ($app == 'home') continue; } else { - $query = ''; + unset($query); list($path, $query) = explode('?', $file, 2); $mod = filemtime(EGW_SERVER_ROOT.$path); @@ -1937,7 +1964,7 @@ if ($app == 'home') continue; * Generate bundle url(s) for given js files * * @param array $js_includes - * @param int& $max_modified=null on return maximum modification time of bundle + * @param int& $max_modified =null on return maximum modification time of bundle * @return array js-files (can be more then one, if one of given files can not be bundeled) */ protected static function bundle_urls(array $js_includes, &$max_modified=null) @@ -1945,6 +1972,7 @@ if ($app == 'home') continue; $debug_minify = $GLOBALS['egw_info']['server']['debug_minify'] === 'True'; $to_include = $to_minify = array(); $max_modified = 0; + $query = null; foreach($js_includes as $path) { if ($path == '/phpgwapi/js/jsapi/egw.js') continue; // loaded via own tag, and we must not load it twice! @@ -2073,9 +2101,9 @@ if ($app == 'home') continue; * Include a css file, either speicified by it's path (relative to EGW_SERVER_ROOT) or appname and css file name * * @param string $app path (relative to EGW_SERVER_ROOT) or appname (if !is_null($name)) - * @param string $name=null name of css file in $app/templates/{default|$this->template}/$name.css - * @param boolean $append=true true append file, false prepend (add as first) file used eg. for template itself - * @param boolean $no_default_css=false true do NOT load any default css, only what app explicitly includes + * @param string $name =null name of css file in $app/templates/{default|$this->template}/$name.css + * @param boolean $append =true true append file, false prepend (add as first) file used eg. for template itself + * @param boolean $no_default_css =false true do NOT load any default css, only what app explicitly includes * @return boolean false: css file not found, true: file found */ public static function includeCSS($app, $name=null, $append=true, $no_default_css=false) @@ -2129,9 +2157,10 @@ if ($app == 'home') continue; self::includeCSS($app,'app'); // add all css files from egw_framework::includeCSS() + $query = null; foreach(self::$css_include_files as $path) { - $query = ''; + unset($query); list($path,$query) = explode('?',$path,2); $path .= '?'. filemtime(EGW_SERVER_ROOT.$path).($query ? '&'.$query : ''); $response->includeCSS($GLOBALS['egw_info']['server']['webserver_url'].$path); @@ -2141,8 +2170,7 @@ if ($app == 'home') continue; self::validate_file('.', 'app', $app); // add all js files from egw_framework::validate_file() - $files = self::$js_include_mgr->get_included_files(); - $files = self::bundle_js_includes($files); + $files = self::bundle_js_includes(self::$js_include_mgr->get_included_files()); foreach($files as $path) { $response->includeScript($GLOBALS['egw_info']['server']['webserver_url'].$path); @@ -2192,7 +2220,7 @@ if ($app == 'home') continue; * Include favorites when generating the page server-side * * @param string $app application, needed to find preferences - * @param string $default=null preference name for default favorite, default "nextmatch-$app.index.rows-favorite" + * @param string $default =null preference name for default favorite, default "nextmatch-$app.index.rows-favorite" * @deprecated use egw_favorites::favorite_list * @return array with a single sidebox menu item (array) containing html for favorites */ @@ -2210,7 +2238,7 @@ if ($app == 'home') continue; * @param string $name Name of the favorite * @param string $action "add" or "delete" * @param boolean|int|string $group ID of the group to create the favorite for, or 'all' for all users - * @param array $filters=array() key => value pairs for the filter + * @param array $filters =array() key => value pairs for the filter * @return boolean Success */ public static function ajax_set_favorite($app, $name, $action, $group, $filters = array()) @@ -2272,9 +2300,9 @@ if (!function_exists('display_sidebox')) * * @deprecated use $GLOBALS['egw']->framework->sidebox() */ - function display_sidebox($appname,$menu_title,$file) + function display_sidebox($appname,$menu_title,$_file) { - $file = str_replace('preferences.uisettings.index', 'preferences.preferences_settings.index', $file); + $file = str_replace('preferences.uisettings.index', 'preferences.preferences_settings.index', $_file); $GLOBALS['egw']->framework->sidebox($appname,$menu_title,$file); } } From d54c88e047bc33373bb304d5a0dc0da72ecf1951 Mon Sep 17 00:00:00 2001 From: Klaus Leithoff Date: Fri, 10 Oct 2014 10:10:36 +0000 Subject: [PATCH 24/97] * Mail: fix for messed up plain-text signature in some cases --- phpgwapi/inc/class.html.inc.php | 17 +++++++++++++++-- phpgwapi/inc/class.translation.inc.php | 8 ++++---- 2 files changed, 19 insertions(+), 6 deletions(-) diff --git a/phpgwapi/inc/class.html.inc.php b/phpgwapi/inc/class.html.inc.php index 7d07c59603..035ab9586f 100644 --- a/phpgwapi/inc/class.html.inc.php +++ b/phpgwapi/inc/class.html.inc.php @@ -1557,7 +1557,14 @@ egw_LAB.wait(function() { */ static function splithtmlByPRE($html) { - if (($pos = stripos($html,'
',$pos);
 			$length = $endofpre-$pos+6;
 			$html2ret[] = substr($html,$pos,$length);
-			$pos =  stripos($html,'
 
tags if ($stripcrl === true ) { - if (stripos($_html,'
')!==false)
 			{
 				$contentArr = html::splithtmlByPRE($_html);
 				foreach ($contentArr as $k =>&$elem)
 				{
-					if (stripos($elem,'
')===false)
 					{
 						//$elem = str_replace('@(\r\n)@i',' ',$elem);
 						$elem = str_replace(array("\r\n","\n"),($isHTML?'':' '),$elem);
@@ -1326,12 +1326,12 @@ class translation
 		// reducing double \r\n to single ones, dont mess with pre sections
 		if ($stripcrl === true && $isHTML)
 		{
-			if (stripos($_html,'
')!==false)
 			{
 				$contentArr = html::splithtmlByPRE($_html);
 				foreach ($contentArr as $k =>&$elem)
 				{
-					if (stripos($elem,'
')===false)
 					{
 						//this is supposed to strip out all remaining stuff in tags, this is sometimes taking out whole sections off content
 						if ( $stripalltags ) {

From e1b2df5609f4140226166c5fbdbea50813bfce7e Mon Sep 17 00:00:00 2001
From: Klaus Leithoff 
Date: Fri, 10 Oct 2014 12:11:14 +0000
Subject: [PATCH 25/97] pending translations from our translation server

---
 etemplate/lang/egw_de.lang | 2 ++
 etemplate/lang/egw_en.lang | 2 ++
 2 files changed, 4 insertions(+)

diff --git a/etemplate/lang/egw_de.lang b/etemplate/lang/egw_de.lang
index b4860804ba..d1d8822e67 100644
--- a/etemplate/lang/egw_de.lang
+++ b/etemplate/lang/egw_de.lang
@@ -20,6 +20,8 @@
 '%1' is not a valid integer !!!	etemplate	de	'%1' ist keine gültige Ganzzahl !!!
 '%1' is not allowed ('%2')!	etemplate	de	'%1' ist NICHT erlaubt ('%2')!
 1 minute	etemplate	de	1 Minute
+15 minutes	etemplate	de	15 Minuten
+30 minutes	etemplate	de	30 Minuten
 30 seconds	etemplate	de	30 Sekunden
 5 minutes	etemplate	de	5 Minuten
 a pattern to be searched for	etemplate	de	ein Muster nach dem gesucht werden soll
diff --git a/etemplate/lang/egw_en.lang b/etemplate/lang/egw_en.lang
index a8b84da948..3feecc2677 100644
--- a/etemplate/lang/egw_en.lang
+++ b/etemplate/lang/egw_en.lang
@@ -20,6 +20,8 @@
 '%1' is not a valid integer !!!	etemplate	en	'%1' is not a valid integer!
 '%1' is not allowed ('%2')!	etemplate	en	'%1' is NOT allowed ('%2')!
 1 minute	etemplate	en	1 Minute
+15 minutes	etemplate	en	15 Minutes
+30 minutes	etemplate	en	30 Minutes
 30 seconds	etemplate	en	30 seconds
 5 minutes	etemplate	en	5 Minutes
 a pattern to be searched for	etemplate	en	Pattern to be searched for

From b0e4655effa09bdaf691a55d528855aee1a37235 Mon Sep 17 00:00:00 2001
From: Hadi Nategh 
Date: Fri, 10 Oct 2014 12:40:41 +0000
Subject: [PATCH 26/97] Give widget color dialog unique class in order to
 identify it later for binding click handler to picker span. -Fix the bug,
 color picker opens other colorpickers dialog which are in the same template.

---
 etemplate/js/et2_widget_color.js | 12 ++++++++++--
 1 file changed, 10 insertions(+), 2 deletions(-)

diff --git a/etemplate/js/et2_widget_color.js b/etemplate/js/et2_widget_color.js
index fdfc9814ae..9f815ff4ba 100644
--- a/etemplate/js/et2_widget_color.js
+++ b/etemplate/js/et2_widget_color.js
@@ -138,13 +138,21 @@ var et2_color = et2_inputWidget.extend(
 				resizable: false,
 				width: "auto"
 			});
-
+			jQuery('table.jPicker').each(function(){
+				if (!this.getAttribute('class').match(/jPickerColorIden/))
+				{
+					//Add an identifier to dialog for later on to bind a click handler to it
+					//as jquery dialog has already an unique id, we make a unique class identifier with help of the widget id
+					jQuery(this).addClass('jPickerColorIden-'+self.id);
+					return false;
+				}
+			});
 			// Hide original move bar
 			jQuery('table.jPicker .Move').hide();
 
 			// Trigger dialog opening
 			jQuery('.Image',self.$node.next()).click(function() {
-				jQuery("table.jPicker").dialog("open");
+				jQuery("table.jPickerColorIden-"+self.id).dialog("open");
 			});
 		},500);
 		return true;

From 889c8b88e8ede3d41eaa509ded74b550f8c09ca3 Mon Sep 17 00:00:00 2001
From: Hadi Nategh 
Date: Fri, 10 Oct 2014 15:03:00 +0000
Subject: [PATCH 27/97] Unset action "moveto" from display toolbar actions

---
 mail/inc/class.mail_ui.inc.php | 1 +
 1 file changed, 1 insertion(+)

diff --git a/mail/inc/class.mail_ui.inc.php b/mail/inc/class.mail_ui.inc.php
index 4247ad3aef..e38abe5ff5 100644
--- a/mail/inc/class.mail_ui.inc.php
+++ b/mail/inc/class.mail_ui.inc.php
@@ -2025,6 +2025,7 @@ class mail_ui
 		unset($actionsenabled['mark']['children']['unread']);
 		unset($actionsenabled['mark']['children']['undelete']);
 		unset($actionsenabled['mark']['children']['readall']);
+		unset($actionsenabled['moveto']);
 		unset($actionsenabled['drag_mail']);
 		$actionsenabled['mark']['children']['flagged']=array(
 			'group' => $actionsenabled['mark']['children']['flagged']['group'],

From 8298fdefcf6ca267d9dbd9c38799409df834e376 Mon Sep 17 00:00:00 2001
From: Ralf Becker 
Date: Fri, 10 Oct 2014 19:19:39 +0000
Subject: [PATCH 28/97] copy 14.1 changelog to trunk to satisfy update checker

---
 doc/rpm-build/debian.changes | 9 +++++++++
 1 file changed, 9 insertions(+)

diff --git a/doc/rpm-build/debian.changes b/doc/rpm-build/debian.changes
index 3b1f92396b..2baf3502b1 100644
--- a/doc/rpm-build/debian.changes
+++ b/doc/rpm-build/debian.changes
@@ -1,3 +1,12 @@
+egroupware-epl (14.1.20141010-1) hardy; urgency=low
+
+  * Mail: fix download/saving of mail or attachments lead to redirect loop on next refresh
+  * Mail: allow to enter name+mail eg. "Ralf Becker " in compose, automatic fix unquoted commas in entered mail addresses
+  * all apps: custom fields of type "float" allow to specify maxlength,size,min,max comma-separated in length field
+  * Sambaadmin: fix editing user-data
+
+ -- Ralf Becker   Fri, 10 Oct 2014 19:21:37 +0200
+
 egroupware-epl (14.1.20141007-1) hardy; urgency=low
 
   * Mail: handle (and correct if needed) charset for subject on import of messages

From 026347ba2ff7aeb939ebe473ca1e85ee9afe9221 Mon Sep 17 00:00:00 2001
From: Ralf Becker 
Date: Mon, 13 Oct 2014 09:07:23 +0000
Subject: [PATCH 29/97] disabling immediate direct call to loadingFinished()
 for selected tab seems to have no recognisable impact and some widgets, eg.
 color-picker have problems with calling doLoadingFinished twice

---
 etemplate/js/et2_widget_tabs.js | 10 +++++++---
 1 file changed, 7 insertions(+), 3 deletions(-)

diff --git a/etemplate/js/et2_widget_tabs.js b/etemplate/js/et2_widget_tabs.js
index 407fd8e342..141f752011 100644
--- a/etemplate/js/et2_widget_tabs.js
+++ b/etemplate/js/et2_widget_tabs.js
@@ -236,12 +236,16 @@ var et2_tabbox = et2_valueWidget.extend([et2_IInput],
 
 		// We can do this and not wind up with 2 because child is a template,
 		// which has special handling
-		this._children[0].loadingFinished(promises);
+		// disabling immediate direct call for selected tab seems to have no recognisable impact and
+		// some widgets, eg. color-picker have problems with calling doLoadingFinished twice
+		// (color input gets re-rendered second time after hitting [Apply], if color-picker is in a tab)
+		//this._children[0].loadingFinished(promises);
 
 		// Defer parsing & loading of other tabs until later
 		window.setTimeout(function() {
 			for (var i = 0; i < tabs.tabData.length; i++)
 			{
+				if (i == tabs.selected_index) continue;
 				tabs._loadTab(i,promises);
 			}
 			jQuery.when.apply(jQuery,promises).then(function() {
@@ -343,7 +347,7 @@ var et2_tabbox = et2_valueWidget.extend([et2_IInput],
 
 	/**
 	 * Gets the index of the currently active tab
-	 * 
+	 *
 	 * @returns {number}
 	 */
 	get_active_tab: function() {
@@ -352,7 +356,7 @@ var et2_tabbox = et2_valueWidget.extend([et2_IInput],
 
 	/**
 	 * Sets the currently active tab by index
-	 * 
+	 *
 	 * @param {number} _idx
 	 */
 	setActiveTab: function(_idx) {

From b542033f8db81f1000e0705dd64803d1a7312f74 Mon Sep 17 00:00:00 2001
From: Hadi Nategh 
Date: Mon, 13 Oct 2014 10:06:30 +0000
Subject: [PATCH 30/97] Make sure the class name used as identifier has no
 invalid chars

---
 etemplate/js/et2_widget_color.js | 7 +++++--
 1 file changed, 5 insertions(+), 2 deletions(-)

diff --git a/etemplate/js/et2_widget_color.js b/etemplate/js/et2_widget_color.js
index 9f815ff4ba..5be9c5cc63 100644
--- a/etemplate/js/et2_widget_color.js
+++ b/etemplate/js/et2_widget_color.js
@@ -128,6 +128,9 @@ var et2_color = et2_inputWidget.extend(
 
 		// Make it look better - plugin defers initialization, so we have to also
 		setTimeout(function() {
+			//Regex to exclude invalid charachters from class identifier name, to be able to address the class name with jquery selector later.
+			var regExClassName = /[\[\]']+/g;
+			
 			// Make the buttons look like all the others
 			jQuery("div.jPicker :button").addClass("et2_button et2_button_text");
 
@@ -143,7 +146,7 @@ var et2_color = et2_inputWidget.extend(
 				{
 					//Add an identifier to dialog for later on to bind a click handler to it
 					//as jquery dialog has already an unique id, we make a unique class identifier with help of the widget id
-					jQuery(this).addClass('jPickerColorIden-'+self.id);
+					jQuery(this).addClass('jPickerColorIden-'+self.id.replace(regExClassName, '_'));
 					return false;
 				}
 			});
@@ -152,7 +155,7 @@ var et2_color = et2_inputWidget.extend(
 
 			// Trigger dialog opening
 			jQuery('.Image',self.$node.next()).click(function() {
-				jQuery("table.jPickerColorIden-"+self.id).dialog("open");
+				jQuery("table.jPickerColorIden-"+self.id.replace(regExClassName, '_')).dialog("open");
 			});
 		},500);
 		return true;

From 55e931871ea0167dd9074d2a419b6622b2b71c0d Mon Sep 17 00:00:00 2001
From: Ralf Becker 
Date: Mon, 13 Oct 2014 12:15:30 +0000
Subject: [PATCH 31/97] mtime postfix for WebDAV has to use "?download=", as
 our WebDAV treats everything else literal

---
 .../class.etemplate_widget_template.inc.php   | 24 ++++++++++---------
 1 file changed, 13 insertions(+), 11 deletions(-)

diff --git a/etemplate/inc/class.etemplate_widget_template.inc.php b/etemplate/inc/class.etemplate_widget_template.inc.php
index 8b0e897b60..94d05c3adc 100644
--- a/etemplate/inc/class.etemplate_widget_template.inc.php
+++ b/etemplate/inc/class.etemplate_widget_template.inc.php
@@ -38,17 +38,17 @@ class etemplate_widget_template extends etemplate_widget
 	/**
 	 * Get instance of template specified by name, template(-set) and version
 	 *
-	 * @param string $name
-	 * @param string $template_set=null default try template-set from user and if not found "default"
-	 * @param string $version=''
-	 * @param string $load_via='' use given template to load $name
+	 * @param string $_name
+	 * @param string $template_set =null default try template-set from user and if not found "default"
+	 * @param string $version =''
+	 * @param string $load_via ='' use given template to load $name
 	 * @todo Reading customized templates from database
 	 * @return etemplate_widget_template|boolean false if not found
 	 */
-	public static function instance($name, $template_set=null, $version='', $load_via='')
+	public static function instance($_name, $template_set=null, $version='', $load_via='')
 	{
 		//$start = microtime(true);
-		list($name) = explode('?', $name);	// remove optional cache-buster
+		list($name) = explode('?', $_name);	// remove optional cache-buster
 		if (isset(self::$cache[$name]) || !($path = self::relPath($name, $template_set, $version)))
 		{
 			if ((!$path || self::read($load_via, $template_set)) && isset(self::$cache[$name]))
@@ -119,8 +119,8 @@ class etemplate_widget_template extends etemplate_widget
 	 * Get path/URL relative to EGroupware install of a template of full vfs url
 	 *
 	 * @param string $name
-	 * @param string $template_set=null default try template-set from user and if not found "default"
-	 * @param string $version=''
+	 * @param string $template_set =null default try template-set from user and if not found "default"
+	 * @param string $version =''
 	 * @return string path of template xml file or null if not found
 	 */
 	public static function relPath($name, $template_set=null, $version='')
@@ -188,10 +188,12 @@ class etemplate_widget_template extends etemplate_widget
 			}
 			else
 			{
-				// no mtime postfix, as our WebDAV treats ? literal and not ignore them like Apache for static files!
 				$url = egw_vfs::download_url($path);
 
 				if ($url[0] == '/') $url = egw::link($url);
+
+				// mtime postfix has to use '?download=', as our WebDAV treats everything else literal and not ignore them like Apache for static files!
+				$url .= '?download='.filemtime(egw_vfs::PREFIX.$path);
 			}
 		}
 		//error_log(__METHOD__."('$path') returning $url");
@@ -204,8 +206,8 @@ class etemplate_widget_template extends etemplate_widget
 	 * Reimplemented because templates can have an own namespace specified in attrs[content], NOT id!
 	 *
 	 * @param string $method_name
-	 * @param array $params=array('') parameter(s) first parameter has to be cname, second $expand!
-	 * @param boolean $respect_disabled=false false (default): ignore disabled, true: method is NOT run for disabled widgets AND their children
+	 * @param array $params =array('') parameter(s) first parameter has to be cname, second $expand!
+	 * @param boolean $respect_disabled =false false (default): ignore disabled, true: method is NOT run for disabled widgets AND their children
 	 */
 	public function run($method_name, $params=array(''), $respect_disabled=false)
 	{

From a1e444fdfd2b1b712446bbb8ad63bd264e0fb7ce Mon Sep 17 00:00:00 2001
From: Ralf Becker 
Date: Mon, 13 Oct 2014 12:21:53 +0000
Subject: [PATCH 32/97] fix non-fatal PHP Parse error, when $j was used eg. in
 onload

---
 etemplate/inc/class.boetemplate.inc.php | 4 +++-
 1 file changed, 3 insertions(+), 1 deletion(-)

diff --git a/etemplate/inc/class.boetemplate.inc.php b/etemplate/inc/class.boetemplate.inc.php
index bed766271c..5f617016bf 100644
--- a/etemplate/inc/class.boetemplate.inc.php
+++ b/etemplate/inc/class.boetemplate.inc.php
@@ -225,7 +225,9 @@ class boetemplate extends soetemplate
 					$name = str_replace($matches[3],$value,$name);
 				}
 			}
-			if (eval($code='$name = "'.str_replace(array('\\', '"'), array('\\\\', '\\"'), $name).'";') === false)
+			if (eval($code='$name = "'.str_replace(array('\\', '"'), array('\\\\', '\\"'),
+				// fix non-fatal PHP Parse error, when $j was used eg. in onload
+				str_replace(array('$j.', '$j('), array('jQuery.', 'jQuery('), $name)).'";') === false)
 			{
 					error_log(__METHOD__."(name='$name', c='$c', row=$row, c_='$c_', row_=$row_, ...) line ".__LINE__." ERROR parsing: $code");
 					error_log(function_backtrace());

From bde32aaeebce5fc25f3b42a5f6f9ddc097d7f1fb Mon Sep 17 00:00:00 2001
From: Ralf Becker 
Date: Mon, 13 Oct 2014 12:49:14 +0000
Subject: [PATCH 33/97] add deprecation note to egw_framework::on_* methods, as
 they get stoped by CSP (they work for old apps incl. old eTemplate)

---
 phpgwapi/inc/class.egw_framework.inc.php | 18 ++++++++++++------
 1 file changed, 12 insertions(+), 6 deletions(-)

diff --git a/phpgwapi/inc/class.egw_framework.inc.php b/phpgwapi/inc/class.egw_framework.inc.php
index 735aa5a44c..3f2fedd722 100644
--- a/phpgwapi/inc/class.egw_framework.inc.php
+++ b/phpgwapi/inc/class.egw_framework.inc.php
@@ -1725,6 +1725,7 @@ if ($app == 'home') continue;
 	/**
 	 * Body tags for onLoad, onUnload and onResize
 	 *
+	 * @deprecated since 14.1 use app.js et2_ready method instead to execute code or bind a handler (CSP will stop onXXX attributes!)
 	 * @var array
 	 */
 	protected static $body_tags = array();
@@ -1734,6 +1735,7 @@ if ($app == 'home') continue;
 	 *
 	 * @param string $code ='' javascript to be used
 	 * @param boolean $replace =false false: append to existing, true: replace existing tag
+	 * @deprecated since 14.1 use app.js et2_ready method instead to execute code or bind a handler (CSP will stop onXXX attributes!)
 	 * @return string content of onXXX tag after adding code
 	 */
 	static function set_onload($code='',$replace=false)
@@ -1754,6 +1756,7 @@ if ($app == 'home') continue;
 	 *
 	 * @param string $code ='' javascript to be used
 	 * @param boolean $replace =false false: append to existing, true: replace existing tag
+	 * @deprecated since 14.1 use app.js et2_ready method instead to execute code or bind a handler (CSP will stop onXXX attributes!)
 	 * @return string content of onXXX tag after adding code
 	 */
 	static function set_onunload($code='',$replace=false)
@@ -1774,6 +1777,7 @@ if ($app == 'home') continue;
 	 *
 	 * @param string $code ='' javascript to be used
 	 * @param boolean $replace =false false: append to existing, true: replace existing tag
+	 * @deprecated since 14.1 use app.js et2_ready method instead to execute code or bind a handler (CSP will stop onXXX attributes!)
 	 * @return string content of onXXX tag after adding code
 	 */
 	static function set_onbeforeunload($code='',$replace=false)
@@ -1790,12 +1794,13 @@ if ($app == 'home') continue;
 	}
 
 	/**
-	* Sets an onResize action for a page
-	*
-	* @param string $code ='' javascript to be used
-	* @param boolean $replace =false false: append to existing, true: replace existing tag
-	* @return string content of onXXX tag after adding code
-	*/
+	 * Sets an onResize action for a page
+	 *
+	 * @param string $code ='' javascript to be used
+	 * @param boolean $replace =false false: append to existing, true: replace existing tag
+	 * @deprecated since 14.1 use app.js et2_ready method instead to execute code or bind a handler (CSP will stop onXXX attributes!)
+	 * @return string content of onXXX tag after adding code
+	 */
 	static function set_onresize($code='',$replace=false)
 	{
 		if ($replace || empty(self::$body_tags['onResize']))
@@ -1812,6 +1817,7 @@ if ($app == 'home') continue;
 	/**
 	 * Adds on(Un)Load= attributes to the body tag of a page
 	 *
+	 * @deprecated since 14.1 use app.js et2_ready method instead to execute code or bind a handler (CSP will stop onXXX attributes!)
 	 * @returns string the attributes to be used
 	 */
 	static public function _get_body_attribs()

From 639a1b6c0399dc26df9f6feedc0b9f344a4befbe Mon Sep 17 00:00:00 2001
From: Hadi Nategh 
Date: Mon, 13 Oct 2014 13:33:10 +0000
Subject: [PATCH 34/97] Check contact duplication of addressbooks with presets
 -Fix addressbook contacts added by add contact plus do not get checked for
 duplication -Fix another CSP error

---
 addressbook/inc/class.addressbook_ui.inc.php | 9 ++++++++-
 addressbook/js/app.js                        | 8 ++++++++
 2 files changed, 16 insertions(+), 1 deletion(-)

diff --git a/addressbook/inc/class.addressbook_ui.inc.php b/addressbook/inc/class.addressbook_ui.inc.php
index 9aecc49323..a7eaf49a0a 100644
--- a/addressbook/inc/class.addressbook_ui.inc.php
+++ b/addressbook/inc/class.addressbook_ui.inc.php
@@ -1717,6 +1717,11 @@ window.egw_LAB.wait(function() {
 			{
 				case 'save':
 				case 'apply':
+					if ($content['presets_fields'])
+					{
+						// unset the duplicate_filed after submit because we don't need to warn user for second time about contact duplication
+						unset($content['presets_fields']);
+					}
 					if ($content['delete_photo'])
 					{
 						$content['jpegphoto'] = null;
@@ -1985,7 +1990,9 @@ window.egw_LAB.wait(function() {
 					{
 						if (!empty($content[$field]))
 						{
-							egw_framework::set_onload("app.addressbook.check_value(document.getElementById('exec[$field]'),0);");
+							//Set the presets fields in content in order to be able to use them later in client side for checking duplication only on first time load
+							// after save/apply we unset them
+							$content['presets_fields'][]= $field;
 							break;
 						}
 					}
diff --git a/addressbook/js/app.js b/addressbook/js/app.js
index 131be8034d..4dc5392133 100644
--- a/addressbook/js/app.js
+++ b/addressbook/js/app.js
@@ -66,6 +66,14 @@ app.classes.addressbook = AppJS.extend(
 						window.app.infolog = new window.app.classes.infolog();
 					}
 				}
+				// Call check value if the AB got opened with presets
+				if (window.location.href.match(/&presets\[email\]/g) && content.presets_fields)
+				{
+					for(var i=0;i< content.presets_fields.length;i++)
+					{
+						this.check_value(this.et2.getWidgetById(content.presets_fields),0);
+					}
+				}
 				break;
 		}
 

From 7369a71d49413fb1b0eb76e7b68e230dabc925ce Mon Sep 17 00:00:00 2001
From: Hadi Nategh 
Date: Mon, 13 Oct 2014 14:06:20 +0000
Subject: [PATCH 35/97] Fix a missing space in connect-src line of CPS

---
 phpgwapi/inc/class.egw_framework.inc.php | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/phpgwapi/inc/class.egw_framework.inc.php b/phpgwapi/inc/class.egw_framework.inc.php
index 3f2fedd722..e812ed3a67 100644
--- a/phpgwapi/inc/class.egw_framework.inc.php
+++ b/phpgwapi/inc/class.egw_framework.inc.php
@@ -211,7 +211,7 @@ abstract class egw_framework
 		if (($additional = $this->_get_csp_frame_src())) $frame_src = array_unique(array_merge($frame_src, $additional));
 
 		$csp = "script-src 'self' ".self::csp_script_src_attrs().
-			"; connect-src 'self'".self::csp_connect_src_attrs().
+			"; connect-src 'self' ".self::csp_connect_src_attrs().
 			"; style-src 'self' ".self::csp_style_src_attrs().
 			"; frame-src ".implode(' ', $frame_src);
 

From dd8f1090f5854ca21d7a9cd58f0aaa19b82c3376 Mon Sep 17 00:00:00 2001
From: Ralf Becker 
Date: Mon, 13 Oct 2014 17:23:33 +0000
Subject: [PATCH 36/97] * Filemanager: fixed super-user not able to create
 top-level directory, eg. /test

---
 filemanager/inc/class.filemanager_ui.inc.php |  9 +++++++--
 filemanager/js/app.js                        | 12 ++++++------
 2 files changed, 13 insertions(+), 8 deletions(-)

diff --git a/filemanager/inc/class.filemanager_ui.inc.php b/filemanager/inc/class.filemanager_ui.inc.php
index 667f8ce6cb..98847e67ad 100644
--- a/filemanager/inc/class.filemanager_ui.inc.php
+++ b/filemanager/inc/class.filemanager_ui.inc.php
@@ -625,7 +625,7 @@ class filemanager_ui
 			case 'saveaszip':
 				egw_vfs::download_zip($selected);
 				common::egw_exit();
-			
+
 			default:
 				list($action, $settings) = explode('_', $action, 2);
 				switch($action)
@@ -1130,7 +1130,7 @@ class filemanager_ui
 				unset($readonlys['tabs']['filemanager.file.eacl']);	// --> switch the tab on again
 				foreach($content['eacl'] as &$eacl)
 				{
-					$eacl['path'] = rtrim(parse_url($eacl['path'],PHP_URL_PATH),'/');		
+					$eacl['path'] = rtrim(parse_url($eacl['path'],PHP_URL_PATH),'/');
 					$readonlys['delete['.$eacl['ino'].'-'.$eacl['owner'].']'] = $eacl['ino'] != $content['ino'] ||
 						$eacl['path'] != $content['path'] || !$content['is_owner'];
 				}
@@ -1247,6 +1247,11 @@ class filemanager_ui
 	 */
 	public static function ajax_action($action, $selected, $dir=null, $props=null)
 	{
+		// do we have root rights, need to run here too, as method is static and therefore does NOT run __construct
+		if (egw_session::appsession('is_root','filemanager'))
+		{
+			egw_vfs::$is_root = true;
+		}
 		$response = egw_json_response::get();
 
 		$arr = array(
diff --git a/filemanager/js/app.js b/filemanager/js/app.js
index 51c7eb35c4..b75bbeaaa7 100644
--- a/filemanager/js/app.js
+++ b/filemanager/js/app.js
@@ -406,7 +406,7 @@ app.classes.filemanager = AppJS.extend(
 		{
 			var path = this.get_path();
 			this._do_action('createdir', dir, true);	// true=synchronous request
-			this.change_dir(path+'/'+dir);
+			this.change_dir((path == '/' ? '' : path)+'/'+dir);
 		}
 	},
 
@@ -462,7 +462,7 @@ app.classes.filemanager = AppJS.extend(
 			var data = egw.dataGetUIDdata(_senders[i].id);
 			var url = data ? data.data.download_url : '/webdav.php'+this.id2path(_senders[i].id);
 			if (url[0] == '/') url = egw.link(url);
-			
+
 			var a = document.createElement('a');
 			if(typeof a.download == "undefined")
 			{
@@ -494,7 +494,7 @@ app.classes.filemanager = AppJS.extend(
 	is_multiple_allowed: function(action, selected)
 	{
 		var allowed = typeof document.createElement('a').download != "undefined";
-		
+
 		if(typeof action == "undefined") return allowed;
 
 		return (allowed || selected.length <= 1) && action.not_disableClass.apply(action, arguments);
@@ -780,10 +780,10 @@ app.classes.filemanager = AppJS.extend(
 		event.stopPropagation();
 		return false;
 	},
-	
+
 	/**
 	 * Set Sudo button's label and change its onclick handler according to its action
-	 * 
+	 *
 	 * @param {widget object} _widget sudo buttononly
 	 * @param {string} _action string of action type {login|logout}
 	 */
@@ -798,7 +798,7 @@ app.classes.filemanager = AppJS.extend(
 					widget.set_label('Logout');
 					this.et2._inst.submit(widget);
 					break;
-					
+
 				default:
 					widget.set_label('Superuser');
 					widget.onclick = function(){

From ea47198d3578337c975f7a988a92c5641de661ef Mon Sep 17 00:00:00 2001
From: Hadi Nategh 
Date: Tue, 14 Oct 2014 08:01:52 +0000
Subject: [PATCH 37/97] Fix copy/move of mail to another mail account

---
 mail/js/app.js | 4 ++++
 1 file changed, 4 insertions(+)

diff --git a/mail/js/app.js b/mail/js/app.js
index b8228889c7..da5e56855c 100644
--- a/mail/js/app.js
+++ b/mail/js/app.js
@@ -2864,6 +2864,10 @@ app.classes.mail = AppJS.extend(
 		messages['all'] = _allMessagesChecked;
 		if (messages['all']=='cancel') return false;
 		if (messages['all']) messages['activeFilters'] = this.mail_getActiveFilters(_action);
+		
+		// Make sure a default target folder is set in case of drop target is parent 0 (mail account name) 
+		if (!target.match(/::/g)) target += '::INBOX';
+		
 		var self = this;
 		egw.json('mail.mail_ui.ajax_copyMessages',[target, messages, 'move'], function(){self.unlock_tree();})
 			.sendRequest();

From b3441cb29b3c6a0d1d1b15983ca219acf2474aac Mon Sep 17 00:00:00 2001
From: Klaus Leithoff 
Date: Tue, 14 Oct 2014 08:51:01 +0000
Subject: [PATCH 38/97] * Mail: fix copy/move of mails between accounts by
 making sure source and target are distinctively defined when performing the
 move

---
 mail/inc/class.mail_ui.inc.php | 8 ++++----
 1 file changed, 4 insertions(+), 4 deletions(-)

diff --git a/mail/inc/class.mail_ui.inc.php b/mail/inc/class.mail_ui.inc.php
index e38abe5ff5..c6de002500 100644
--- a/mail/inc/class.mail_ui.inc.php
+++ b/mail/inc/class.mail_ui.inc.php
@@ -4484,8 +4484,8 @@ class mail_ui
 				}
 				try
 				{
-					//error_log(__METHOD__.__LINE__."->".print_r($messageList,true).' folder:'.$folder.' Method:'.$_forceDeleteMethod);
-					$this->mail_bo->moveMessages($targetFolder,$messageList,($_copyOrMove=='copy'?false:true),$folder,false,($targetProfileID!=$sourceProfileID?$targetProfileID:null));
+					//error_log(__METHOD__.__LINE__."->".print_r($messageList,true).' folder:'.$folder.' Method:'.$_forceDeleteMethod.' '.$targetProfileID.'/'.$sourceProfileID);
+					$this->mail_bo->moveMessages($targetFolder,$messageList,($_copyOrMove=='copy'?false:true),$folder,false,$sourceProfileID,($targetProfileID!=$sourceProfileID?$targetProfileID:null));
 				}
 				catch (egw_exception $e)
 				{
@@ -4511,8 +4511,8 @@ class mail_ui
 				}
 				try
 				{
-					//error_log(__METHOD__.__LINE__."->".print_r($messageList,true).' folder:'.$folder.' Method:'.$_forceDeleteMethod);
-					$this->mail_bo->moveMessages($targetFolder,$messageList,($_copyOrMove=='copy'?false:true),$folder,false,($targetProfileID!=$sourceProfileID?$targetProfileID:null));
+					//error_log(__METHOD__.__LINE__."->".print_r($messageList,true).' folder:'.$folder.' Method:'.$_forceDeleteMethod.' '.$targetProfileID.'/'.$sourceProfileID);
+					$this->mail_bo->moveMessages($targetFolder,$messageList,($_copyOrMove=='copy'?false:true),$folder,false,$sourceProfileID,($targetProfileID!=$sourceProfileID?$targetProfileID:null));
 				}
 				catch (egw_exception $e)
 				{

From fa11f01258b97b15adadac1c0d6cb7ac641439d0 Mon Sep 17 00:00:00 2001
From: Ralf Becker 
Date: Tue, 14 Oct 2014 08:52:11 +0000
Subject: [PATCH 39/97] * Admin/LDAP: show LDAP extra attributes shell/homedir,
 if enabled in setup

---
 admin/inc/class.admin_account.inc.php | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/admin/inc/class.admin_account.inc.php b/admin/inc/class.admin_account.inc.php
index 6dc40db30c..cc3482e5c0 100644
--- a/admin/inc/class.admin_account.inc.php
+++ b/admin/inc/class.admin_account.inc.php
@@ -82,7 +82,7 @@ class admin_account
 			}
 			// should we show extra ldap attributes home-directory and login-shell
 			$account['ldap_extra_attributes'] = $GLOBALS['egw_info']['server']['ldap_extra_attributes'] &&
-				get_class($GLOBALS['egw']->accounts) === 'accounts_ldap';
+				get_class($GLOBALS['egw']->accounts->backend) === 'accounts_ldap';
 
 			$readonlys = array();
 

From 83b67069644209b512ca14536cb4e1983f74d457 Mon Sep 17 00:00:00 2001
From: Ralf Becker 
Date: Tue, 14 Oct 2014 15:58:37 +0000
Subject: [PATCH 40/97] * Timesheet: fix (un)setting project for adding,
 editing and save&new timesheets - fixed et2_widget_textbox to update
 options.blur in set_blur(), as it is used in getValue, also updating input -
 fixed et2_widget_linkentry to trigger change event, after reacting to click
 on X in search - fixed timesheet to handle ts_project and pm_id in bo
 (ts_project is always stored in db for searching, even if it contains no
 custom project name) - fixed not working change of project in an existing
 timesheet - fixed unsetting of project

---
 etemplate/js/et2_widget_link.js          |  9 ++-
 etemplate/js/et2_widget_textbox.js       |  2 +
 timesheet/inc/class.timesheet_bo.inc.php | 96 +++++++++++++++++++++---
 timesheet/inc/class.timesheet_ui.inc.php | 46 +++++-------
 timesheet/js/app.js                      | 16 ++++
 timesheet/templates/default/edit.xet     |  2 +-
 6 files changed, 129 insertions(+), 42 deletions(-)

diff --git a/etemplate/js/et2_widget_link.js b/etemplate/js/et2_widget_link.js
index 134e1a85b3..ca35e7c01a 100644
--- a/etemplate/js/et2_widget_link.js
+++ b/etemplate/js/et2_widget_link.js
@@ -545,6 +545,7 @@ var et2_link_entry = et2_inputWidget.extend(
 		this._super.apply(this, arguments);
 
 		this.search = null;
+		this.clear = null;
 		this.app_select = null;
 		this._oldValue = {
 			id: null,
@@ -570,6 +571,7 @@ var et2_link_entry = et2_inputWidget.extend(
 			this.search.autocomplete("destroy");
 		}
 		this.search = null;
+		this.clear = null;
 		this.app_select = null;
 		this.request = null;
 	},
@@ -691,6 +693,7 @@ var et2_link_entry = et2_inputWidget.extend(
 		this.clear = $j(document.createElement("span"))
 			.addClass("ui-icon ui-icon-close")
 			.click(function(e){
+				if (!self.search) return;	// only gives an error, we should never get into that situation
 				// No way to tell if the results is open, so if they click the button while open, it clears
 				if(self.last_search && self.last_search != self.search.val())
 				{
@@ -705,7 +708,11 @@ var et2_link_entry = et2_inputWidget.extend(
 					self.search.autocomplete("close");
 					self.set_value(null);
 					self.search.val("");
-					self.search.trigger("change");
+					// call trigger, after finishing this handler, not in the middle of it
+					window.setTimeout(function()
+					{
+						self.search.trigger("change");
+					}, 0);
 				}
 				self.search.focus();
 			})
diff --git a/etemplate/js/et2_widget_textbox.js b/etemplate/js/et2_widget_textbox.js
index 5d2cbd27a1..2cb0509f26 100644
--- a/etemplate/js/et2_widget_textbox.js
+++ b/etemplate/js/et2_widget_textbox.js
@@ -217,8 +217,10 @@ var et2_textbox = et2_inputWidget.extend(
 				});
 			}
 		} else {
+			if (!this.getValue()) this.input.val('');
 			this.input.removeAttr("placeholder");
 		}
+		this.options.blur = _value;
 	}
 });
 et2_register_widget(et2_textbox, ["textbox", "passwd"]);
diff --git a/timesheet/inc/class.timesheet_bo.inc.php b/timesheet/inc/class.timesheet_bo.inc.php
index 6b19a58bea..85d1787de1 100644
--- a/timesheet/inc/class.timesheet_bo.inc.php
+++ b/timesheet/inc/class.timesheet_bo.inc.php
@@ -152,6 +152,13 @@ class timesheet_bo extends so_sql_cf
 	*/
 	var $columns_to_search = array('egw_timesheet.ts_id', 'ts_project', 'ts_title', 'ts_description', 'ts_duration', 'ts_quantity', 'ts_unitprice');
 
+	/**
+	 * all cols in data which are not (direct)in the db, for data_merge
+	 *
+	 * @var array
+	 */
+	var $non_db_cols = array('pm_id');
+
 	function __construct()
 	{
 		parent::__construct(TIMESHEET_APP,'egw_timesheet',self::EXTRA_TABLE,'','ts_extra_name','ts_extra_value','ts_id');
@@ -627,19 +634,21 @@ class timesheet_bo extends so_sql_cf
 			$this->user = $this->data['ts_modifier'];
 		}
 
-		// check if we have a real modification
-		// read the old record
-		$new =& $this->data;
-		unset($this->data);
-		$this->read($new['ts_id']);
-		$old =& $this->data;
-		$this->data =& $new;
-		$changed[] = array();
-		if (isset($old)) foreach($old as $name => $value)
+		// check if we have a real modification of an existing record
+		if ($this->data['ts_id'])
 		{
-			if (isset($new[$name]) && $new[$name] != $value) $changed[] = $name;
+			$new =& $this->data;
+			unset($this->data);
+			$this->read($new['ts_id']);
+			$old =& $this->data;
+			$this->data =& $new;
+			$changed = array();
+			if (isset($old)) foreach($old as $name => $value)
+			{
+				if (isset($new[$name]) && $new[$name] != $value) $changed[] = $name;
+			}
 		}
-		if (!$changed)
+		if (isset($old) && !$changed)
 		{
 			return false;
 		}
@@ -666,7 +675,6 @@ class timesheet_bo extends so_sql_cf
 			egw_link::notify_update(TIMESHEET_APP,$this->data['ts_id'],$this->data);
 		}
 
-
 		return $err;
 	}
 
@@ -970,4 +978,68 @@ class timesheet_bo extends so_sql_cf
 		}
 		if ($backup) $this->data = $backup;
 	}
+
+
+	/**
+	 * changes the data from the db-format to your work-format
+	 *
+	 * Reimplemented to store just ts_project in db, but have pm_id and ts_project in memory,
+	 * with ts_project only set, if it contains a custom project name.
+	 *
+	 * @param array $data =null if given works on that array and returns result, else works on internal data-array
+	 * @return array
+	 */
+	function db2data($data=null)
+	{
+		if (($intern = !is_array($data)))
+		{
+			$data =& $this->data;
+		}
+		// get pm_id from links and ts_project: either project matching ts_project or first found project
+		if (!isset($data['pm_id']) && $data['ts_id'])
+		{
+			$first_pm_id = null;
+			foreach(egw_link::get_links('timesheet', $data['ts_id'], 'projectmanager') as $pm_id)
+			{
+				if (!isset($first_pm_id)) $first_pm_id = $pm_id;
+				if ($data['ts_project'] == egw_link::title('projectmanager', $pm_id))
+				{
+					$data['pm_id'] = $pm_id;
+					$data['ts_project_blur'] = $data['ts_project'];
+					$data['ts_project'] = '';
+					break;
+				}
+			}
+			if (!isset($data['pm_id']) && isset($first_pm_id)) $data['pm_id'] = $first_pm_id;
+		}
+		elseif ($data['ts_id'] && $data['pm_id'] && egw_link::title('projectmanager', $data['pm_id']) == $data['ts_project'])
+		{
+			$data['ts_project_blur'] = $data['ts_project'];
+			$data['ts_project'] = '';
+		}
+		return parent::db2data($intern ? null : $data);	// important to use null, if $intern!
+	}
+
+	/**
+	 * changes the data from your work-format to the db-format
+	 *
+	 * Reimplemented to store just ts_project in db, but have pm_id and ts_project in memory,
+	 * with ts_project only set, if it contains a custom project name.
+	 *
+	 * @param array $data =null if given works on that array and returns result, else works on internal data-array
+	 * @return array
+	 */
+	function data2db($data=null)
+	{
+		if (($intern = !is_array($data)))
+		{
+			$data =& $this->data;
+		}
+		// allways store ts_project to be able to search for it, even if no custom project is set
+		if (empty($data['ts_project']))
+		{
+			$data['ts_project'] = $data['pm_id'] ? egw_link::title('projectmanager', $data['pm_id']) : '';
+		}
+		return parent::data2db($intern ? null : $data);	// important to use null, if $intern!
+	}
 }
diff --git a/timesheet/inc/class.timesheet_ui.inc.php b/timesheet/inc/class.timesheet_ui.inc.php
index 5117c7a4b6..0b97a0be04 100644
--- a/timesheet/inc/class.timesheet_ui.inc.php
+++ b/timesheet/inc/class.timesheet_ui.inc.php
@@ -91,6 +91,7 @@ class timesheet_ui extends timesheet_bo
 				$only_admin_edit = true;
 				$msg = lang('only Admin can edit this status');
 			}
+			$this->data['ts_project_blur'] = $this->data['pm_id'] ? egw_link::title('projectmanager', $this->data['pm_id']) : '';
 		}
 		else
 		{
@@ -144,19 +145,12 @@ class timesheet_ui extends timesheet_bo
 			list($button) = @each($content['button']);
 			$view = $content['view'];
 			$referer = $content['referer'];
+			$content['ts_project_blur'] = $content['pm_id'] ? egw_link::title('projectmanager', $content['pm_id']) : '';
 			$this->data = $content;
 			foreach(array('button','view','referer','tabs','start_time') as $key)
 			{
 				unset($this->data[$key]);
 			}
-			// user switched project to none --> remove project and blur
-			if ($this->data['ts_project_blur'] && !$this->data['pm_id'] && $this->data['old_pm_id'])
-			{
-				unset($this->data['ts_project_blur']);
-				unset($content['ts_project_blur']);
-				unset($this->data['ts_project']);
-				unset($content['ts_project']);
-			}
 			switch($button)
 			{
 				case 'edit':
@@ -186,8 +180,6 @@ class timesheet_ui extends timesheet_bo
 					{
 						$etpl->set_validation_error('start_time',lang('Starttime has to be before endtime !!!'));
 					}
-					// only store project-blur, if a project is selected
-					if (!$this->data['ts_project'] && $this->data['pm_id']) $this->data['ts_project'] = $this->data['ts_project_blur'];
 					// set ts_title to ts_project if short viewtype (title is not editable)
 					if($this->ts_viewtype == 'short')
 					{
@@ -213,6 +205,20 @@ class timesheet_ui extends timesheet_bo
 					}
 					if ($etpl->validation_errors()) break;	// the user need to fix the error, before we can save the entry
 
+					// account for changed project --> remove old one from links and add new one
+					if ((int) $this->data['pm_id'] != (int) $this->data['old_pm_id'])
+					{
+						// update links accordingly
+						if ($this->data['pm_id'])
+						{
+							egw_link::link(TIMESHEET_APP,$content['link_to']['to_id'],'projectmanager',$this->data['pm_id']);
+						}
+						if ($this->data['old_pm_id'])
+						{
+							egw_link::unlink2(0,TIMESHEET_APP,$content['link_to']['to_id'],0,'projectmanager',$this->data['old_pm_id']);
+							unset($this->data['old_pm_id']);
+						}
+					}
 					// check if we are linked to a project, but that is NOT set as project
 					if (!$this->data['pm_id'] && is_array($content['link_to']['to_id']))
 					{
@@ -221,6 +227,7 @@ class timesheet_ui extends timesheet_bo
 							if ($data['app'] == 'projectmanager')
 							{
 								$this->data['pm_id'] = $data['id'];
+								$this->data['ts_project_blur'] = egw_link::title('projectmanager', $data['id']);
 								break;
 							}
 						}
@@ -234,19 +241,6 @@ class timesheet_ui extends timesheet_bo
 					else
 					{
 						$msg = lang('Entry saved');
-						if ((int) $this->data['pm_id'] != (int) $this->data['old_pm_id'])
-						{
-							// update links accordingly
-							if ($this->data['pm_id'])
-							{
-								egw_link::link(TIMESHEET_APP,$content['link_to']['to_id'],'projectmanager',$this->data['pm_id']);
-							}
-							if ($this->data['old_pm_id'])
-							{
-								egw_link::unlink2(0,TIMESHEET_APP,$content['link_to']['to_id'],0,'projectmanager',$this->data['old_pm_id']);
-								unset($this->data['old_pm_id']);
-							}
-						}
 						if (is_array($content['link_to']['to_id']) && count($content['link_to']['to_id']))
 						{
 							egw_link::link(TIMESHEET_APP,$this->data['ts_id'],$content['link_to']['to_id']);
@@ -371,10 +365,6 @@ class timesheet_ui extends timesheet_bo
 		{
 			$content['pm_id'] = $preserv['old_pm_id'];
 		}
-		if ($content['pm_id'])
-		{
-			$preserv['ts_project_blur'] = $content['ts_project_blur'] = egw_link::title('projectmanager',$content['pm_id']);
-		}
 		if ($this->pm_integration == 'full')
 		{
 			$preserv['ts_project'] = $preserv['ts_project_blur'];
@@ -392,7 +382,7 @@ class timesheet_ui extends timesheet_bo
 
 		// the actual title-blur is either the preserved title blur (if we are called from infolog entry),
 		// or the preserved project-blur comming from the current selected project
-		$content['ts_title_blur'] = $preserv['ts_title_blur'] ? $preserv['ts_title_blur'] : $preserv['ts_project_blur'];
+		$content['ts_title_blur'] = $preserv['ts_title_blur'] ? $preserv['ts_title_blur'] : $content['ts_project_blur'];
 		$readonlys = array(
 			'button[delete]'   => !$this->data['ts_id'] || !$this->check_acl(EGW_ACL_DELETE) || $this->data['ts_status'] == self::DELETED_STATUS,
 			'button[undelete]' => $this->data['ts_status'] != self::DELETED_STATUS,
diff --git a/timesheet/js/app.js b/timesheet/js/app.js
index 5292738419..2e6df6030c 100644
--- a/timesheet/js/app.js
+++ b/timesheet/js/app.js
@@ -96,4 +96,20 @@ app.classes.timesheet = AppJS.extend(
 			egw.css(".et2_label.ts_description","display:" + (filter2.getValue() == '1' ? "block;" : "none;"));
 		}
 	},
+
+	/**
+	 * Change handler for project selection to set empty ts_project string, if project get deleted
+	 *
+	 * @param {type} _egw
+	 * @param {et2_widget_link_entry} _widget
+	 * @returns {undefined}
+	 */
+	pm_id_changed: function(_egw, _widget)
+	{
+		var ts_project = this.et2.getWidgetById('ts_project');
+		if (ts_project)
+		{
+			ts_project.set_blur(_widget.getValue() ? _widget.search.val() : '');
+		}
+	}
 });
diff --git a/timesheet/templates/default/edit.xet b/timesheet/templates/default/edit.xet
index 73e54fbb20..5d0a4f0b2a 100644
--- a/timesheet/templates/default/edit.xet
+++ b/timesheet/templates/default/edit.xet
@@ -16,7 +16,7 @@
 				
 					
 					
-						
+						
 					
 					
 					

From 08e1ce3c9ee918a8bee31f8fd024b3bfdc11b742 Mon Sep 17 00:00:00 2001
From: Nathan Gray 
Date: Tue, 14 Oct 2014 16:03:50 +0000
Subject: [PATCH 41/97] Keep custom translations as highest precidence. Fixes
 loading translations for another app loses custom translations

---
 phpgwapi/inc/class.translation.inc.php | 9 +++++++++
 1 file changed, 9 insertions(+)

diff --git a/phpgwapi/inc/class.translation.inc.php b/phpgwapi/inc/class.translation.inc.php
index 6b293b3bf3..f572979897 100644
--- a/phpgwapi/inc/class.translation.inc.php
+++ b/phpgwapi/inc/class.translation.inc.php
@@ -339,6 +339,15 @@ class translation
 				self::$loaded_apps[$app] = $l;	// dont set something not existing to $loaded_apps, no need to load client-side
 			}
 		}
+		// Re-merge custom over instance level, they have higher precidence
+		if($tree_level && !$instance_level && self::$instance_specific_translations)
+		{
+			$custom = egw_cache::getInstance(__CLASS__, 'custom:en');
+			if($custom)
+			{
+				self::$lang_arr = array_merge(self::$lang_arr, $custom);
+			}
+		}
 		//error_log(__METHOD__.'('.array2string($apps).", '$lang') took ".(1000*(microtime(true)-$start))." ms, loaded_apps=".array2string(self::$loaded_apps).", loaded ".count($loaded)." phrases -> total=".count(self::$lang_arr));//.": ".function_backtrace());
 	}
 

From 6097c39ea5cf5c42e67a64b5a4dcd16ec46b2364 Mon Sep 17 00:00:00 2001
From: Nathan Gray 
Date: Tue, 14 Oct 2014 16:11:56 +0000
Subject: [PATCH 42/97] Silence debug

---
 importexport/inc/class.importexport_definitions_ui.inc.php | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/importexport/inc/class.importexport_definitions_ui.inc.php b/importexport/inc/class.importexport_definitions_ui.inc.php
index a9af27a3e1..0a42f7f087 100644
--- a/importexport/inc/class.importexport_definitions_ui.inc.php
+++ b/importexport/inc/class.importexport_definitions_ui.inc.php
@@ -512,7 +512,7 @@ class importexport_definitions_ui
 		if(is_array($content) &&! $content['edit'])
 		{
 			if(self::_debug) error_log('importexport.wizard->$content '. print_r($content,true));
-			foreach($content as $key => $val) error_log(" $key : ".array2string($val));
+			//foreach($content as $key => $val) error_log(" $key : ".array2string($val));
 			// fetch plugin object
 			if($content['plugin'] && $content['application'])
 			{

From f099800b624e1ebe2793db52bce2470323b556af Mon Sep 17 00:00:00 2001
From: Nathan Gray 
Date: Tue, 14 Oct 2014 16:43:47 +0000
Subject: [PATCH 43/97] Prevent empty options being sent when selectbox is in a
 row. Fixes unwanted numeric options in auto-repeat rows.

---
 etemplate/inc/class.etemplate_widget_menupopup.inc.php | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/etemplate/inc/class.etemplate_widget_menupopup.inc.php b/etemplate/inc/class.etemplate_widget_menupopup.inc.php
index 536d7048ae..0e11f30a0d 100644
--- a/etemplate/inc/class.etemplate_widget_menupopup.inc.php
+++ b/etemplate/inc/class.etemplate_widget_menupopup.inc.php
@@ -224,7 +224,7 @@ class etemplate_widget_menupopup extends etemplate_widget
 		}
 
 		// Make sure  s, etc.  are properly encoded when sent, and not double-encoded
-		$options = (self::$request->sel_options[$form_name] ? $form_name : $this->id);
+		$options = (isset(self::$request->sel_options[$form_name]) ? $form_name : $this->id);
 		if(is_array(self::$request->sel_options[$options]))
 		{
 			// Turn on search, if there's a lot of rows (unless explicitly set)

From ce6536504969a258ce94b70876c679d42ba7ae08 Mon Sep 17 00:00:00 2001
From: Nathan Gray 
Date: Tue, 14 Oct 2014 22:58:56 +0000
Subject: [PATCH 44/97] * Support export Gantt chart to PDF and PNG using
 DHMTLX's service

---
 etemplate/gantt_print.php        | 32 +++++++++++++++++
 etemplate/js/et2_widget_gantt.js | 61 ++++++++++++++++++++++++++++++++
 2 files changed, 93 insertions(+)
 create mode 100644 etemplate/gantt_print.php

diff --git a/etemplate/gantt_print.php b/etemplate/gantt_print.php
new file mode 100644
index 0000000000..acf5014d1e
--- /dev/null
+++ b/etemplate/gantt_print.php
@@ -0,0 +1,32 @@
+ array(
+		'currentapp'	=> 'projectmanager',
+		'noheader'		=> True,
+		'nonavbar'		=> True
+));
+include('../header.inc.php');
+
+egw_framework::csp_script_src_attrs(array('https://export.dhtmlx.com/gantt/api.js'));
+egw_framework::csp_connect_src_attrs('http://export.dhtmlx.com');
+
+egw_framework::validate_file('/phpgwapi/js/dhtmlxtree/codebase/dhtmlxcommon.js');
+egw_framework::validate_file('/phpgwapi/js/dhtmlxGantt/codebase/dhtmlxgantt.js');
+
+egw_framework::includeCSS('/phpgwapi/js/dhtmlxGantt/codebase/dhtmlxgantt.css');
+
+echo $GLOBALS['egw']->framework->header();
+?>
+
\ No newline at end of file
diff --git a/etemplate/js/et2_widget_gantt.js b/etemplate/js/et2_widget_gantt.js
index d87da78521..e56c77f704 100644
--- a/etemplate/js/et2_widget_gantt.js
+++ b/etemplate/js/et2_widget_gantt.js
@@ -591,6 +591,67 @@ var et2_gantt = et2_valueWidget.extend([et2_IResizeable,et2_IInput],
 		return level;
 	},
 
+	/**
+	 * Exports the gantt chart to an external service that generates a file.
+	 *
+	 * @param {string} to One of PDF or PNG
+	 * @returns {undefined}
+	 */
+	_export: function(to) {
+		var w = egw.open_link(egw.link('/etemplate/gantt_print.php'),'_blank','400x400');
+		var self = this;
+		jQuery(w).load(function() {
+			w.egw_LAB.wait(function() {
+				w.gantt = jQuery.extend(true,{},self.gantt);
+
+				// Loading it twice breaks export
+				if(typeof w.gantt.exportToPNG == 'undefined')
+				{
+					w.egw_LAB.script("https://export.dhtmlx.com/gantt/api.js");
+				}
+				w.egw_LAB.wait(function() {
+					$j(w.gantt.$container).parent().clone().appendTo(w.document.body);
+					// Custom CSS - just send it all
+					var css = '';
+					$j("link[type='text/css']").each(function() {css += this.outerHTML;});
+
+					var options = {
+						name: (w.gantt.getTask(w.gantt._order[0]).text||'gantt').replace(/ /g,'_')+'.'+to.toLowerCase(),
+						header: css + egw.config('site_title','phpgwapi'),
+						footer: $j('#egw_fw_footer',w.opener).html(),
+						// Doesn't work, export never happens:
+						// callback: function() {w.setTimeout(function() {w.close();}, 5000);}
+					};
+					console.log(options);
+					switch(to)
+					{
+						case 'PNG':
+							w.gantt.exportToPNG(options);
+							break;
+						case 'PDF':
+							w.gantt.exportToPDF(options);
+					}
+					w.setTimeout(function() {w.close();}, 5000);
+				});
+			});
+		});
+	},
+
+
+	/**
+	 * Exports the gantt chart do dhtmlx's external service, and makes a PDF
+	 */
+	exportToPDF: function() {
+		this._export('PDF');
+	},
+
+	/**
+	 * Exports the gantt chart do dhtmlx's external service, and makes a PNG
+	 */
+	exportToPNG: function() {
+		this._export('PNG');
+	},
+
 	/**
 	 * Bind all the internal gantt events for nice widget actions
 	 */

From 628b1369f7a73b6a03e9dde5265471a102476c2d Mon Sep 17 00:00:00 2001
From: Hadi Nategh 
Date: Wed, 15 Oct 2014 12:03:15 +0000
Subject: [PATCH 45/97] Make calendar's tooltip scrollable

---
 calendar/js/app.js | 11 +++++++++++
 1 file changed, 11 insertions(+)

diff --git a/calendar/js/app.js b/calendar/js/app.js
index a0f4fce8e5..87f9544428 100644
--- a/calendar/js/app.js
+++ b/calendar/js/app.js
@@ -442,6 +442,17 @@ app.classes.calendar = AppJS.extend(
 					open: function(event,ui){
 						ui.tooltip.removeClass("ui-tooltip");
 						ui.tooltip.addClass("calendar_uitooltip");
+					},
+					close: function( event, ui )
+					{
+						ui.tooltip.hover(
+							function () {
+								if (this.scrollHeight > this.clientHeight)	jQuery(this).stop(true).fadeTo(100, 1);
+							},
+							function () {
+								jQuery(this).fadeOut("100", function(){	jQuery(this).remove();});
+							}
+						);
 					}
 				});
 				ttp.tooltip("enable");

From 424b4c451c2e2970704f9ab758e3143bc062a505 Mon Sep 17 00:00:00 2001
From: Ralf Becker 
Date: Wed, 15 Oct 2014 14:55:08 +0000
Subject: [PATCH 46/97] fix redirects in a popup and new egw_exception_redirect
 to be used in hooks/callbacks like for addressbook.edit to redirect to a
 different location

---
 addressbook/inc/class.addressbook_ui.inc.php | 10 +++++
 json.php                                     |  5 +++
 phpgwapi/inc/class.egw_exception.inc.php     | 45 +++++++++++++++++---
 phpgwapi/inc/common_functions.inc.php        |  5 +++
 phpgwapi/js/jsapi/egw_json.js                |  6 +++
 5 files changed, 64 insertions(+), 7 deletions(-)

diff --git a/addressbook/inc/class.addressbook_ui.inc.php b/addressbook/inc/class.addressbook_ui.inc.php
index a7eaf49a0a..88f5deb001 100644
--- a/addressbook/inc/class.addressbook_ui.inc.php
+++ b/addressbook/inc/class.addressbook_ui.inc.php
@@ -1791,6 +1791,10 @@ window.egw_LAB.wait(function() {
 									$content['msg'] .= ', '.$success_msg;
 								}
 							}
+							catch(egw_exception_redirect $r)
+							{
+								// catch it to continue execution and rethrow it later
+							}
 							catch (Exception $ex) {
 								$content['msg'] .= ', '.$ex->getMessage();
 								$button = 'apply';	// do not close dialog
@@ -1837,6 +1841,12 @@ window.egw_LAB.wait(function() {
 					}
 					egw_framework::refresh_opener($content['msg'], 'addressbook', $content['id'],  $content['id'] ? 'update' : 'add',
 						null, null, null, $this->error ? 'error' : 'success');
+
+					// re-throw redirect exception, if there's no error
+					if (!$this->error && isset($r))
+					{
+						throw $r;
+					}
 					if ($button == 'save')
 					{
 						egw_framework::window_close();
diff --git a/json.php b/json.php
index 2e04f3f730..28736362dd 100644
--- a/json.php
+++ b/json.php
@@ -35,6 +35,11 @@ function login_redirect(&$anon_account)
  */
 function ajax_exception_handler(Exception $e)
 {
+	// handle redirects without logging
+	if (is_a($e, 'egw_exception_redirect'))
+	{
+		egw::redirect($e->url, $e->app);
+	}
 	// logging all exceptions to the error_log
 	$message = null;
 	if (function_exists('_egw_log_exception'))
diff --git a/phpgwapi/inc/class.egw_exception.inc.php b/phpgwapi/inc/class.egw_exception.inc.php
index 8f156b9add..fb446466d0 100644
--- a/phpgwapi/inc/class.egw_exception.inc.php
+++ b/phpgwapi/inc/class.egw_exception.inc.php
@@ -27,6 +27,10 @@
 class egw_exception extends Exception
 {
 	// nothing fancy yet
+	function __construct($msg=null,$code=100,Exception $previous=null)
+	{
+		return parent::__construct($msg, $code, $previous);
+	}
 }
 
 /**
@@ -38,8 +42,8 @@ class egw_exception_no_permission extends egw_exception
 	/**
 	 * Constructor
 	 *
-	 * @param string $msg=null message, default "Permission denied!"
-	 * @param int $code=100 numerical code, default 100
+	 * @param string $msg =null message, default "Permission denied!"
+	 * @param int $code =100 numerical code, default 100
 	 */
 	function __construct($msg=null,$code=100)
 	{
@@ -106,8 +110,8 @@ class egw_exception_not_found extends egw_exception
 	/**
 	 * Constructor
 	 *
-	 * @param string $msg=null message, default "Entry not found!"
-	 * @param int $code=99 numerical code, default 2
+	 * @param string $msg =null message, default "Entry not found!"
+	 * @param int $code =99 numerical code, default 2
 	 */
 	function __construct($msg=null,$code=2)
 	{
@@ -145,8 +149,8 @@ class egw_exception_db extends egw_exception
 	/**
 	 * Constructor
 	 *
-	 * @param string $msg=null message, default "Database error!"
-	 * @param int $code=100
+	 * @param string $msg =null message, default "Database error!"
+	 * @param int $code =100
 	 */
 	function __construct($msg=null,$code=100)
 	{
@@ -179,4 +183,31 @@ class egw_exception_db_invalid_sql extends egw_exception_db { }
 /**
  * EGroupware not (fully) installed, visit setup
  */
-class egw_exception_db_setup extends egw_exception_db { }
\ No newline at end of file
+class egw_exception_db_setup extends egw_exception_db { }
+
+/**
+ * Allow callbacks to request a redirect
+ *
+ * Can be caught be applications and is otherwise handled by global exception handler.
+ */
+class egw_exception_redirect extends egw_exception
+{
+	public $url;
+	public $app;
+
+	/**
+	 * Constructor
+	 *
+	 * @param string $url
+	 * @param string $app
+	 * @param string $msg
+	 * @param int $code
+	 */
+	function __construct($url,$app=null,$msg=null,$code=301)
+	{
+		$this->url = $url;
+		$this->app = $app;
+
+		parent::__construct($msg, $code);
+	}
+}
diff --git a/phpgwapi/inc/common_functions.inc.php b/phpgwapi/inc/common_functions.inc.php
index d129f793a9..0867fefa32 100755
--- a/phpgwapi/inc/common_functions.inc.php
+++ b/phpgwapi/inc/common_functions.inc.php
@@ -1709,6 +1709,11 @@ function _egw_log_exception(Exception $e,&$headline=null)
  */
 function egw_exception_handler(Exception $e)
 {
+	// handle redirects without logging
+	if (is_a($e, 'egw_exception_redirect'))
+	{
+		egw::redirect($e->url, $e->app);
+	}
 	// logging all exceptions to the error_log (if not cli) and get headline
 	$headline = null;
 	_egw_log_exception($e,$headline);
diff --git a/phpgwapi/js/jsapi/egw_json.js b/phpgwapi/js/jsapi/egw_json.js
index bde4b1ae55..aceae865fc 100644
--- a/phpgwapi/js/jsapi/egw_json.js
+++ b/phpgwapi/js/jsapi/egw_json.js
@@ -406,6 +406,12 @@ egw.extend('json', egw.MODULE_WND_LOCAL, function(_app, _wnd) {
 			{
 				egw_topWindow().location.href = res.data.url;
 			}
+			// json request was originating from a different window --> redirect that one
+			else if(this.DOMContainer && this.DOMContainer.ownerDocument.defaultView != window)
+			{
+				this.DOMContainer.ownerDocument.location.href = res.data.url;
+			}
+			// main window, open url in respective tab
 			else
 			{
 				egw_appWindowOpen(res.data.app, res.data.url);

From 4f59ab7d819e8f478cf0798ad8f55ac0e2a1e2ed Mon Sep 17 00:00:00 2001
From: Ralf Becker 
Date: Wed, 15 Oct 2014 15:49:41 +0000
Subject: [PATCH 47/97] resize import popup to regular compose size

---
 mail/inc/class.mail_ui.inc.php | 4 +++-
 1 file changed, 3 insertions(+), 1 deletion(-)

diff --git a/mail/inc/class.mail_ui.inc.php b/mail/inc/class.mail_ui.inc.php
index c6de002500..60826bb4a6 100644
--- a/mail/inc/class.mail_ui.inc.php
+++ b/mail/inc/class.mail_ui.inc.php
@@ -3112,8 +3112,10 @@ class mail_ui
 			}
 			if (!$importFailed)
 			{
+				list($width, $height) = explode('x', egw_link::get_registry('mail', 'add_popup'));
+				if ($width > 0 && $height > 0) egw_json_response::get()->call('resizeTo', $width, $height);
 				ExecMethod2('mail.mail_ui.displayMessage',$linkData);
-				exit;
+				return;
 			}
 		}
 		if (!is_array($content)) $content = array();

From 5082d8a634c11bd3603baeb99b43a39a11e56a20 Mon Sep 17 00:00:00 2001
From: Nathan Gray 
Date: Wed, 15 Oct 2014 15:52:07 +0000
Subject: [PATCH 48/97] - Better popup resize calculations - Test new popup
 size for addressbook

---
 addressbook/inc/class.addressbook_hooks.inc.php |  4 ++--
 phpgwapi/js/jsapi/egw.js                        | 16 +++++++++++++++-
 2 files changed, 17 insertions(+), 3 deletions(-)

diff --git a/addressbook/inc/class.addressbook_hooks.inc.php b/addressbook/inc/class.addressbook_hooks.inc.php
index 17880b046b..3915d0f1c2 100644
--- a/addressbook/inc/class.addressbook_hooks.inc.php
+++ b/addressbook/inc/class.addressbook_hooks.inc.php
@@ -361,13 +361,13 @@ class addressbook_hooks
 				'menuaction' => 'addressbook.addressbook_ui.edit'
 			),
 			'edit_id' => 'contact_id',
-			'edit_popup'  => '800x525',
+			'edit_popup'  => '859x550',
 			'add' => array(
 				'menuaction' => 'addressbook.addressbook_ui.edit'
 			),
 			'add_app'    => 'link_app',
 			'add_id'     => 'link_id',
-			'add_popup'  => '800x525',
+			'add_popup'  => '859x550',
 			'file_access_user' => true,	// file_access supports 4th parameter $user
 			'file_access'=> 'addressbook.addressbook_bo.file_access',
 			'default_types' => array('n' => array('name' => 'contact', 'options' => array('icon' => 'navbar.png','template' => 'addressbook.edit'))),
diff --git a/phpgwapi/js/jsapi/egw.js b/phpgwapi/js/jsapi/egw.js
index cd944e40a6..543e3ae844 100644
--- a/phpgwapi/js/jsapi/egw.js
+++ b/phpgwapi/js/jsapi/egw.js
@@ -224,7 +224,21 @@
 				{
 					// Resize popup when et2 load is done
 					jQuery(node).one("load",function() {
-						window.resizeTo(jQuery(document).width()+45,jQuery(document).height()+100);
+						var $main_div = $j('#popupMainDiv');
+						var $et2 = $j('.et2_container');
+						var w = {
+							width: egw_getWindowInnerWidth(),
+							height: egw_getWindowInnerHeight()
+						};
+						// Use et2_container for width since #popupMainDiv is full width, but we still need
+						// to take padding/margin into account
+						var delta_width = w.width - ($et2.outerWidth(true) + ($main_div.outerWidth(true) - $main_div.width()));
+						var delta_height = w.height - ($et2.outerHeight(true) + ($main_div.outerHeight(true) - $main_div.height()));
+						debugger;
+						if(delta_width != 0 || delta_height != 0)
+						{
+							window.resizeTo(egw_getWindowOuterWidth() - delta_width,egw_getWindowOuterHeight() - delta_height);
+						}
 					});
 				}
 				var et2 = new etemplate2(node, currentapp+".etemplate_new.ajax_process_content.etemplate");

From 3759e498d93b1675530f3ca101a75b83a2a75ad1 Mon Sep 17 00:00:00 2001
From: Ralf Becker 
Date: Wed, 15 Oct 2014 19:08:26 +0000
Subject: [PATCH 49/97] * Calendar: make custom fields available in table
 plugins for document merge

---
 calendar/inc/class.calendar_merge.inc.php | 8 +++++---
 1 file changed, 5 insertions(+), 3 deletions(-)

diff --git a/calendar/inc/class.calendar_merge.inc.php b/calendar/inc/class.calendar_merge.inc.php
index c2832163a7..b6eb870480 100644
--- a/calendar/inc/class.calendar_merge.inc.php
+++ b/calendar/inc/class.calendar_merge.inc.php
@@ -6,7 +6,7 @@
  * @author Ralf Becker 
  * @author Nathan Gray
  * @package calendar
- * @copyright (c) 2007-9 by Ralf Becker 
+ * @copyright (c) 2007-14 by Ralf Becker 
  * @copyright 2011 Nathan Gray
  * @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License
  * @version $Id$
@@ -271,7 +271,8 @@ class calendar_merge extends bo_merge
 			'offset' => 0,
 			'num_rows' => 20,
 			'order' => 'cal_start',
-			'daywise' => true
+			'daywise' => true,
+			'cfs' => array(),	// read all custom-fields
 		));
 
 		$days = array();
@@ -370,7 +371,8 @@ class calendar_merge extends bo_merge
 			'offset' => 0,
 			'num_rows' => 20,
 			'order' => 'cal_start',
-			'daywise' => true
+			'daywise' => true,
+			'cfs' => array(),	// read all custom-fields
 		));
 
 		$replacements = array();

From bdecd27961a21d141d9867a1475010509d3e98a2 Mon Sep 17 00:00:00 2001
From: Nathan Gray 
Date: Wed, 15 Oct 2014 22:22:49 +0000
Subject: [PATCH 50/97] Show planned times on gantt chart while editing real
 times

---
 etemplate/js/et2_widget_gantt.js           | 25 ++++++++++++++++
 etemplate/templates/default/etemplate2.css | 35 +++++++++++++++++++++-
 2 files changed, 59 insertions(+), 1 deletion(-)

diff --git a/etemplate/js/et2_widget_gantt.js b/etemplate/js/et2_widget_gantt.js
index e56c77f704..dcb51a7701 100644
--- a/etemplate/js/et2_widget_gantt.js
+++ b/etemplate/js/et2_widget_gantt.js
@@ -1011,6 +1011,31 @@ $j(function() {
 		}
 	};
 
+	gantt.templates.leftside_text = function(start, end, task) {
+		var text = '';
+		if(task.planned_start)
+		{
+			if(typeof task.planned_start == 'string') task.planned_start = new Date(task.planned_start);
+			var p_start = gantt.posFromDate(task.planned_start) - gantt.posFromDate(start);
+			text = "
" + + gantt.date.date_to_str(gantt.config.api_date)(task.planned_start) + + "
"; + } + return text; + }; + gantt.templates.rightside_text = function(start, end, task) { + var text = ''; + if(task.planned_end) + { + if(typeof task.planned_end == 'string') task.planned_end = new Date(task.planned_end); + var p_end = gantt.posFromDate(task.planned_end) - gantt.posFromDate(end); + text = "
" + + gantt.date.date_to_str(gantt.config.api_date)(task.planned_end) + + "
"; + } + return text; + }; + // Link styling gantt.templates.link_class = function(link) { var link_class = ''; diff --git a/etemplate/templates/default/etemplate2.css b/etemplate/templates/default/etemplate2.css index 02887b75d1..96f9c86837 100644 --- a/etemplate/templates/default/etemplate2.css +++ b/etemplate/templates/default/etemplate2.css @@ -1510,7 +1510,10 @@ div.ui-toolbar-menulist{ { background-size: 18px; } - +.et2_gantt .gantt_side_content +{ + overflow: visible; +} .et2_gantt .gantt_task_progress { text-align: left; @@ -1544,6 +1547,36 @@ div.ui-toolbar-menulist{ { border-left-color: red; } +.et2_gantt .gantt_task_planned +{ + margin-top: 7px; + position: absolute; + opacity: 0.3; + border-style: outset; + border-width: 2px; + border-top: none; + border-bottom: none; + width: 2px; +} +.et2_gantt .gantt_task_planned:hover span { + display:inline; +} +.et2_gantt .gantt_task_planned span +{ + z-index: 10; + display: none; + background: white; + position: relative; + top: 2em; +} +.et2_gantt .gantt_left .gantt_task_planned +{ + border-right: none; +} +.et2_gantt .gantt_right .gantt_task_planned +{ + border-left: none; +} /** * Do not wrap content of a single widget incl. a label or children of a hbox. From 1d5bcc7194385c1a2640a065c27884230c7eb5e6 Mon Sep 17 00:00:00 2001 From: Nathan Gray Date: Wed, 15 Oct 2014 22:50:03 +0000 Subject: [PATCH 51/97] Set some minimum height to deal with floaters which are outside of popup size calculation's reach --- mail/templates/default/app.css | 3 +++ mail/templates/pixelegg/app.css | 5 +++++ mail/templates/pixelegg/app.less | 2 ++ 3 files changed, 10 insertions(+) diff --git a/mail/templates/default/app.css b/mail/templates/default/app.css index 3e2bff8513..ff31479230 100644 --- a/mail/templates/default/app.css +++ b/mail/templates/default/app.css @@ -526,6 +526,9 @@ div.mail-compose_fileselector { display: none !important; } +#mail-display, #mail-compose { + min-height: 500px; +} .mailDisplayContainer, .mailDisplayAttachments { display: block; display: -moz-inline-stack; diff --git a/mail/templates/pixelegg/app.css b/mail/templates/pixelegg/app.css index b706162eed..0226c8b39c 100755 --- a/mail/templates/pixelegg/app.css +++ b/mail/templates/pixelegg/app.css @@ -525,6 +525,9 @@ div.mail-compose_fileselector { border: 0px !important; display: none !important; } +#mail-display { + min-height: 500px; +} .mailDisplayContainer, .mailDisplayAttachments { display: block; @@ -978,6 +981,7 @@ div#mail-index div#mail-index_mailPreview div#mail-index_mailPreviewHeadersSubje } #mail-compose { width: 870px; + min-height: 500px; padding: 5px; /*// ###############################################################################*/ /*// Mail Header*/ @@ -1474,6 +1478,7 @@ div#popupMainDiv { } #mail-display { width: 870px; + min-height: 500px; padding: 5px; /*.mailDisplayHeaderSection{*/ /*top: 0;*/ diff --git a/mail/templates/pixelegg/app.less b/mail/templates/pixelegg/app.less index 3dfd970e55..e0bfb8a3d5 100755 --- a/mail/templates/pixelegg/app.less +++ b/mail/templates/pixelegg/app.less @@ -332,6 +332,7 @@ div#mail-index{ #mail-compose{ // gesamtbreite width: 870px; + min-height: 500px; padding: 5px; @@ -671,6 +672,7 @@ div#popupMainDiv{ #mail-display{ // gesamtbreite width: 870px; + min-height: 500px; padding: 5px; /*.mailDisplayHeaderSection{*/ From c158a32c7206b85154e91bb4180dd51cbe49565b Mon Sep 17 00:00:00 2001 From: Nathan Gray Date: Wed, 15 Oct 2014 23:02:42 +0000 Subject: [PATCH 52/97] Fix timesheet sum --- infolog/inc/class.infolog_merge.inc.php | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/infolog/inc/class.infolog_merge.inc.php b/infolog/inc/class.infolog_merge.inc.php index a38b67eb46..a837df7a1b 100644 --- a/infolog/inc/class.infolog_merge.inc.php +++ b/infolog/inc/class.infolog_merge.inc.php @@ -135,11 +135,7 @@ class infolog_merge extends bo_merge { $timesheets = array(); $links = egw_link::get_links('infolog',$id,'timesheet'); - foreach($links as $link) - { - $timesheets[] = $link['id']; - } - $sum = ExecMethod('timesheet.timesheet_bo.sum',$timesheets); + $sum = ExecMethod('timesheet.timesheet_bo.sum',$links); $info['$$info_sum_timesheets$$'] = $sum['duration']; } From cb94c506d9bca9944bb1303bac3c97190b2511d8 Mon Sep 17 00:00:00 2001 From: Hadi Nategh Date: Thu, 16 Oct 2014 08:29:10 +0000 Subject: [PATCH 53/97] Fix hide handler of cc, bcc expander in compose --- mail/js/app.js | 31 +++++++++++++++++++++++++------ 1 file changed, 25 insertions(+), 6 deletions(-) diff --git a/mail/js/app.js b/mail/js/app.js index da5e56855c..170c425035 100644 --- a/mail/js/app.js +++ b/mail/js/app.js @@ -3623,20 +3623,39 @@ app.classes.mail = AppJS.extend( /** * Hide Folder, Cc and Bcc rows from the compose popup - * + * -Only fields which have no content should get hidden */ compose_fieldExpander_hide: function () { - var widgets = {cc:{},bcc:{},folder:{}}; + var widgets = { + cc:{ + widget:{}, + jQClass: '.mailComposeJQueryCc' + }, + bcc:{ + widget:{}, + jQClass: '.mailComposeJQueryBcc' + }, + folder:{ + widget:{}, + jQClass: '.mailComposeJQueryFolder' + }}; + for(var widget in widgets) { - widgets[widget] = this.et2.getWidgetById(widget+'_expander'); - if (typeof widgets[widget] != 'undefined') + var expanderBtn = widget + '_expander'; + widgets[widget].widget = this.et2.getWidgetById(widget); + // Add expander button widget to the widgets object + widgets[expanderBtn] = {widget:this.et2.getWidgetById(expanderBtn)}; + + if (typeof widgets[widget].widget != 'undefined' + && typeof widgets[expanderBtn].widget != 'undefined' + && widgets[widget].widget.get_value().length == 0) { - widgets[widget].set_disabled(false); + widgets[expanderBtn].widget.set_disabled(false); + jQuery(widgets[widget].jQClass).hide(); } } - jQuery(".mailComposeJQueryCc,.mailComposeJQueryBcc,.mailComposeJQueryFolder").hide(); }, /** From 004de5c81e48668e402f82df563f3f161936d7aa Mon Sep 17 00:00:00 2001 From: Ralf Becker Date: Thu, 16 Oct 2014 09:29:01 +0000 Subject: [PATCH 54/97] * InfoLog: allow to (re-)set view of entries link to contacts via favorites --- infolog/inc/class.infolog_ui.inc.php | 52 ++++++++++++++++------------ infolog/js/app.js | 39 ++++++++++++++++----- 2 files changed, 59 insertions(+), 32 deletions(-) diff --git a/infolog/inc/class.infolog_ui.inc.php b/infolog/inc/class.infolog_ui.inc.php index de8f438447..94725f428b 100644 --- a/infolog/inc/class.infolog_ui.inc.php +++ b/infolog/inc/class.infolog_ui.inc.php @@ -313,7 +313,7 @@ class infolog_ui } //error_log(__METHOD__."() prefs[listNoSubs]=".array2string($this->prefs['listNoSubs'])." --> parent_id=$parent_id"); unset($query['col_filter']['parent_id']); - if(!$query['action']) + if(!$query['col_filter']['action']) { egw_cache::setSession('infolog', $query['session_for'].'session_data', $query); } @@ -345,9 +345,10 @@ class infolog_ui $links['linked'] = array(); unset($query['col_filter']['linked']); } - if($query['action'] && in_array($query['action'], array_keys($GLOBALS['egw_info']['apps'])) && $query['action_id']) + if($query['col_filter']['action'] && in_array($query['col_filter']['action']['app'], array_keys($GLOBALS['egw_info']['apps']))) { - $link_filters['action'] = array('app'=>$query['action'], 'id' => $query['action_id']); + $link_filters['action'] = $query['col_filter']['action']; + unset($query['col_filter']['action']); $links['action'] = array(); } foreach($link_filters as $key => $link) @@ -408,7 +409,7 @@ class infolog_ui // do we need to read the custom fields, depends on the column is enabled and customfields exist, prefs are filter specific // so we have to check that as well $details = $query['filter2'] == 'all'; - $columnselection_pref = 'nextmatch-'.($query['action'] ? 'infolog.'.$query['action'] : (is_object($query['template']) ? $query['template']->name : 'infolog.index.rows')) + $columnselection_pref = 'nextmatch-'.($link_filters['action'] ? 'infolog.'.$link_filters['action']['app'] : (is_object($query['template']) ? $query['template']->name : 'infolog.index.rows')) .($details ? '-details' : ''); $columselection = $this->prefs[$columnselection_pref]; @@ -454,17 +455,17 @@ class infolog_ui // Don't add parent in if info_id_parent (expanding to show subs) if ($query['action_id'] && !$query['col_filter']['info_id_parent']) { - $parents = $query['action'] == 'sp' && $query['action_id'] ? (array)$query['action_id'] : array(); - if (count($parents) == 1 && is_array($query['action_id'])) + $parents = $link_filters['action'] && $link_filters['action']['app'] == 'sp' ? (array)$link_filters['action']['id'] : array(); + if (count($parents) == 1 && is_array($link_filters['action']['id'])) { - $query['action_id'] = array_shift($query['action_id']); // display single parent as app_header + $link_filters['action']['id'] = array_shift($link_filters['action']['id']); // display single parent as app_header } } $parent_first = count($parents) == 1; $parent_index = 0; // et2 nextmatch listens to total, and only displays that many rows, so add parent in or we'll lose the last row - if($parent_first || $query['action'] == 'sp' && is_array($query['action_id'])) $query['total']++; + if($parent_first || $link_filters['action'] && $link_filters['action']['app'] == 'sp' && is_array($link_filters['action']['id'])) $query['total']++; // Check to see if we need to remove description foreach($infos as $id => $info) @@ -540,14 +541,14 @@ class infolog_ui { $GLOBALS['egw_info']['flags']['app_header'] .= ' - '.lang($this->filters[$query['filter']]); } - if ($query['action'] && ($title = $query['action_title'] || is_array($query['action_id']) ? - $query['action_title'] : egw_link::title($query['action']=='sp'?'infolog':$query['action'],$query['action_id']))) + if ($link_filters['action'] && ($title = $link_filters['action']['title'] || is_array($link_filters['action']['id']) ? + $link_filters['action']['title'] : egw_link::title($link_filters['action']['app'] == 'sp' ? 'infolog' : $link_filters['action']['app'], $link_filters['action']['id']))) { $GLOBALS['egw_info']['flags']['app_header'] .= ': '.$title; } } - if (isset($linked)) $query['col_filter']['linked'] = $linked; // add linked back to the colfilter + if ($link_filters) $query['col_filter'] += $link_filters; // add linked and action back to col_filter return $query['total']; } @@ -943,9 +944,14 @@ class infolog_ui if (is_int($colfk)) unset($values['nm']['col_filter']); } } - $values['action'] = $persist['action'] = $values['nm']['action'] = $action; - $values['action_id'] = $persist['action_id'] = $values['nm']['action_id'] = $action_id; - $values['action_title'] = $persist['action_title'] = $values['nm']['action_title'] = $action_title; + $values['action'] = $persist['action'] = $action; + $values['action_id'] = $persist['action_id'] = $action_id; + $values['action_title'] = $persist['action_title'] = $action_title; + $values['nm']['col_filter']['action'] = $action && $action_id ? array( + 'app' => $action, + 'id' => $action_id, + 'title' => $action_title, + ) : null; $values['duration_format'] = ','.$this->duration_format; $persist['called_as'] = $called_as; $persist['own_referer'] = $own_referer; @@ -1240,7 +1246,7 @@ class infolog_ui /** * Handles actions on multiple infologs * - * @param action + * @param string $_action * @param array $checked contact id's to use if !$use_all * @param boolean $use_all if true use all entries of the current selection (in the session) * @param int &$success number of succeded actions @@ -1251,7 +1257,7 @@ class infolog_ui * @param boolean $skip_notifications true to NOT notify users about changes * @return boolean true if all actions succeded, false otherwise */ - function action($action, $checked, $use_all, &$success, &$failed, &$action_msg, + function action($_action, $checked, $use_all, &$success, &$failed, &$action_msg, array $query, &$msg, $skip_notifications = false) { //echo '

'.__METHOD__."('$action',".array2string($checked).','.(int)$use_all.",...)

\n"; @@ -1273,7 +1279,7 @@ class infolog_ui } // Actions with options in the selectbox - list($action, $settings) = explode('_', $action, 2); + list($action, $settings) = explode('_', $_action, 2); // Actions that can handle a list of IDs switch($action) @@ -1476,14 +1482,14 @@ class infolog_ui * Closes an infolog * * @param int|array $values=0 info_id (default _GET[info_id]) - * @param string $referer='' + * @param string $_referer='' * @param boolean $closesingle=false */ - function close($values=0,$referer='',$closesingle=false,$skip_notification = false) + function close($values=0,$_referer='',$closesingle=false,$skip_notification = false) { //echo "

".__METHOD__."($values,$referer,$closeall)

\n"; $info_id = (int) (is_array($values) ? $values['info_id'] : ($values ? $values : $_GET['info_id'])); - $referer = is_array($values) ? $values['referer'] : $referer; + $referer = is_array($values) ? $values['referer'] : $_referer; if ($info_id) { @@ -1526,14 +1532,14 @@ class infolog_ui * Deletes an InfoLog entry * * @param array|int $values info_id (default _GET[info_id]) - * @param string $referer + * @param string $_referer * @param string $called_by * @param boolean $skip_notification Do not send notification of deletion */ - function delete($values=0,$referer='',$called_by='',$skip_notification=False) + function delete($values=0,$_referer='',$called_by='',$skip_notification=False) { $info_id = (int) (is_array($values) ? $values['info_id'] : ($values ? $values : $_GET['info_id'])); - $referer = is_array($values) ? $values['referer'] : $referer; + $referer = is_array($values) ? $values['referer'] : $_referer; if (!is_array($values) && $info_id > 0 && !$this->bo->anzSubs($info_id)) // entries without subs get confirmed by javascript { diff --git a/infolog/js/app.js b/infolog/js/app.js index 549819aabc..834d4322d6 100644 --- a/infolog/js/app.js +++ b/infolog/js/app.js @@ -90,7 +90,7 @@ app.classes.infolog = AppJS.extend( if (typeof _links != 'undefined') { if (typeof _links.infolog != 'undefined') - { + { switch (_app) { case 'timesheet': @@ -105,19 +105,38 @@ app.classes.infolog = AppJS.extend( { var info_type = egw.dataGetUIDdata(_app+"::"+_id)?egw.dataGetUIDdata(_app+"::"+_id).data.info_type:false; var cal_show = egw.preference('cal_show','infolog')||false; - + if (info_type && cal_show) { var rex = RegExp(info_type,'gi'); if (cal_show.match(rex)) { - //Trigger refresh the whole calendar if the changed infolog entry is integrated one + //Trigger refresh the whole calendar if the changed infolog entry is integrated one if (typeof app['calendar'] != 'undefined') app.calendar.egw.window.location.reload(); - } + } } } }, + /** + * Retrieve the current state of the application for future restoration + * + * Reimplemented to add col_filter.action from content set by server + * when eg. viewing infologs linked to contacts. + * + * @return {object} Application specific map representing the current state + */ + getState: function() + { + // call parent + var state = this._super.apply(this, arguments); + + var filters = this.et2 ? this.et2.getArrayMgr('content').data.nm.col_filter : {}; + state.col_filter.action = filters.action || null; + + return state; + }, + /** * Enable or disable the date filter * @@ -172,7 +191,7 @@ app.classes.infolog = AppJS.extend( // Change preference location - widget is nextmatch nm.options.settings.columnselection_pref = nm.options.settings.columnselection_pref.replace('-details','') + (filter2.value == 'all' ? '-details' :''); - + // Load new preferences var colData = nm.columns.slice(); for(var i = 0; i < nm.columns.length; i++) colData[i].disabled=false; @@ -193,7 +212,8 @@ app.classes.infolog = AppJS.extend( /** * Show or hide details by changing the CSS class * - * @param show + * @param {boolean} show + * @param {DOMNode} dom_node */ show_details: function(show, dom_node) { @@ -403,18 +423,19 @@ app.classes.infolog = AppJS.extend( isLoadingCompleted = false; jQuery('#infolog-edit-print').unbind("DOMSubtreeModified"); }); - setTimeout(function(){isLoadingCompleted = false},1000); + setTimeout(function() { + isLoadingCompleted = false; + }, 1000); var interval = setInterval(function(){ if (!isLoadingCompleted) { clearInterval(interval); that.infolog_print_preview(); } - }, 100); }); }, - + /** * Trigger print() function to print the current window */ From 47bf4d0824e7e637ce837dc31d7ca1e0d7b1afbd Mon Sep 17 00:00:00 2001 From: Ralf Becker Date: Thu, 16 Oct 2014 09:32:13 +0000 Subject: [PATCH 55/97] * Addressbook: with double-click preference set to edit, CRM-view did not open when selected in menu --- addressbook/inc/class.addressbook_hooks.inc.php | 5 ----- addressbook/inc/class.addressbook_ui.inc.php | 2 +- addressbook/js/app.js | 4 +++- 3 files changed, 4 insertions(+), 7 deletions(-) diff --git a/addressbook/inc/class.addressbook_hooks.inc.php b/addressbook/inc/class.addressbook_hooks.inc.php index 3915d0f1c2..ba6c46d960 100644 --- a/addressbook/inc/class.addressbook_hooks.inc.php +++ b/addressbook/inc/class.addressbook_hooks.inc.php @@ -384,11 +384,6 @@ class addressbook_hooks ), 'merge' => true, ); - if($GLOBALS['egw_info']['user']['preferences']['addressbook']['crm_list'] == '~edit~') - { - $links['view'] = $links['edit']; - $links['view_popup'] = $links['edit_popup']; - } return $links; } diff --git a/addressbook/inc/class.addressbook_ui.inc.php b/addressbook/inc/class.addressbook_ui.inc.php index 88f5deb001..4182521266 100644 --- a/addressbook/inc/class.addressbook_ui.inc.php +++ b/addressbook/inc/class.addressbook_ui.inc.php @@ -368,7 +368,7 @@ class addressbook_ui extends addressbook_bo 'default' => $GLOBALS['egw_info']['user']['preferences']['addressbook']['crm_list'] == '~edit~', 'allowOnMultiple' => false, 'url' => 'menuaction=addressbook.addressbook_ui.edit&contact_id=$id', - 'popup' => egw_link::get_registry('addressbook', 'add_popup'), + 'popup' => egw_link::get_registry('addressbook', 'edit_popup'), 'group' => $group, ), 'add' => array( diff --git a/addressbook/js/app.js b/addressbook/js/app.js index 4dc5392133..8cf0529b0e 100644 --- a/addressbook/js/app.js +++ b/addressbook/js/app.js @@ -500,6 +500,8 @@ app.classes.addressbook = AppJS.extend( /** * Apply advanced search filters to index nextmatch + * + * @param {object} filters */ adv_search: function(filters) { @@ -576,7 +578,7 @@ app.classes.addressbook = AppJS.extend( */ addEmail: function(action, selected) { - // Check for all selected. + // Check for all selected. var nm = this.et2.getWidgetById('nm'); if(fetchAll(selected, nm, jQuery.proxy(function(ids) { // fetchAll() returns just the ID, no prefix, so map it to match normal selected From e2b160aedeba0fa20ef278739f8d1e7639ce194b Mon Sep 17 00:00:00 2001 From: Klaus Leithoff Date: Thu, 16 Oct 2014 09:50:16 +0000 Subject: [PATCH 56/97] fix problem with undefined var w_h in javascript --- mail/js/app.js | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/mail/js/app.js b/mail/js/app.js index 170c425035..f573a03dd3 100644 --- a/mail/js/app.js +++ b/mail/js/app.js @@ -2577,6 +2577,10 @@ app.classes.mail = AppJS.extend( */ mail_infolog: function(_action, _elems) { + //define/preset w_h in case something fails + var reg = '750x580'; + var w_h =reg.split('x'); + if (typeof _elems == 'undefined' || _elems.length==0) { if (this.et2.getArrayMgr("content").getEntry('mail_id')) @@ -2604,7 +2608,7 @@ app.classes.mail = AppJS.extend( } if (typeof app_registry['edit'] != 'undefined' && typeof app_registry['edit_popup'] != 'undefined' ) { - var w_h =app_registry['edit_popup'].split('x'); + w_h =app_registry['edit_popup'].split('x'); } } } @@ -2622,6 +2626,9 @@ app.classes.mail = AppJS.extend( */ mail_tracker: function(_action, _elems) { + //define/preset w_h in case something fails + var reg = '780x535'; + var w_h =reg.split('x'); if (typeof _elems == 'undefined' || _elems.length==0) { if (this.et2.getArrayMgr("content").getEntry('mail_id')) @@ -2649,7 +2656,7 @@ app.classes.mail = AppJS.extend( } if (typeof app_registry['add'] != 'undefined' && typeof app_registry['add_popup'] != 'undefined' ) { - var w_h =app_registry['add_popup'].split('x'); + w_h =app_registry['add_popup'].split('x'); } } } From 55b97699b640ad64b2bec216fb5d950fd6fdc0fb Mon Sep 17 00:00:00 2001 From: Hadi Nategh Date: Thu, 16 Oct 2014 14:41:30 +0000 Subject: [PATCH 57/97] Notify user of how to select content of a draggable item --- phpgwapi/js/egw_action/egw_action_dragdrop.js | 25 +++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/phpgwapi/js/egw_action/egw_action_dragdrop.js b/phpgwapi/js/egw_action/egw_action_dragdrop.js index fdff8ef275..c7d1cdfe73 100644 --- a/phpgwapi/js/egw_action/egw_action_dragdrop.js +++ b/phpgwapi/js/egw_action/egw_action_dragdrop.js @@ -235,6 +235,31 @@ function egwDragActionImplementation() "start": function(e) { return ai.helper != null; }, + revert: function(valid) + { + var dTarget = this; + if (!valid) + { + // Tolerance value of pixels arround the draggable target + // to distinguish whether the action was intended for dragging or selecting content. + var tipTelorance = 10; + var helperTop = ai.helper.position().top; + + if (helperTop >= dTarget.offset().top + && helperTop <= (dTarget.height() + dTarget.offset().top) + tipTelorance) + { + var key = ["Mac68K","MacPPC","MacIntel"].indexOf(window.navigator.platform) < 0 ? 'Ctrl' : 'Command'; + egw.message(egw.lang('Hold %1 key to select content.', key),'info'); + } + // Invalid target + return true; + } + else + { + // Valid target + return false; + } + }, // Solves problem with scroll position changing in the grid // component "refreshPositions": true, From 8af4ca6049d66a618f5b90eb9312bd13bf4afa57 Mon Sep 17 00:00:00 2001 From: Ralf Becker Date: Thu, 16 Oct 2014 15:59:10 +0000 Subject: [PATCH 58/97] reverting r49030 and implementing same functionality with just app.infolog.(get|set)State(), fixes introduced error set you could not filter eg. by type in an action view --- infolog/inc/class.infolog_ui.inc.php | 34 ++++++++++++---------------- infolog/js/app.js | 24 +++++++++++++++++--- 2 files changed, 35 insertions(+), 23 deletions(-) diff --git a/infolog/inc/class.infolog_ui.inc.php b/infolog/inc/class.infolog_ui.inc.php index 94725f428b..f0be10f4cf 100644 --- a/infolog/inc/class.infolog_ui.inc.php +++ b/infolog/inc/class.infolog_ui.inc.php @@ -313,7 +313,7 @@ class infolog_ui } //error_log(__METHOD__."() prefs[listNoSubs]=".array2string($this->prefs['listNoSubs'])." --> parent_id=$parent_id"); unset($query['col_filter']['parent_id']); - if(!$query['col_filter']['action']) + if(!$query['action']) { egw_cache::setSession('infolog', $query['session_for'].'session_data', $query); } @@ -345,10 +345,9 @@ class infolog_ui $links['linked'] = array(); unset($query['col_filter']['linked']); } - if($query['col_filter']['action'] && in_array($query['col_filter']['action']['app'], array_keys($GLOBALS['egw_info']['apps']))) + if($query['action'] && in_array($query['action'], array_keys($GLOBALS['egw_info']['apps'])) && $query['action_id']) { - $link_filters['action'] = $query['col_filter']['action']; - unset($query['col_filter']['action']); + $link_filters['action'] = array('app'=>$query['action'], 'id' => $query['action_id']); $links['action'] = array(); } foreach($link_filters as $key => $link) @@ -409,7 +408,7 @@ class infolog_ui // do we need to read the custom fields, depends on the column is enabled and customfields exist, prefs are filter specific // so we have to check that as well $details = $query['filter2'] == 'all'; - $columnselection_pref = 'nextmatch-'.($link_filters['action'] ? 'infolog.'.$link_filters['action']['app'] : (is_object($query['template']) ? $query['template']->name : 'infolog.index.rows')) + $columnselection_pref = 'nextmatch-'.($query['action'] ? 'infolog.'.$query['action'] : (is_object($query['template']) ? $query['template']->name : 'infolog.index.rows')) .($details ? '-details' : ''); $columselection = $this->prefs[$columnselection_pref]; @@ -455,17 +454,17 @@ class infolog_ui // Don't add parent in if info_id_parent (expanding to show subs) if ($query['action_id'] && !$query['col_filter']['info_id_parent']) { - $parents = $link_filters['action'] && $link_filters['action']['app'] == 'sp' ? (array)$link_filters['action']['id'] : array(); - if (count($parents) == 1 && is_array($link_filters['action']['id'])) + $parents = $query['action'] == 'sp' && $query['action_id'] ? (array)$query['action_id'] : array(); + if (count($parents) == 1 && is_array($query['action_id'])) { - $link_filters['action']['id'] = array_shift($link_filters['action']['id']); // display single parent as app_header + $query['action_id'] = array_shift($query['action_id']); // display single parent as app_header } } $parent_first = count($parents) == 1; $parent_index = 0; // et2 nextmatch listens to total, and only displays that many rows, so add parent in or we'll lose the last row - if($parent_first || $link_filters['action'] && $link_filters['action']['app'] == 'sp' && is_array($link_filters['action']['id'])) $query['total']++; + if($parent_first || $query['action'] == 'sp' && is_array($query['action_id'])) $query['total']++; // Check to see if we need to remove description foreach($infos as $id => $info) @@ -541,14 +540,14 @@ class infolog_ui { $GLOBALS['egw_info']['flags']['app_header'] .= ' - '.lang($this->filters[$query['filter']]); } - if ($link_filters['action'] && ($title = $link_filters['action']['title'] || is_array($link_filters['action']['id']) ? - $link_filters['action']['title'] : egw_link::title($link_filters['action']['app'] == 'sp' ? 'infolog' : $link_filters['action']['app'], $link_filters['action']['id']))) + if ($query['action'] && ($title = $query['action_title'] || is_array($query['action_id']) ? + $query['action_title'] : egw_link::title($query['action']=='sp'?'infolog':$query['action'],$query['action_id']))) { $GLOBALS['egw_info']['flags']['app_header'] .= ': '.$title; } } - if ($link_filters) $query['col_filter'] += $link_filters; // add linked and action back to col_filter + if (isset($linked)) $query['col_filter']['linked'] = $linked; // add linked back to the colfilter return $query['total']; } @@ -944,14 +943,9 @@ class infolog_ui if (is_int($colfk)) unset($values['nm']['col_filter']); } } - $values['action'] = $persist['action'] = $action; - $values['action_id'] = $persist['action_id'] = $action_id; - $values['action_title'] = $persist['action_title'] = $action_title; - $values['nm']['col_filter']['action'] = $action && $action_id ? array( - 'app' => $action, - 'id' => $action_id, - 'title' => $action_title, - ) : null; + $values['action'] = $persist['action'] = $values['nm']['action'] = $action; + $values['action_id'] = $persist['action_id'] = $values['nm']['action_id'] = $action_id; + $values['action_title'] = $persist['action_title'] = $values['nm']['action_title'] = $action_title; $values['duration_format'] = ','.$this->duration_format; $persist['called_as'] = $called_as; $persist['own_referer'] = $own_referer; diff --git a/infolog/js/app.js b/infolog/js/app.js index 834d4322d6..5b79d2fcce 100644 --- a/infolog/js/app.js +++ b/infolog/js/app.js @@ -121,7 +121,7 @@ app.classes.infolog = AppJS.extend( /** * Retrieve the current state of the application for future restoration * - * Reimplemented to add col_filter.action from content set by server + * Reimplemented to add action/action_id from content set by server * when eg. viewing infologs linked to contacts. * * @return {object} Application specific map representing the current state @@ -131,12 +131,30 @@ app.classes.infolog = AppJS.extend( // call parent var state = this._super.apply(this, arguments); - var filters = this.et2 ? this.et2.getArrayMgr('content').data.nm.col_filter : {}; - state.col_filter.action = filters.action || null; + var nm = this.et2 ? this.et2.getArrayMgr('content').data.nm : {}; + state.action = nm.action || null; + state.action_id = nm.action_id || null; return state; }, + /** + * Set the application's state to the given state. + * + * Reimplemented to also reset action/action_id. + * + * @param {{name: string, state: object}|string} state Object (or JSON string) for a state. + * Only state is required, and its contents are application specific. + * + * @return {boolean} false - Returns false to stop event propagation + */ + setState: function(state) + { + if (typeof state.state.action == 'undefined') state.state.action = null; + + return this._super.apply(this, arguments); + }, + /** * Enable or disable the date filter * From 9481dab570282f913c688c34c43a03a7db34469d Mon Sep 17 00:00:00 2001 From: Nathan Gray Date: Thu, 16 Oct 2014 22:41:39 +0000 Subject: [PATCH 59/97] Drag & drop support for mail into filemanager --- filemanager/inc/class.filemanager_ui.inc.php | 19 ++++++++++- mail/inc/class.mail_ui.inc.php | 33 +++++++++++++++----- 2 files changed, 43 insertions(+), 9 deletions(-) diff --git a/filemanager/inc/class.filemanager_ui.inc.php b/filemanager/inc/class.filemanager_ui.inc.php index 98847e67ad..26640c907e 100644 --- a/filemanager/inc/class.filemanager_ui.inc.php +++ b/filemanager/inc/class.filemanager_ui.inc.php @@ -183,6 +183,11 @@ class filemanager_ui 'type' => 'drag', 'onExecute' => 'javaScript:app.filemanager.drag' ), + 'file_drop_mail' => array( + 'type' => 'drop', + 'acceptedTypes' => 'mail', + 'onExecute' => 'javaScript:app.filemanager.drop' + ), 'file_drop_move' => array( 'icon' => 'stylite/move', 'acceptedTypes' => 'file', @@ -513,7 +518,19 @@ class filemanager_ui case 'copy': foreach($selected as $path) { - if (!egw_vfs::is_dir($path)) + if (strpos($path, 'mail::') === 0 && $path = substr($path, 6)) + { + // Support for dropping mail in filemanager - Pass mail back to mail app + if(ExecMethod2('mail.mail_ui.vfsSaveMessage', $path, $dir, false)) + { + ++$files; + } + else + { + ++$errs; + } + } + elseif (!egw_vfs::is_dir($path)) { $to = egw_vfs::concat($dir,egw_vfs::basename($path)); if ($path != $to && egw_vfs::copy($path,$to)) diff --git a/mail/inc/class.mail_ui.inc.php b/mail/inc/class.mail_ui.inc.php index 60826bb4a6..24f19b6359 100644 --- a/mail/inc/class.mail_ui.inc.php +++ b/mail/inc/class.mail_ui.inc.php @@ -1391,7 +1391,7 @@ class mail_ui 'toolbarDefault' => true ), 'drag_mail' => array( - 'dragType' => array('mail','file'), + 'dragType' => array('mail'), 'type' => 'drag', 'onExecute' => 'javaScript:app.mail.mail_dragStart', ) @@ -2517,16 +2517,19 @@ class mail_ui * * @param string|array $ids use splitRowID, to separate values * @param string $path path in vfs (no egw_vfs::PREFIX!), only directory for multiple id's ($ids is an array) - * @return string javascript eg. to close the selector window + * @param boolean $close Return javascript to close the window + * @return string|boolean javascript eg. to close the selector window if $close is true, or success/fail if $close is false */ - function vfsSaveMessage($ids,$path) + function vfsSaveMessage($ids,$path, $close = true) { - error_log(__METHOD__.' IDs:'.array2string($ids).' SaveToPath:'.$path); + //error_log(__METHOD__.' IDs:'.array2string($ids).' SaveToPath:'.$path); if (is_array($ids) && !egw_vfs::is_writable($path) || !is_array($ids) && !egw_vfs::is_writable(dirname($path))) { return 'alert("'.addslashes(lang('%1 is NOT writable by you!',$path)).'"); window.close();'; } + translation::add_app('mail'); + foreach((array)$ids as $id) { $hA = self::splitRowID($id); @@ -2534,7 +2537,16 @@ class mail_ui $mailbox = $hA['folder']; $message = $this->mail_bo->getMessageRawBody($uid, $partID='', $mailbox); $err=null; - if (!($fp = egw_vfs::fopen($file=$path,'wb')) || !fwrite($fp,$message)) + if(egw_vfs::is_dir($path)) + { + $headers = $this->mail_bo->getMessageHeader($uid,$partID,true,false,$mailbox); + $file = $path . '/'.preg_replace('/[\f\n\t\v\\:*#?<>\|]/',"_",$headers['SUBJECT']).'.eml'; + } + else + { + $file = $path; + } + if (!($fp = egw_vfs::fopen($file,'wb')) || !fwrite($fp,$message)) { $err .= lang('Error saving %1!',$file); $succeeded = false; @@ -2546,15 +2558,20 @@ class mail_ui if ($fp) fclose($fp); if ($succeeded) { - translation::add_app('mail'); - $headers = $this->mail_bo->getMessageHeader($uid,$partID,true,false,$mailbox); unset($headers['SUBJECT']);//already in filename $infoSection = mail_bo::createHeaderInfoSection($headers, 'SUPPRESS', false); $props = array(array('name' => 'comment','val' => $infoSection)); egw_vfs::proppatch($file,$props); } } - egw_framework::window_close(($err?$err:null)); + if($close) + { + egw_framework::window_close(($err?$err:null)); + } + else + { + return $succeeded; + } } /** From 2778b482733fe4ce9171fba6db068d275e6a31e3 Mon Sep 17 00:00:00 2001 From: Ralf Becker Date: Fri, 17 Oct 2014 08:22:28 +0000 Subject: [PATCH 60/97] fix popup detection to cope with window.opener being a reference to itself --> should NOT be detected as popup --- phpgwapi/js/jsapi/egw_message.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/phpgwapi/js/jsapi/egw_message.js b/phpgwapi/js/jsapi/egw_message.js index 69c5afc59e..204e0091df 100644 --- a/phpgwapi/js/jsapi/egw_message.js +++ b/phpgwapi/js/jsapi/egw_message.js @@ -147,7 +147,7 @@ egw.extend('message', egw.MODULE_WND_LOCAL, function(_app, _wnd) { var popup = false; try { - if (_wnd.opener && typeof _wnd.opener.top.egw == 'function') + if (_wnd.opener && _wnd.opener != _wnd && typeof _wnd.opener.top.egw == 'function') { popup = true; } From 15bc2f3adefb10f4109d74b879d8bd1c83b8493d Mon Sep 17 00:00:00 2001 From: Ralf Becker Date: Fri, 17 Oct 2014 11:37:15 +0000 Subject: [PATCH 61/97] change tab loading again to initialise visible tab immediatly and gard et2_color / jPicker against being initialised twice --- etemplate/js/et2_widget_color.js | 21 ++++++++++++++------- etemplate/js/et2_widget_tabs.js | 5 +---- 2 files changed, 15 insertions(+), 11 deletions(-) diff --git a/etemplate/js/et2_widget_color.js b/etemplate/js/et2_widget_color.js index 5be9c5cc63..9a1fde48b5 100644 --- a/etemplate/js/et2_widget_color.js +++ b/etemplate/js/et2_widget_color.js @@ -99,7 +99,11 @@ var et2_color = et2_inputWidget.extend( this._super.call(this, arguments); }, - doLoadingFinished: function() { + doLoadingFinished: function() + { + // as tabs can cause a double loading, we check here if jPicker is already initialised + if (this.get_jPicker()) return; + this._super.apply(this, arguments); var self = this; @@ -130,7 +134,7 @@ var et2_color = et2_inputWidget.extend( setTimeout(function() { //Regex to exclude invalid charachters from class identifier name, to be able to address the class name with jquery selector later. var regExClassName = /[\[\]']+/g; - + // Make the buttons look like all the others jQuery("div.jPicker :button").addClass("et2_button et2_button_text"); @@ -163,14 +167,17 @@ var et2_color = et2_inputWidget.extend( /** * Get the jPicker object for this widget, so further things can be done to it + * + * Id of jPicker node is either our id+'_jPicker' or our dom_id (no idea why). */ get_jPicker: function() { - if(jQuery.jPicker.List.length) + for(var i=0; i < jQuery.jPicker.List.length; ++i) { - var self = this; - return jQuery(jQuery.jPicker.List.filter(function(elem,index) { - return (elem && elem.id == self.id + "_jPicker"); - }))[0]; + var node = jQuery.jPicker.List[i]; + if (node && (node.id == this.id+'_jPicker' || node.id == this.dom_id)) + { + return node; + } } return null; }, diff --git a/etemplate/js/et2_widget_tabs.js b/etemplate/js/et2_widget_tabs.js index 141f752011..30fa61c882 100644 --- a/etemplate/js/et2_widget_tabs.js +++ b/etemplate/js/et2_widget_tabs.js @@ -236,10 +236,7 @@ var et2_tabbox = et2_valueWidget.extend([et2_IInput], // We can do this and not wind up with 2 because child is a template, // which has special handling - // disabling immediate direct call for selected tab seems to have no recognisable impact and - // some widgets, eg. color-picker have problems with calling doLoadingFinished twice - // (color input gets re-rendered second time after hitting [Apply], if color-picker is in a tab) - //this._children[0].loadingFinished(promises); + this._children[0].loadingFinished(promises); // Defer parsing & loading of other tabs until later window.setTimeout(function() { From f14d3b4c8b271a5ade155da8b909dcb41af5cbbb Mon Sep 17 00:00:00 2001 From: Ralf Becker Date: Fri, 17 Oct 2014 13:49:42 +0000 Subject: [PATCH 62/97] add message to submit or postpone statistic first --- admin/js/app.js | 1 + admin/lang/egw_de.lang | 2 +- admin/lang/egw_en.lang | 1 + 3 files changed, 3 insertions(+), 1 deletion(-) diff --git a/admin/js/app.js b/admin/js/app.js index 4c892a9883..f04bb558d1 100644 --- a/admin/js/app.js +++ b/admin/js/app.js @@ -115,6 +115,7 @@ app.classes.admin = AppJS.extend( { if (this.iframe && this.iframe.getDOMNode().contentDocument.location.href.match(/menuaction=admin.admin_statistics.submit/)) { + this.egw.message(this.egw.lang('Please submit (or postpone) statistic first'), 'info'); return; // do not allow to leave statistics submit } if (_url) diff --git a/admin/lang/egw_de.lang b/admin/lang/egw_de.lang index c1f48f46e1..1ff2cb6691 100644 --- a/admin/lang/egw_de.lang +++ b/admin/lang/egw_de.lang @@ -191,7 +191,6 @@ delete all records admin de Alle Einträge löschen delete application admin de Anwendung löschen delete category admin de Kategorie löschen delete group admin de Gruppe löschen -delete including sub-enteries common de Inklusive Untereinträge löschen delete peer server admin de Server von Serververbund löschen delete selected entries admin de Ausgewählte Einträge löschen delete the category admin de Kategorie löschen @@ -504,6 +503,7 @@ please enter a name admin de Bitte einen Namen eingeben please enter a name for that server ! admin de Bitte einen Namen für diesen Server eingeben! please run setup to become current admin de Bitte Setup ausführen um die Installation zu aktualisieren please select admin de Bitte auswählen +please submit (or postpone) statistic first admin de Bitte Statistik erst abschicken (oder aufschieben) postfix with ldap admin de Postfix mit LDAP postpone for admin de Aufschieben um preferences admin de Einstellungen diff --git a/admin/lang/egw_en.lang b/admin/lang/egw_en.lang index 234d5f0cf7..3b0c1fe662 100644 --- a/admin/lang/egw_en.lang +++ b/admin/lang/egw_en.lang @@ -503,6 +503,7 @@ please enter a name admin en Enter a name please enter a name for that server ! admin en Enter a name for that server! please run setup to become current admin en Run setup to become current please select admin en Please select +please submit (or postpone) statistic first admin en Please submit (or postpone) statistic first postfix with ldap admin en Postfix with LDAP postpone for admin en Postpone for preferences admin en Preferences From e2d6d75607492a65da813017f86f83eaacd9e194 Mon Sep 17 00:00:00 2001 From: Ralf Becker Date: Fri, 17 Oct 2014 13:51:28 +0000 Subject: [PATCH 63/97] fix async jobs with scalar data (strings, int) got quoted over and over again --- admin/inc/class.admin_account.inc.php | 2 +- admin/inc/class.uiasyncservice.inc.php | 11 +++-------- phpgwapi/inc/common_functions.inc.php | 18 +++++++++--------- 3 files changed, 13 insertions(+), 18 deletions(-) diff --git a/admin/inc/class.admin_account.inc.php b/admin/inc/class.admin_account.inc.php index cc3482e5c0..2b5a6e3515 100644 --- a/admin/inc/class.admin_account.inc.php +++ b/admin/inc/class.admin_account.inc.php @@ -190,7 +190,7 @@ class admin_account /** * Delete an account * - * @param array $content=null + * @param array $content =null */ public static function delete(array $content=null) { diff --git a/admin/inc/class.uiasyncservice.inc.php b/admin/inc/class.uiasyncservice.inc.php index fe12e92494..cec90d57ca 100644 --- a/admin/inc/class.uiasyncservice.inc.php +++ b/admin/inc/class.uiasyncservice.inc.php @@ -1,6 +1,6 @@ @@ -59,7 +59,7 @@ class uiasyncservice { echo '

'.lang("You have no email address for your user set !!!")."

\n"; } - elseif (!$async->set_timer($times,'test','admin.uiasyncservice.test',(array)$GLOBALS['egw_info']['user']['account_email'])) + elseif (!$async->set_timer($times,'test','admin.uiasyncservice.test',$GLOBALS['egw_info']['user']['account_email'])) { echo '

'.lang("Error setting timer, wrong syntax or maybe there's one already running !!!")."

\n"; } @@ -93,11 +93,7 @@ class uiasyncservice if (isset($_POST['asyncservice']) && $_POST['asyncservice'] != $GLOBALS['egw_info']['server']['asyncservice']) { - $config =& CreateObject('phpgwapi.config','phpgwapi'); - $config->read_repository(); - $config->value('asyncservice',$GLOBALS['egw_info']['server']['asyncservice']=$_POST['asyncservice']); - $config->save_repository(); - unset($config); + config::save_value('asyncservice', $GLOBALS['egw_info']['server']['asyncservice']=$_POST['asyncservice'], 'phpgwapi'); } if (!$async->only_fallback) { @@ -189,7 +185,6 @@ class uiasyncservice } echo '

'."\n"; echo "\n"; - } function test($to) diff --git a/phpgwapi/inc/common_functions.inc.php b/phpgwapi/inc/common_functions.inc.php index 0867fefa32..2a532ac4a4 100755 --- a/phpgwapi/inc/common_functions.inc.php +++ b/phpgwapi/inc/common_functions.inc.php @@ -246,7 +246,7 @@ function array2string($var) * Check if a given extension is loaded or load it if possible (requires sometimes disabled or unavailable dl function) * * @param string $extension - * @param boolean $throw=false should we throw an exception, if $extension could not be loaded, default false = no + * @param boolean $throw =false should we throw an exception, if $extension could not be loaded, default false = no * @return boolean true if loaded now, false otherwise */ function check_load_extension($extension,$throw=false) @@ -1118,7 +1118,7 @@ function filesystem_separator() * print an array or object as pre-formatted html * * @param mixed $array - * @param boolean $print=true print or return the content + * @param boolean $print =true print or return the content * @return string if !$print */ function _debug_array($array,$print=True) @@ -1324,7 +1324,7 @@ function prepend_tables_prefix($prefix,$tables) * backtrace of the calling functions for php4.3+ else menuaction/scriptname * * @author RalfBecker-AT-outdoor-training.de - * @param int $remove=0 number of levels to remove + * @param int $remove =0 number of levels to remove * @return string function-names separated by slashes (beginning with the calling function not this one) */ function function_backtrace($remove=0) @@ -1356,7 +1356,7 @@ function function_backtrace($remove=0) * * @internal * @param array &$var reference of array to check - * @param string $name='' name of the array + * @param string $name ='' name of the array */ function _check_script_tag(&$var,$name='') { @@ -1499,7 +1499,7 @@ if (!function_exists('lang') || defined('NO_LANG')) // setup declares an own ver * function to handle multilanguage support * * @param string $key message in englich with %1, %2, ... placeholders - * @param string $vars=null multiple values to replace the placeholders + * @param string $vars =null multiple values to replace the placeholders * @return string translated message with placeholders replaced */ function lang($key,$vars=null) @@ -1520,7 +1520,7 @@ if (!function_exists('lang') || defined('NO_LANG')) // setup declares an own ver * as calling lang would try to load the translations, evtl. cause more errors, eg. because there's no db-connection. * * @param string $key message in englich with %1, %2, ... placeholders - * @param string $vars=null multiple values to replace the placeholders + * @param string $vars =null multiple values to replace the placeholders * @return string translated message with placeholders replaced */ function try_lang($key,$vars=null) @@ -1596,7 +1596,7 @@ if (isset($_SERVER['SCRIPT_FILENAME']) && $_SERVER['SCRIPT_FILENAME'] == __FILE_ * Used to migrate from PHP serialized database values to json-encoded ones. * * @param string $str string with serialized array - * @param boolean $allow_not_serialized=false true: return $str as is, if it is no serialized array + * @param boolean $allow_not_serialized =false true: return $str as is, if it is no serialized array * @return array|str|false */ function json_php_unserialize($str, $allow_not_serialized=false) @@ -1606,9 +1606,9 @@ function json_php_unserialize($str, $allow_not_serialized=false) { return $arr; } - if (!$allow_not_serialized || $str[0] == '[' || $str[0] == '{') + if (!$allow_not_serialized || $str[0] == '[' || $str[0] == '{' || $str[0] == '"' || $str === 'null' || ($val = json_decode($str)) !== null) { - return json_decode($str, true); + return isset($val) ? $val : json_decode($str, true); } return $str; } From dae1ce65545d6abdaa2974d05d5aba0a26134aae Mon Sep 17 00:00:00 2001 From: Hadi Nategh Date: Fri, 17 Oct 2014 16:34:14 +0000 Subject: [PATCH 64/97] Work in progress, standardization of dnd action helper styling for all apps --- etemplate/templates/default/etemplate2.css | 38 +++++++++++ mail/inc/class.mail_ui.inc.php | 2 +- mail/js/app.js | 12 ---- phpgwapi/js/egw_action/egw_action_dragdrop.js | 66 +++++++++++++++++-- 4 files changed, 100 insertions(+), 18 deletions(-) diff --git a/etemplate/templates/default/etemplate2.css b/etemplate/templates/default/etemplate2.css index 96f9c86837..ef4b35a1fb 100644 --- a/etemplate/templates/default/etemplate2.css +++ b/etemplate/templates/default/etemplate2.css @@ -1638,3 +1638,41 @@ div.et2_image_tooltipPopup { overflow:auto; } +/*egw_action_ddHelper*/ +div.et2_egw_action_ddHelper { + +} +div.et2_egw_action_ddHelper_tip { + position: relative; + text-overflow: ellipsis; + overflow: hidden; + white-space: nowrap; + float: left; + background-color: rgba(0, 0, 0, 0.70); + box-shadow: 6px 6px 8px gray; + border: 1px solid black; + border-top: none; + color: white; +} +div.et2_egw_action_ddHelper table.et2_egw_action_ddHelper_row { + background-color: rgba(255, 194, 0, 0.70); + box-shadow: 6px 6px 8px gray; + border: 1px solid black; +} +table.et2_egw_action_ddHelper_row tr { + background: none; + max-height: 20px; +} +table.et2_egw_action_ddHelper_row * { + white-space: nowrap !important; +} +span.et2_egw_action_ddHelper_itemCnt { + background: transparent; + position: absolute; + left: 20px; + top: 8%; + font-size: 367%; + color: rgba(255, 255, 255, 1); + text-align: center; + font-weight: bold; +} diff --git a/mail/inc/class.mail_ui.inc.php b/mail/inc/class.mail_ui.inc.php index 24f19b6359..2dab793946 100644 --- a/mail/inc/class.mail_ui.inc.php +++ b/mail/inc/class.mail_ui.inc.php @@ -1393,7 +1393,7 @@ class mail_ui 'drag_mail' => array( 'dragType' => array('mail'), 'type' => 'drag', - 'onExecute' => 'javaScript:app.mail.mail_dragStart', + //'onExecute' => 'javaScript:app.mail.mail_dragStart', ) ) ); diff --git a/mail/js/app.js b/mail/js/app.js index f573a03dd3..d1d7a59796 100644 --- a/mail/js/app.js +++ b/mail/js/app.js @@ -2806,18 +2806,6 @@ app.classes.mail = AppJS.extend( } }, - // Tree widget stubs - /** - * mail_dragStart - displays information while dragging - * - * @param action - * @param _senders - the representation of the elements dragged - * @return the ddhelper - */ - mail_dragStart: function(action,_senders) { - return $j("
" + _senders.length + " Mails selected
"); - }, - /** * mail_move2folder - implementation of the move action from action menu * diff --git a/phpgwapi/js/egw_action/egw_action_dragdrop.js b/phpgwapi/js/egw_action/egw_action_dragdrop.js index c7d1cdfe73..4fce6ee152 100644 --- a/phpgwapi/js/egw_action/egw_action_dragdrop.js +++ b/phpgwapi/js/egw_action/egw_action_dragdrop.js @@ -82,7 +82,62 @@ function egwDragActionImplementation() ai.helper = null; ai.ddTypes = []; ai.selected = []; + + // Define default helper DOM + ai.defaultDDHelper = function (_selected) + { + // Table containing clone of rows + var table = $j(document.createElement("table")).addClass('egwGridView_grid et2_egw_action_ddHelper_row'); + // tr element to use as last row to show lable more ... + var moreRow = $j(document.createElement('tr')).addClass('et2_egw_action_ddHelper_tip'); + // Main div helper container + var div = $j(document.createElement("div")).append(table); + // Lable to show number of items + var spanCnt = $j(document.createElement('span')) + .addClass('et2_egw_action_ddHelper_itemsCnt') + .appendTo(div); + + // TODO: get the right drag item next to the number + var itemsLabel = ''; + spanCnt.text(_selected.length + itemsLabel); + + var rows = []; + // Maximum number of rows to show + var maxRows = 3; + + var index = 0; + for (var i = 0; i < _selected.length;i++) + { + var row = $j(_selected[i].iface.getDOMNode()).clone(); + if (row) + { + rows.push(row); + table.append(row); + } + index++; + if (index == maxRows) + { + var restRows = _selected.length - maxRows; + if (restRows) moreRow.text((_selected.length - maxRows) +' '+egw.lang('more selected ...')); + table.append(moreRow); + break; + } + } + + var text = $j(document.createElement('div')).addClass('et2_egw_action_ddHelper_textArea'); + div.append(text); + // Add notice of Ctrl key, if supported + if('draggable' in document.createElement('span') && + navigator && navigator.userAgent.indexOf('Chrome') >= 0) + { + var key = ["Mac68K","MacPPC","MacIntel"].indexOf(window.navigator.platform) < 0 ? 'Ctrl' : 'Command'; + + text.text(egw.lang('Hold %1 to drag %2 to your computer',key, itemsLabel)); + } + return div; + } + ai.doRegisterAction = function(_aoi, _callback, _context) { var node = _aoi.getDOMNode(); @@ -221,6 +276,9 @@ function egwDragActionImplementation() if (ai.helper) { + // Add a basic class to the helper in order to standardize the background layout + ai.helper.addClass('et2_egw_action_ddHelper'); + // Append the helper object to the body element - this // fixes a bug in IE: If the element isn't inserted into // the DOM-tree jquery appends it to the parent node. @@ -230,7 +288,7 @@ function egwDragActionImplementation() } // Return an empty div if the helper dom node is not set - return $j(document.createElement("div")); + return $j(document.createElement("div")).addClass('et2_egw_action_ddHelper'); }, "start": function(e) { return ai.helper != null; @@ -264,7 +322,7 @@ function egwDragActionImplementation() // component "refreshPositions": true, "scroll": false, - "containment": "document", + //"containment": "document", "iframeFix": true } ); @@ -332,9 +390,7 @@ function egwDragActionImplementation() // If no helper has been defined, create an default one if (!this.helper && hasLink) { - this.helper = $j(document.createElement("div")); - this.helper.addClass("egw_action_ddHelper"); - this.helper.text("(" + _selected.length + ")"); + this.helper = ai.defaultDDHelper(_selected); } return true; From 24b577921080efb63e4063677b381210df782d53 Mon Sep 17 00:00:00 2001 From: Nathan Gray Date: Fri, 17 Oct 2014 17:17:23 +0000 Subject: [PATCH 65/97] Better styling for planned times so early tasks can be seen too. --- etemplate/js/et2_widget_gantt.js | 9 +++++---- etemplate/templates/default/etemplate2.css | 3 ++- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/etemplate/js/et2_widget_gantt.js b/etemplate/js/et2_widget_gantt.js index dcb51a7701..db6b6342d7 100644 --- a/etemplate/js/et2_widget_gantt.js +++ b/etemplate/js/et2_widget_gantt.js @@ -73,6 +73,7 @@ var et2_gantt = et2_valueWidget.extend([et2_IResizeable,et2_IInput], show_progress: true, order_branch: true, min_column_width: 30, + task_height: 25, fit_tasks: true, autosize: '', // Date rounding happens either way, but this way it rounds to the displayed grid resolution @@ -1015,9 +1016,9 @@ $j(function() { var text = ''; if(task.planned_start) { - if(typeof task.planned_start == 'string') task.planned_start = new Date(task.planned_start); + if(typeof task.planned_start == 'string') task.planned_start = gantt.date.parseDate(task.planned_start, "xml_date"); var p_start = gantt.posFromDate(task.planned_start) - gantt.posFromDate(start); - text = "
" + text = "
" + gantt.date.date_to_str(gantt.config.api_date)(task.planned_start) + "
"; } @@ -1027,9 +1028,9 @@ $j(function() { var text = ''; if(task.planned_end) { - if(typeof task.planned_end == 'string') task.planned_end = new Date(task.planned_end); + if(typeof task.planned_end == 'string') task.planned_end = gantt.date.parseDate(task.planned_end, "xml_date"); var p_end = gantt.posFromDate(task.planned_end) - gantt.posFromDate(end); - text = "
" + text = "
" + gantt.date.date_to_str(gantt.config.api_date)(task.planned_end) + "
"; } diff --git a/etemplate/templates/default/etemplate2.css b/etemplate/templates/default/etemplate2.css index ef4b35a1fb..3d7225eee8 100644 --- a/etemplate/templates/default/etemplate2.css +++ b/etemplate/templates/default/etemplate2.css @@ -1549,7 +1549,7 @@ div.ui-toolbar-menulist{ } .et2_gantt .gantt_task_planned { - margin-top: 7px; + height: 10px; position: absolute; opacity: 0.3; border-style: outset; @@ -1576,6 +1576,7 @@ div.ui-toolbar-menulist{ .et2_gantt .gantt_right .gantt_task_planned { border-left: none; + top: 25px; } /** From 0a00a710f7738e82ea3f6fb3c2eec024cae33617 Mon Sep 17 00:00:00 2001 From: Ralf Becker Date: Sat, 18 Oct 2014 12:01:10 +0000 Subject: [PATCH 66/97] * Filemanager: fix since PHP 5.5.18 not longer working non-ascii chars in filenames, eg. German umlauts or accents --- phpgwapi/inc/class.egw_vfs.inc.php | 29 ++--- .../class.filesystem_stream_wrapper.inc.php | 22 ++-- .../inc/class.global_stream_wrapper.inc.php | 2 +- .../inc/class.links_stream_wrapper.inc.php | 8 +- .../inc/class.sqlfs_stream_wrapper.inc.php | 36 +++--- phpgwapi/inc/class.vfs_stream_wrapper.inc.php | 110 +++++++++++++----- 6 files changed, 131 insertions(+), 76 deletions(-) diff --git a/phpgwapi/inc/class.egw_vfs.inc.php b/phpgwapi/inc/class.egw_vfs.inc.php index d52b05a1e7..a2793fde20 100644 --- a/phpgwapi/inc/class.egw_vfs.inc.php +++ b/phpgwapi/inc/class.egw_vfs.inc.php @@ -7,7 +7,7 @@ * @package api * @subpackage vfs * @author Ralf Becker - * @copyright (c) 2008-10 by Ralf Becker + * @copyright (c) 2008-14 by Ralf Becker * @version $Id$ */ @@ -56,6 +56,9 @@ * * The static egw_vfs::copy() method does exactly that, but you have to do it eg. on your own, if * you want to copy eg. an uploaded file into the vfs. + * + * egw_vfs::parse_url($url, $component=-1), egw_vfs::dirname($url) and egw_vfs::basename($url) work + * on urls containing utf-8 characters, which get NOT urlencoded in our VFS! */ class egw_vfs extends vfs_stream_wrapper { @@ -331,7 +334,7 @@ class egw_vfs extends vfs_stream_wrapper if (self::LOG_LEVEL > 0) error_log(__METHOD__.'('.array2string($url).','.array2string($path).') already mounted.'); return true; // already mounted } - self::load_wrapper(parse_url($url,PHP_URL_SCHEME)); + self::load_wrapper(self::parse_url($url,PHP_URL_SCHEME)); if ($check_url && (!file_exists($url) || opendir($url) === false)) { @@ -517,7 +520,7 @@ class egw_vfs extends vfs_stream_wrapper if ($opts['maxdepth']) $opts['depth']++;; unset($opts['order']); unset($opts['limit']); - foreach(self::find($options['url']?$file:parse_url($file,PHP_URL_PATH),$opts,true) as $p => $s) + foreach(self::find($options['url']?$file:self::parse_url($file,PHP_URL_PATH),$opts,true) as $p => $s) { unset($result[$p]); $result[$p] = $s; @@ -641,7 +644,7 @@ class egw_vfs extends vfs_stream_wrapper return; // wrong type } $stat = array_slice($stat,13); // remove numerical indices 0-12 - $stat['path'] = parse_url($path,PHP_URL_PATH); + $stat['path'] = self::parse_url($path,PHP_URL_PATH); $stat['name'] = $options['remove'] > 0 ? implode('/',array_slice(explode('/',$stat['path']),$options['remove'])) : self::basename($path); if ($options['mime'] || $options['need_mime']) @@ -683,7 +686,7 @@ class egw_vfs extends vfs_stream_wrapper // do we return url or just vfs pathes if (!$options['url']) { - $path = parse_url($path,PHP_URL_PATH); + $path = self::parse_url($path,PHP_URL_PATH); } $result[$path] = $stat; } @@ -717,7 +720,7 @@ class egw_vfs extends vfs_stream_wrapper // some precaution to never allow to (recursivly) remove /, /apps or /home foreach((array)$urls as $url) { - if (preg_match('/^\/?(home|apps|)\/*$/',parse_url($url,PHP_URL_PATH))) + if (preg_match('/^\/?(home|apps|)\/*$/',self::parse_url($url,PHP_URL_PATH))) { throw new egw_exception_assertion_failed(__METHOD__.'('.array2string($urls).") Cautiously rejecting to remove folder '$url'!"); } @@ -832,7 +835,7 @@ class egw_vfs extends vfs_stream_wrapper } // check if we use an EGroupwre stream wrapper, or a stock php one // if it's not an EGroupware one, we can NOT use uid, gid and mode! - if (($scheme = parse_url($stat['url'],PHP_URL_SCHEME)) && !(class_exists(self::scheme2class($scheme)))) + if (($scheme = self::parse_url($stat['url'],PHP_URL_SCHEME)) && !(class_exists(self::scheme2class($scheme)))) { switch($check) { @@ -1214,7 +1217,7 @@ class egw_vfs extends vfs_stream_wrapper { list($url,$query) = explode('?',$url,2); // strip the query first, as it can contain slashes - if ($url == '/' || $url[0] != '/' && parse_url($url,PHP_URL_PATH) == '/') + if ($url == '/' || $url[0] != '/' && self::parse_url($url,PHP_URL_PATH) == '/') { //error_log(__METHOD__."($url) returning FALSE: already in root!"); return false; @@ -1313,7 +1316,7 @@ class egw_vfs extends vfs_stream_wrapper } if ($path[0] != '/') { - $path = parse_url($path,PHP_URL_PATH); + $path = self::parse_url($path,PHP_URL_PATH); } // we do NOT need to encode % itself, as our path are already url encoded, with the exception of ' ' and '+' // we urlencode double quotes '"', as that fixes many problems in html markup @@ -1331,7 +1334,7 @@ class egw_vfs extends vfs_stream_wrapper public static function download_zip(Array $files, $name = false) { error_log(__METHOD__ . ': '.implode(',',$files)); - + // Create zip file $zip_file = tempnam($GLOBALS['egw_info']['server']['temp_dir'], 'zip'); @@ -1403,7 +1406,7 @@ class egw_vfs extends vfs_stream_wrapper { $base_dir .='/'; } - + // Go into directories, find them all $files = self::find($files); $links = array(); @@ -1477,7 +1480,7 @@ class egw_vfs extends vfs_stream_wrapper error_log('close() result: '.array2string($result)); return 'Error creating zip file'; } - + error_log("Total files: " . $total_files . " Peak memory to zip: " . self::hsize(memory_get_peak_usage(true))); // Stop any buffering @@ -1804,7 +1807,7 @@ class egw_vfs extends vfs_stream_wrapper { if (substr($file, 0, 6) == '/apps/') { - $file = parse_url(egw_vfs::resolve_url_symlinks($path), PHP_URL_PATH); + $file = self::parse_url(egw_vfs::resolve_url_symlinks($path), PHP_URL_PATH); } //Assemble the thumbnail parameters diff --git a/phpgwapi/inc/class.filesystem_stream_wrapper.inc.php b/phpgwapi/inc/class.filesystem_stream_wrapper.inc.php index e76360a82b..dc45a62758 100644 --- a/phpgwapi/inc/class.filesystem_stream_wrapper.inc.php +++ b/phpgwapi/inc/class.filesystem_stream_wrapper.inc.php @@ -7,7 +7,7 @@ * @package api * @subpackage vfs * @author Ralf Becker - * @copyright (c) 2008-10 by Ralf Becker + * @copyright (c) 2008-14 by Ralf Becker * @version $Id$ */ @@ -160,7 +160,7 @@ class filesystem_stream_wrapper implements iface_stream_wrapper } // open the "real" file - if (!($this->opened_stream = fopen($path=egw_vfs::decodePath(parse_url($url,PHP_URL_PATH)),$mode,$options))) + if (!($this->opened_stream = fopen($path=egw_vfs::decodePath(egw_vfs::parse_url($url,PHP_URL_PATH)),$mode,$options))) { if (self::LOG_LEVEL) error_log(__METHOD__."($url,$mode,$options) fopen('$path','$mode',$options) returned false!"); return false; @@ -302,7 +302,7 @@ class filesystem_stream_wrapper implements iface_stream_wrapper */ static function unlink ( $url ) { - $path = egw_vfs::decodePath(parse_url($url,PHP_URL_PATH)); + $path = egw_vfs::decodePath(egw_vfs::parse_url($url,PHP_URL_PATH)); // check access rights (file need to exist and directory need to be writable if (!file_exists($path) || is_dir($path) || !egw_vfs::check_access(egw_vfs::dirname($url),egw_vfs::WRITABLE)) @@ -327,8 +327,8 @@ class filesystem_stream_wrapper implements iface_stream_wrapper */ static function rename ( $url_from, $url_to ) { - $from = parse_url($url_from); - $to = parse_url($url_to); + $from = egw_vfs::parse_url($url_from); + $to = egw_vfs::parse_url($url_to); // check access rights if (!($from_stat = self::url_stat($url_from,0)) || !egw_vfs::check_access(egw_vfs::dirname($url_from),egw_vfs::WRITABLE)) @@ -378,7 +378,7 @@ class filesystem_stream_wrapper implements iface_stream_wrapper */ static function mkdir ( $url, $mode, $options ) { - $path = egw_vfs::decodePath(parse_url($url,PHP_URL_PATH)); + $path = egw_vfs::decodePath(egw_vfs::parse_url($url,PHP_URL_PATH)); $recursive = (bool)($options & STREAM_MKDIR_RECURSIVE); // find the real parent (might be more then one level if $recursive!) @@ -410,7 +410,7 @@ class filesystem_stream_wrapper implements iface_stream_wrapper */ static function rmdir ( $url, $options ) { - $path = egw_vfs::decodePath(parse_url($url,PHP_URL_PATH)); + $path = egw_vfs::decodePath(egw_vfs::parse_url($url,PHP_URL_PATH)); $parent = dirname($path); // check access rights (in real filesystem AND by mount perms) @@ -432,7 +432,7 @@ class filesystem_stream_wrapper implements iface_stream_wrapper */ static function touch($url,$time=null,$atime=null) { - $path = egw_vfs::decodePath(parse_url($url,PHP_URL_PATH)); + $path = egw_vfs::decodePath(egw_vfs::parse_url($url,PHP_URL_PATH)); $parent = dirname($path); // check access rights (in real filesystem AND by mount perms) @@ -499,7 +499,7 @@ class filesystem_stream_wrapper implements iface_stream_wrapper $this->opened_dir = null; - $path = egw_vfs::decodePath(parse_url($this->opened_dir_url = $url,PHP_URL_PATH)); + $path = egw_vfs::decodePath(egw_vfs::parse_url($this->opened_dir_url = $url,PHP_URL_PATH)); // ToDo: check access rights @@ -539,7 +539,7 @@ class filesystem_stream_wrapper implements iface_stream_wrapper */ static function url_stat ( $url, $flags ) { - $parts = parse_url($url); + $parts = egw_vfs::parse_url($url); $path = egw_vfs::decodePath($parts['path']); $stat = @stat($path); // suppressed the stat failed warnings @@ -712,7 +712,7 @@ class filesystem_stream_wrapper implements iface_stream_wrapper */ static function deny_script($url) { - $parts = parse_url($url); + $parts = egw_vfs::parse_url($url); parse_str($parts['query'],$get); $deny = !$get['exec'] && preg_match(self::SCRIPT_EXTENSIONS_PREG,$parts['path']); diff --git a/phpgwapi/inc/class.global_stream_wrapper.inc.php b/phpgwapi/inc/class.global_stream_wrapper.inc.php index 1c362f1660..c134baf387 100644 --- a/phpgwapi/inc/class.global_stream_wrapper.inc.php +++ b/phpgwapi/inc/class.global_stream_wrapper.inc.php @@ -29,7 +29,7 @@ class global_stream_wrapper public function stream_open($path, $mode, $options, &$opened_path) { - $this->stream = &$GLOBALS[$this->name=parse_url($path,PHP_URL_HOST)]; + $this->stream = &$GLOBALS[$this->name=egw_vfs::parse_url($path,PHP_URL_HOST)]; $this->pos = 0; if (!is_string($this->stream)) return false; return true; diff --git a/phpgwapi/inc/class.links_stream_wrapper.inc.php b/phpgwapi/inc/class.links_stream_wrapper.inc.php index 3a303a1a38..5ebeb33dc7 100644 --- a/phpgwapi/inc/class.links_stream_wrapper.inc.php +++ b/phpgwapi/inc/class.links_stream_wrapper.inc.php @@ -74,7 +74,7 @@ class links_stream_wrapper extends links_stream_wrapper_parent { return true; } - $path = parse_url($url,PHP_URL_PATH); + $path = egw_vfs::parse_url($url,PHP_URL_PATH); list(,$apps,$app,$id,$rel_path) = explode('/',$path,5); @@ -154,7 +154,7 @@ class links_stream_wrapper extends links_stream_wrapper_parent // if entry directory does not exist --> return fake directory elseif (!($ret = parent::url_stat($url,$flags,$eacl_check)) && $eacl_check) { - list(,/*$apps*/,/*$app*/,$id,$rel_path) = explode('/', parse_url($url, PHP_URL_PATH), 5); + list(,/*$apps*/,/*$app*/,$id,$rel_path) = explode('/', egw_vfs::parse_url($url, PHP_URL_PATH), 5); if ($id && !isset($rel_path)) { $ret = array( @@ -230,8 +230,8 @@ class links_stream_wrapper extends links_stream_wrapper_parent if($path[0] != '/') { - if (strpos($path,'?') !== false) $query = parse_url($path,PHP_URL_QUERY); - $path = parse_url($path,PHP_URL_PATH).($query ? '?'.$query : ''); + if (strpos($path,'?') !== false) $query = egw_vfs::parse_url($path,PHP_URL_QUERY); + $path = egw_vfs::parse_url($path,PHP_URL_PATH).($query ? '?'.$query : ''); } list(,$apps,$app,$id) = explode('/',$path); diff --git a/phpgwapi/inc/class.sqlfs_stream_wrapper.inc.php b/phpgwapi/inc/class.sqlfs_stream_wrapper.inc.php index ecb9118e70..49f4819949 100644 --- a/phpgwapi/inc/class.sqlfs_stream_wrapper.inc.php +++ b/phpgwapi/inc/class.sqlfs_stream_wrapper.inc.php @@ -192,7 +192,7 @@ class sqlfs_stream_wrapper implements iface_stream_wrapper { if (self::LOG_LEVEL > 1) error_log(__METHOD__."($url,$mode,$options)"); - $path = parse_url($url,PHP_URL_PATH); + $path = egw_vfs::parse_url($url,PHP_URL_PATH); $this->operation = self::url2operation($url); $dir = egw_vfs::dirname($url); @@ -542,7 +542,7 @@ class sqlfs_stream_wrapper implements iface_stream_wrapper { if (self::LOG_LEVEL > 1) error_log(__METHOD__."($url)"); - $path = parse_url($url,PHP_URL_PATH); + $path = egw_vfs::parse_url($url,PHP_URL_PATH); if (!($stat = self::url_stat($path,STREAM_URL_STAT_LINK)) || !egw_vfs::check_access(egw_vfs::dirname($path),egw_vfs::WRITABLE, $parent_stat)) { @@ -590,9 +590,9 @@ class sqlfs_stream_wrapper implements iface_stream_wrapper { if (self::LOG_LEVEL > 1) error_log(__METHOD__."($url_from,$url_to)"); - $path_from = parse_url($url_from,PHP_URL_PATH); + $path_from = egw_vfs::parse_url($url_from,PHP_URL_PATH); $from_dir = egw_vfs::dirname($path_from); - $path_to = parse_url($url_to,PHP_URL_PATH); + $path_to = egw_vfs::parse_url($url_to,PHP_URL_PATH); $to_dir = egw_vfs::dirname($path_to); $operation = self::url2operation($url_from); @@ -684,7 +684,7 @@ class sqlfs_stream_wrapper implements iface_stream_wrapper { if (self::LOG_LEVEL > 1) error_log(__METHOD__."($url,$mode,$options)"); if (self::LOG_LEVEL > 1) error_log(__METHOD__." called from:".function_backtrace()); - $path = parse_url($url,PHP_URL_PATH); + $path = egw_vfs::parse_url($url,PHP_URL_PATH); if (self::url_stat($path,STREAM_URL_STAT_QUIET)) { @@ -698,7 +698,7 @@ class sqlfs_stream_wrapper implements iface_stream_wrapper return false; } $parent_path = egw_vfs::dirname($path); - if (($query = parse_url($url,PHP_URL_QUERY))) $parent_path .= '?'.$query; + if (($query = egw_vfs::parse_url($url,PHP_URL_QUERY))) $parent_path .= '?'.$query; $parent = self::url_stat($parent_path,STREAM_URL_STAT_QUIET); // check if we should also create all non-existing path components and our parent does not exist, @@ -771,7 +771,7 @@ class sqlfs_stream_wrapper implements iface_stream_wrapper { if (self::LOG_LEVEL > 1) error_log(__METHOD__."($url)"); - $path = parse_url($url,PHP_URL_PATH); + $path = egw_vfs::parse_url($url,PHP_URL_PATH); $parent = egw_vfs::dirname($path); if (!($stat = self::url_stat($path,0)) || $stat['mime'] != self::DIR_MIME_TYPE || @@ -826,7 +826,7 @@ class sqlfs_stream_wrapper implements iface_stream_wrapper unset($atime); // not used if (self::LOG_LEVEL > 1) error_log(__METHOD__."($url, $time)"); - $path = parse_url($url,PHP_URL_PATH); + $path = egw_vfs::parse_url($url,PHP_URL_PATH); if (!($stat = self::url_stat($path,STREAM_URL_STAT_QUIET))) { @@ -862,7 +862,7 @@ class sqlfs_stream_wrapper implements iface_stream_wrapper { if (self::LOG_LEVEL > 1) error_log(__METHOD__."($url,$owner)"); - $path = parse_url($url,PHP_URL_PATH); + $path = egw_vfs::parse_url($url,PHP_URL_PATH); if (!($stat = self::url_stat($path,0))) { @@ -902,7 +902,7 @@ class sqlfs_stream_wrapper implements iface_stream_wrapper { if (self::LOG_LEVEL > 1) error_log(__METHOD__."($url,$owner)"); - $path = parse_url($url,PHP_URL_PATH); + $path = egw_vfs::parse_url($url,PHP_URL_PATH); if (!($stat = self::url_stat($path,0))) { @@ -943,7 +943,7 @@ class sqlfs_stream_wrapper implements iface_stream_wrapper { if (self::LOG_LEVEL > 1) error_log(__METHOD__."($url, $mode)"); - $path = parse_url($url,PHP_URL_PATH); + $path = egw_vfs::parse_url($url,PHP_URL_PATH); if (!($stat = self::url_stat($path,0))) { @@ -983,7 +983,7 @@ class sqlfs_stream_wrapper implements iface_stream_wrapper { $this->opened_dir = null; - $path = parse_url($url,PHP_URL_PATH); + $path = egw_vfs::parse_url($url,PHP_URL_PATH); if (!($stat = self::url_stat($url,0)) || // dir not found $stat['mime'] != self::DIR_MIME_TYPE || // no dir @@ -1055,7 +1055,7 @@ class sqlfs_stream_wrapper implements iface_stream_wrapper } if (self::LOG_LEVEL > 1) error_log(__METHOD__."('$url',$flags,$eacl_access)"); - $path = parse_url($url,PHP_URL_PATH); + $path = egw_vfs::parse_url($url,PHP_URL_PATH); // webdav adds a trailing slash to dirs, which causes url_stat to NOT find the file otherwise if ($path != '/' && substr($path,-1) == '/') @@ -1279,7 +1279,7 @@ class sqlfs_stream_wrapper implements iface_stream_wrapper ') VALUES (:fs_name,:fs_dir,:fs_mode,:fs_uid,:fs_gid,:fs_created,:fs_modified,:fs_creator,:fs_mime,:fs_size,:fs_link)'; if (self::LOG_LEVEL > 2) $query = '/* '.__METHOD__.': '.__LINE__.' */ '.$query; $stmt = self::$pdo->prepare($query); - unset(self::$stat_cache[parse_url($link,PHP_URL_PATH)]); + unset(self::$stat_cache[egw_vfs::parse_url($link,PHP_URL_PATH)]); return !!$stmt->execute(array( 'fs_name' => egw_vfs::basename($link), @@ -1310,7 +1310,7 @@ class sqlfs_stream_wrapper implements iface_stream_wrapper */ static function check_extended_acl($url,$check) { - $url_path = parse_url($url,PHP_URL_PATH); + $url_path = egw_vfs::parse_url($url,PHP_URL_PATH); if (is_null(self::$extended_acl)) { @@ -1380,7 +1380,7 @@ class sqlfs_stream_wrapper implements iface_stream_wrapper { if ($path[0] != '/') { - $path = parse_url($path,PHP_URL_PATH); + $path = egw_vfs::parse_url($path,PHP_URL_PATH); } if (is_null($fs_id)) { @@ -1731,7 +1731,7 @@ class sqlfs_stream_wrapper implements iface_stream_wrapper */ static protected function _remove_password(&$url) { - $parts = parse_url($url); + $parts = egw_vfs::parse_url($url); if ($parts['pass'] || $parts['scheme']) { @@ -1753,7 +1753,7 @@ class sqlfs_stream_wrapper implements iface_stream_wrapper if (strpos(is_array($url) ? $url['query'] : $url,'storage=') !== false) { $query = null; - parse_str(is_array($url) ? $url['query'] : parse_url($url,PHP_URL_QUERY), $query); + parse_str(is_array($url) ? $url['query'] : egw_vfs::parse_url($url,PHP_URL_QUERY), $query); switch ($query['storage']) { case 'db': diff --git a/phpgwapi/inc/class.vfs_stream_wrapper.inc.php b/phpgwapi/inc/class.vfs_stream_wrapper.inc.php index 4328424c1c..b0c886a72b 100644 --- a/phpgwapi/inc/class.vfs_stream_wrapper.inc.php +++ b/phpgwapi/inc/class.vfs_stream_wrapper.inc.php @@ -7,7 +7,7 @@ * @package api * @subpackage vfs * @author Ralf Becker - * @copyright (c) 2008-10 by Ralf Becker + * @copyright (c) 2008-14 by Ralf Becker * @version $Id$ */ @@ -159,9 +159,9 @@ class vfs_stream_wrapper implements iface_stream_wrapper $url = $stat['url']; } // if the url resolves to a symlink to the vfs, resolve this vfs:// url direct - if ($url && parse_url($url,PHP_URL_SCHEME) == self::SCHEME) + if ($url && self::parse_url($url,PHP_URL_SCHEME) == self::SCHEME) { - $url = self::resolve_url(parse_url($url,PHP_URL_PATH)); + $url = self::resolve_url(self::parse_url($url,PHP_URL_PATH)); } if (self::LOG_LEVEL > 1) error_log(__METHOD__."($path,file_exists=$file_exists,resolve_last_symlink=$resolve_last_symlink) = '$url'$log"); return $url; @@ -204,7 +204,7 @@ class vfs_stream_wrapper implements iface_stream_wrapper 'home' => str_replace(array('\\\\','\\'),array('','/'),$GLOBALS['egw_info']['user']['homedirectory']), ); } - $parts = array_merge(parse_url($path),$defaults); + $parts = array_merge(self::parse_url($path),$defaults); if (!$parts['host']) $parts['host'] = 'default'; // otherwise we get an invalid url (scheme:///path/to/something)! if (!empty($parts['scheme']) && $parts['scheme'] != self::SCHEME) @@ -218,7 +218,7 @@ class vfs_stream_wrapper implements iface_stream_wrapper { if ($mounted == '/' || $mounted == $parts['path'] || $mounted.'/' == substr($parts['path'],0,strlen($mounted)+1)) { - $scheme = parse_url($url,PHP_URL_SCHEME); + $scheme = self::parse_url($url,PHP_URL_SCHEME); if (is_null(self::$wrappers) || !in_array($scheme,self::$wrappers)) { self::load_wrapper($scheme); @@ -295,7 +295,7 @@ class vfs_stream_wrapper implements iface_stream_wrapper return false; } $this->opened_stream_mode = $mode; - $this->opened_stream_path = $path[0] == '/' ? $path : parse_url($path, PHP_URL_PATH); + $this->opened_stream_path = $path[0] == '/' ? $path : self::parse_url($path, PHP_URL_PATH); $this->opened_stream_url = $url; $this->opened_stream_is_new = !$stat; @@ -470,7 +470,7 @@ class vfs_stream_wrapper implements iface_stream_wrapper { $GLOBALS['egw']->hooks->process(array( 'location' => 'vfs_unlink', - 'path' => $path[0] == '/' ? $path : parse_url($path, PHP_URL_PATH), + 'path' => $path[0] == '/' ? $path : self::parse_url($path, PHP_URL_PATH), 'url' => $url, 'stat' => $stat, ),'',true); @@ -498,7 +498,7 @@ class vfs_stream_wrapper implements iface_stream_wrapper return false; } // if file is moved from one filesystem / wrapper to an other --> copy it (rename fails cross wrappers) - if (parse_url($url_from,PHP_URL_SCHEME) == parse_url($url_to,PHP_URL_SCHEME)) + if (self::parse_url($url_from,PHP_URL_SCHEME) == self::parse_url($url_to,PHP_URL_SCHEME)) { self::symlinkCache_remove($path_from); $ret = rename($url_from,$url_to); @@ -523,8 +523,8 @@ class vfs_stream_wrapper implements iface_stream_wrapper { $GLOBALS['egw']->hooks->process(array( 'location' => 'vfs_rename', - 'from' => $path_from[0] == '/' ? $path_from : parse_url($path_from, PHP_URL_PATH), - 'to' => $path_to[0] == '/' ? $path_to : parse_url($path_to, PHP_URL_PATH), + 'from' => $path_from[0] == '/' ? $path_from : self::parse_url($path_from, PHP_URL_PATH), + 'to' => $path_to[0] == '/' ? $path_to : self::parse_url($path_to, PHP_URL_PATH), 'url_from' => $url_from, 'url_to' => $url_to, ),'',true); @@ -556,7 +556,7 @@ class vfs_stream_wrapper implements iface_stream_wrapper { $GLOBALS['egw']->hooks->process(array( 'location' => 'vfs_mkdir', - 'path' => $path[0] == '/' ? $path : parse_url($path, PHP_URL_PATH), + 'path' => $path[0] == '/' ? $path : self::parse_url($path, PHP_URL_PATH), 'url' => $url, ),'',true); } @@ -589,7 +589,7 @@ class vfs_stream_wrapper implements iface_stream_wrapper { $GLOBALS['egw']->hooks->process(array( 'location' => 'vfs_rmdir', - 'path' => $path[0] == '/' ? $path : parse_url($path, PHP_URL_PATH), + 'path' => $path[0] == '/' ? $path : self::parse_url($path, PHP_URL_PATH), 'url' => $url, 'stat' => $stat, ),'',true); @@ -620,7 +620,7 @@ class vfs_stream_wrapper implements iface_stream_wrapper { return false; } - $k=(string)parse_url($url,PHP_URL_SCHEME); + $k=(string)self::parse_url($url,PHP_URL_SCHEME); if (!(is_array($scheme2urls[$k]))) $scheme2urls[$k] = array(); $scheme2urls[$k][$path] = $url; } @@ -648,7 +648,7 @@ class vfs_stream_wrapper implements iface_stream_wrapper // we need to re-translate the urls to pathes, as they can eg. contain symlinks foreach($urls as $path => $url) { - if (isset($r[$url]) || isset($r[$url=parse_url($url,PHP_URL_PATH)])) + if (isset($r[$url]) || isset($r[$url=self::parse_url($url,PHP_URL_PATH)])) { $ret[$path] = $r[$url]; } @@ -774,7 +774,7 @@ class vfs_stream_wrapper implements iface_stream_wrapper { return false; } - if (($scheme = parse_url($url,PHP_URL_SCHEME)) && !$recheck) + if (($scheme = self::parse_url($url,PHP_URL_SCHEME)) && !$recheck) { // check it it's an eGW stream wrapper returning mime-type via url_stat // we need to first check if the constant is defined, as we get a fatal error in php5.3 otherwise @@ -782,7 +782,7 @@ class vfs_stream_wrapper implements iface_stream_wrapper defined($class.'::STAT_RETURN_MIME_TYPE') && ($mime_attr = constant($class.'::STAT_RETURN_MIME_TYPE'))) { - $stat = call_user_func(array($class,'url_stat'),parse_url($url,PHP_URL_PATH),0); + $stat = call_user_func(array($class,'url_stat'),self::parse_url($url,PHP_URL_PATH),0); if ($stat && $stat[$mime_attr]) { $mime = $stat[$mime_attr]; @@ -801,7 +801,7 @@ class vfs_stream_wrapper implements iface_stream_wrapper // using EGw's own mime magic (currently only checking the extension!) if (!$mime) { - $mime = mime_magic::filename2mime(parse_url($url,PHP_URL_PATH)); + $mime = mime_magic::filename2mime(self::parse_url($url,PHP_URL_PATH)); } //error_log(__METHOD__."($path,$recheck) mime=$mime"); return $mime; @@ -830,7 +830,7 @@ class vfs_stream_wrapper implements iface_stream_wrapper } $this->opened_dir_writable = egw_vfs::check_access($this->opened_dir_url,egw_vfs::WRITABLE); // check our fstab if we need to add some of the mountpoints - $basepath = parse_url($path,PHP_URL_PATH); + $basepath = self::parse_url($path,PHP_URL_PATH); foreach(self::$fstab as $mounted => $url) { if (((egw_vfs::dirname($mounted) == $basepath || egw_vfs::dirname($mounted).'/' == $basepath) && $mounted != '/') && @@ -901,7 +901,7 @@ class vfs_stream_wrapper implements iface_stream_wrapper { if ($lpath[0] != '/') // concat relative path { - $lpath = egw_vfs::concat(parse_url($path,PHP_URL_PATH),'../'.$lpath); + $lpath = egw_vfs::concat(self::parse_url($path,PHP_URL_PATH),'../'.$lpath); } $url = egw_vfs::PREFIX.$lpath; if (self::LOG_LEVEL > 1) error_log(__METHOD__."($path,$flags) symlif (substr($path,-1) == '/' && $path != '/') $path = substr($path,0,-1); // remove trailing slash eg. added by WebDAVink found and resolved to $url"); @@ -929,7 +929,7 @@ class vfs_stream_wrapper implements iface_stream_wrapper throw $e; } // check if a failed url_stat was for a home dir, in that case silently create it - if (!$stat && $try_create_home && egw_vfs::dirname(parse_url($path,PHP_URL_PATH)) == '/home' && + if (!$stat && $try_create_home && egw_vfs::dirname(self::parse_url($path,PHP_URL_PATH)) == '/home' && ($id = $GLOBALS['egw']->accounts->name2id(basename($path))) && $GLOBALS['egw']->accounts->id2name($id) == basename($path)) // make sure path has the right case! { @@ -994,7 +994,7 @@ class vfs_stream_wrapper implements iface_stream_wrapper if ($lpath[0] != '/') { - $lpath = egw_vfs::concat(parse_url($url,PHP_URL_PATH),'../'.$lpath); + $lpath = egw_vfs::concat(self::parse_url($url,PHP_URL_PATH),'../'.$lpath); } //self::symlinkCache_add($path,egw_vfs::PREFIX.$lpath); $url = egw_vfs::PREFIX.egw_vfs::concat($lpath,$rel_path); @@ -1029,7 +1029,7 @@ class vfs_stream_wrapper implements iface_stream_wrapper if (isset(self::$symlink_cache[$path])) return; // nothing to do - if ($target[0] != '/') $target = parse_url($target,PHP_URL_PATH); + if ($target[0] != '/') $target = self::parse_url($target,PHP_URL_PATH); self::$symlink_cache[$path] = $target; @@ -1229,26 +1229,78 @@ class vfs_stream_wrapper implements iface_stream_wrapper return str_replace('.','_',$scheme).'_stream_wrapper'; } + /** + * Utf-8 save version of parse_url + * + * Does caching withing request, to not have to parse urls over and over again. + * + * @param string $url + * @param int $component =-1 PHP_URL_* constants + * @return array|string|boolean on success array or string, if $component given, or false on failure + */ + static function parse_url($url, $component=-1) + { + static $component2str = array( + PHP_URL_SCHEME => 'scheme', + PHP_URL_HOST => 'host', + PHP_URL_PORT => 'port', + PHP_URL_USER => 'user', + PHP_URL_PASS => 'pass', + PHP_URL_PATH => 'path', + PHP_URL_QUERY => 'query', + PHP_URL_FRAGMENT => 'fragment', + ); + static $cache = array(); // some caching + + $result =& $cache[$url]; + + if (!isset($result)) + { + // Build arrays of values we need to decode before parsing + static $entities = array('%21', '%2A', '%27', '%28', '%29', '%3B', '%3A', '%40', '%26', '%3D', '%24', '%2C', '%2F', '%3F', '%23', '%5B', '%5D'); + static $replacements = array('!', '*', "'", "(", ")", ";", ":", "@", "&", "=", "$", ",", "/", "?", "#", "[", "]"); + static $str_replace = null; + if (!isset($str_replace)) $str_replace = function_exists('mb_str_replace') ? 'mb_str_replace' : 'str_replace'; + + // Create encoded URL with special URL characters decoded so it can be parsed + // All other characters will be encoded + $encodedURL = $str_replace($entities, $replacements, urlencode($url)); + + // Parse the encoded URL + $result = $encodedParts = parse_url($encodedURL); + + // Now, decode each value of the resulting array + if ($encodedParts) + { + $result = array(); + foreach ($encodedParts as $key => $value) + { + $result[$key] = urldecode($str_replace($replacements, $entities, $value)); + } + } + } + return $component >= 0 ? $result[$component2str[$component]] : $result; + } + /** * Getting the path from an url (or path) AND removing trailing slashes * * @param string $path url or path (might contain trailing slash from WebDAV!) - * @param string $only_remove_scheme=self::SCHEME if given only that scheme get's removed + * @param string $only_remove_scheme =self::SCHEME if given only that scheme get's removed * @return string path without training slash */ static protected function get_path($path,$only_remove_scheme=self::SCHEME) { - $url_parts = parse_url($path); - if ($path[0] != '/' && (!$only_remove_scheme || $url_parts['scheme'] == $only_remove_scheme)) + if ($path[0] != '/' && (!$only_remove_scheme || self::parse_url($path, PHP_URL_SCHEME) == $only_remove_scheme)) { - $path = $url_parts['path']; + $path = self::parse_url($path, PHP_URL_PATH); } // remove trailing slashes eg. added by WebDAV - if ($url_parts['path'] != '/') + if ($path != '/') { - while (substr($path,-1) == '/' && $path != '/') + while (mb_substr($path, -1) == '/' && $path != '/') { - $path = substr($path,0,-1); + $path = mb_substr($path,0,-1); } } return $path; From 93bc0feb16a8fe3723648bf027d0fc073607f750 Mon Sep 17 00:00:00 2001 From: Ralf Becker Date: Sat, 18 Oct 2014 12:17:17 +0000 Subject: [PATCH 67/97] * Filemanager: fix since PHP 5.5.18 not longer working non-ascii chars in filenames, eg. German umlauts or accents --- etemplate/inc/class.vfs_widget.inc.php | 2 +- filemanager/cli.php | 20 +++++++++---------- .../inc/class.filemanager_admin.inc.php | 2 +- filemanager/inc/class.filemanager_ui.inc.php | 4 ++-- 4 files changed, 14 insertions(+), 14 deletions(-) diff --git a/etemplate/inc/class.vfs_widget.inc.php b/etemplate/inc/class.vfs_widget.inc.php index a69b1ba122..948c11b609 100644 --- a/etemplate/inc/class.vfs_widget.inc.php +++ b/etemplate/inc/class.vfs_widget.inc.php @@ -327,7 +327,7 @@ class vfs_widget { if (substr($path,0,6) == '/apps/') { - $path = parse_url(egw_vfs::resolve_url_symlinks($path),PHP_URL_PATH); + $path = egw_vfs::parse_url(egw_vfs::resolve_url_symlinks($path),PHP_URL_PATH); } //Assemble the thumbnail parameters diff --git a/filemanager/cli.php b/filemanager/cli.php index 5b71190eac..3cfbd01d34 100755 --- a/filemanager/cli.php +++ b/filemanager/cli.php @@ -337,7 +337,7 @@ switch($cmd) $mode = $url; // first param is mode $url = array_shift($argv); } - if (parse_url($url,PHP_URL_SCHEME)) load_wrapper($url); // cant use stat or egw_vfs::mode2int otherwise! + if (egw_vfs::parse_url($url,PHP_URL_SCHEME)) load_wrapper($url); // cant use stat or egw_vfs::mode2int otherwise! if (strpos($mode,'+') !== false || strpos($mode,'-') !== false) { @@ -362,7 +362,7 @@ switch($cmd) { $owner = $url; // first param is owner/group $url = array_shift($argv); - if (parse_url($url,PHP_URL_SCHEME)) load_wrapper($url); // we need the header loaded + if (egw_vfs::parse_url($url,PHP_URL_SCHEME)) load_wrapper($url); // we need the header loaded if ($owner == 'root') { $owner = 0; @@ -388,7 +388,7 @@ switch($cmd) $params = array($url,$owner); break; } - if (($scheme = parse_url($url,PHP_URL_SCHEME))) + if (($scheme = egw_vfs::parse_url($url,PHP_URL_SCHEME))) { load_wrapper($url); } @@ -418,7 +418,7 @@ switch($cmd) { if ($argc) { - if (!($name = basename(parse_url($url,PHP_URL_PATH)))) $name = '/'; + if (!($name = basename(egw_vfs::parse_url($url,PHP_URL_PATH)))) $name = '/'; echo "\n$name:\n"; } // separate evtl. query part, to re-add it after the file-name @@ -446,7 +446,7 @@ switch($cmd) { if ($argc) { - echo "\n".basename(parse_url($url,PHP_URL_PATH)).":\n"; + echo "\n".basename(egw_vfs::parse_url($url,PHP_URL_PATH)).":\n"; } fpassthru($f); fclose($f); @@ -469,7 +469,7 @@ switch($cmd) */ function load_wrapper($url) { - $scheme = parse_url($url,PHP_URL_SCHEME); + $scheme = egw_vfs::parse_url($url,PHP_URL_SCHEME); if (!in_array($scheme,stream_get_wrappers())) { @@ -484,7 +484,7 @@ function load_wrapper($url) default: if (!isset($GLOBALS['egw']) && !in_array($scheme,array('smb','imap'))) { - load_egw(parse_url($url,PHP_URL_USER),parse_url($url,PHP_URL_PASS),parse_url($url,PHP_URL_HOST)); + load_egw(egw_vfs::parse_url($url,PHP_URL_USER),egw_vfs::parse_url($url,PHP_URL_PASS),egw_vfs::parse_url($url,PHP_URL_HOST)); } // get eGW's __autoload() function include_once(EGW_API_INC.'/common_functions.inc.php'); @@ -642,7 +642,7 @@ function do_eacl(array $argv) function do_stat($url,$long=false,$numeric=false,$full_path=false,$inode=false) { //echo "do_stat($url,$long,$numeric,$full_path)\n"; - $bname = parse_url($url,PHP_URL_PATH); + $bname = egw_vfs::parse_url($url,PHP_URL_PATH); if (!$full_path) { @@ -761,7 +761,7 @@ function _cp($from,$to,$verbose=false,$perms=false) if (is_dir($to) || !file_exists($to) && is_dir($from) && $mkdir) { - $path = parse_url($from,PHP_URL_PATH); + $path = egw_vfs::parse_url($from,PHP_URL_PATH); if (is_dir($to)) { list($to,$query) = explode('?',$to,2); @@ -841,7 +841,7 @@ function do_lntree($from,$to) function _ln($src, $base, $stat) { //echo "_ln('$src', '$base', ".array2string($stat).")\n"; - $dst = $base.parse_url($src, PHP_URL_PATH); + $dst = $base.egw_vfs::parse_url($src, PHP_URL_PATH); if (is_link($src)) { diff --git a/filemanager/inc/class.filemanager_admin.inc.php b/filemanager/inc/class.filemanager_admin.inc.php index 9941f8ad71..8ceae6b71b 100644 --- a/filemanager/inc/class.filemanager_admin.inc.php +++ b/filemanager/inc/class.filemanager_admin.inc.php @@ -178,7 +178,7 @@ class filemanager_admin extends filemanager_ui 'url' => $url, ); $readonlys["disable[$path]"] = !$this->versioning || !egw_vfs::$is_root || - parse_url($url,PHP_URL_SCHEME) != $this->versioning; + egw_vfs::parse_url($url,PHP_URL_SCHEME) != $this->versioning; } $readonlys['umount[/]'] = $readonlys['umount[/apps]'] = true; // do not allow to unmount / or /apps $readonlys['url'] = !self::$is_setup; diff --git a/filemanager/inc/class.filemanager_ui.inc.php b/filemanager/inc/class.filemanager_ui.inc.php index 26640c907e..3b49a13a1d 100644 --- a/filemanager/inc/class.filemanager_ui.inc.php +++ b/filemanager/inc/class.filemanager_ui.inc.php @@ -1107,7 +1107,7 @@ class filemanager_ui if (($readonlys['uid'] = !egw_vfs::$is_root) && !$content['uid']) $content['ro_uid_root'] = 'root'; // only owner can change group & perms if (($readonlys['gid'] = !$content['is_owner'] || - parse_url(egw_vfs::resolve_url($content['path']),PHP_URL_SCHEME) == 'oldvfs')) // no uid, gid or perms in oldvfs + egw_vfs::parse_url(egw_vfs::resolve_url($content['path']),PHP_URL_SCHEME) == 'oldvfs')) // no uid, gid or perms in oldvfs { if (!$content['gid']) $content['ro_gid_root'] = 'root'; foreach($content['perms'] as $name => $value) @@ -1147,7 +1147,7 @@ class filemanager_ui unset($readonlys['tabs']['filemanager.file.eacl']); // --> switch the tab on again foreach($content['eacl'] as &$eacl) { - $eacl['path'] = rtrim(parse_url($eacl['path'],PHP_URL_PATH),'/'); + $eacl['path'] = rtrim(egw_vfs::parse_url($eacl['path'],PHP_URL_PATH),'/'); $readonlys['delete['.$eacl['ino'].'-'.$eacl['owner'].']'] = $eacl['ino'] != $content['ino'] || $eacl['path'] != $content['path'] || !$content['is_owner']; } From 64aa838e9d9130c7dd7c7f37a32d09c62a9f96a2 Mon Sep 17 00:00:00 2001 From: Ralf Becker Date: Sat, 18 Oct 2014 16:30:39 +0000 Subject: [PATCH 68/97] * Mail: import and display of mails failed, if personal part of addresses contains valid encoded utf-8 characters - using now Horde_Mime_Headers::parseHeaders() for headers instead of Mail_mimeDecode, which should be completly replaced with Horde_Mime_Part::parseMessage() - replaced imap_rfc822_parse_adrlist with Horde_Mail_Rfc822::parseAddressList() using static wrapper emailadmin_imapbase::parseAddressList() --- mail/inc/class.mail_activesync.inc.php | 58 +++++++---------- mail/inc/class.mail_compose.inc.php | 88 ++++++++------------------ 2 files changed, 47 insertions(+), 99 deletions(-) diff --git a/mail/inc/class.mail_activesync.inc.php b/mail/inc/class.mail_activesync.inc.php index 6d9ffe6b83..7c1d1ae69a 100644 --- a/mail/inc/class.mail_activesync.inc.php +++ b/mail/inc/class.mail_activesync.inc.php @@ -471,9 +471,8 @@ class mail_activesync implements activesync_plugin_write, activesync_plugin_send $mailObject->MessageID = $message->headers['message-id']; /* the client send garbage sometimes (blackberry->domain\username) // from - $address_array = imap_rfc822_parse_adrlist((get_magic_quotes_gpc()?stripslashes($message->headers['from']):$message->headers['from']),''); - foreach((array)$address_array as $addressObject) { - if ($addressObject->host == '.SYNTAX-ERROR.') continue; + foreach(emailadmin_imapbase::parseAddressList((get_magic_quotes_gpc()?stripslashes($message->headers['from']):$message->headers['from'])) as $addressObject) { + if (!$addressObject->valid) continue; if ($this->debugLevel>0) debugLog("Header Sentmail From: ".array2string($addressObject).' vs. '.array2string($message->headers['from'])); $mailObject->From = $addressObject->mailbox. (!empty($addressObject->host) ? '@'.$addressObject->host : ''); $mailObject->FromName = $addressObject->personal; @@ -481,39 +480,34 @@ class mail_activesync implements activesync_plugin_write, activesync_plugin_send */ // prepare addressee list; moved the adding of addresses to the mailobject down // to - $address_array = imap_rfc822_parse_adrlist((get_magic_quotes_gpc()?stripslashes($message->headers["to"]):$message->headers["to"]),''); - foreach((array)$address_array as $addressObject) { - if ($addressObject->host == '.SYNTAX-ERROR.') continue; + foreach(emailadmin_imapbase::parseAddressList((get_magic_quotes_gpc()?stripslashes($message->headers["to"]):$message->headers["to"])) as $addressObject) { + if (!$addressObject->valid) continue; if ($this->debugLevel>0) debugLog("Header Sentmail To: ".array2string($addressObject) ); //$mailObject->AddAddress($addressObject->mailbox. (!empty($addressObject->host) ? '@'.$addressObject->host : ''),$addressObject->personal); $toMailAddr[] = imap_rfc822_write_address($addressObject->mailbox, $addressObject->host, $addressObject->personal); } // CC - $address_array = imap_rfc822_parse_adrlist((get_magic_quotes_gpc()?stripslashes($message->headers["cc"]):$message->headers["cc"]),''); - foreach((array)$address_array as $addressObject) { - if ($addressObject->host == '.SYNTAX-ERROR.') continue; + foreach(emailadmin_imapbase::parseAddressList((get_magic_quotes_gpc()?stripslashes($message->headers["cc"]):$message->headers["cc"])) as $addressObject) { + if (!$addressObject->valid) continue; if ($this->debugLevel>0) debugLog("Header Sentmail CC: ".array2string($addressObject) ); //$mailObject->AddCC($addressObject->mailbox. (!empty($addressObject->host) ? '@'.$addressObject->host : ''),$addressObject->personal); $ccMailAddr[] = imap_rfc822_write_address($addressObject->mailbox, $addressObject->host, $addressObject->personal); } // BCC - $address_array = imap_rfc822_parse_adrlist((get_magic_quotes_gpc()?stripslashes($message->headers["bcc"]):$message->headers["bcc"]),''); - foreach((array)$address_array as $addressObject) { - if ($addressObject->host == '.SYNTAX-ERROR.') continue; + foreach(emailadmin_imapbase::parseAddressList((get_magic_quotes_gpc()?stripslashes($message->headers["bcc"]):$message->headers["bcc"])) as $addressObject) { + if (!$addressObject->valid) continue; if ($this->debugLevel>0) debugLog("Header Sentmail BCC: ".array2string($addressObject) ); //$mailObject->AddBCC($addressObject->mailbox. (!empty($addressObject->host) ? '@'.$addressObject->host : ''),$addressObject->personal); $bccMailAddr[] = imap_rfc822_write_address($addressObject->mailbox, $addressObject->host, $addressObject->personal); } /* // AddReplyTo - $address_array = imap_rfc822_parse_adrlist((get_magic_quotes_gpc()?stripslashes($message->headers['reply-to']):$message->headers['reply-to']),''); - foreach((array)$address_array as $addressObject) { - if ($addressObject->host == '.SYNTAX-ERROR.') continue; + foreach(emailadmin_imapbase::parseAddressList((get_magic_quotes_gpc()?stripslashes($message->headers['reply-to']):$message->headers['reply-to'])) as $addressObject) { + if (!$addressObject->valid) continue; if ($this->debugLevel>0) debugLog("Header Sentmail REPLY-TO: ".array2string($addressObject) ); $mailObject->AddReplyTo($addressObject->mailbox. (!empty($addressObject->host) ? '@'.$addressObject->host : ''),$addressObject->personal); } */ - $addedfullname = false; // save some headers when forwarding mails (content type & transfer-encoding) $headers = ""; @@ -605,8 +599,7 @@ class mail_activesync implements activesync_plugin_write, activesync_plugin_send $toCount = 0; //error_log(__METHOD__.__LINE__.array2string($toMailAddr)); foreach((array)$toMailAddr as $address) { - $address_array = imap_rfc822_parse_adrlist((get_magic_quotes_gpc()?stripslashes($address):$address),''); - foreach((array)$address_array as $addressObject) { + foreach(emailadmin_imapbase::parseAddressList((get_magic_quotes_gpc()?stripslashes($address):$address)) as $addressObject) { $emailAddress = $addressObject->mailbox. (!empty($addressObject->host) ? '@'.$addressObject->host : ''); if ($ClientSideMeetingRequest === true && $allowSendingInvitations == 'sendifnocalnotif' && calendar_boupdate::email_update_requested($emailAddress,(isset($cSMRMethod)?$cSMRMethod:'REQUEST'))) continue; $mailObject->AddAddress($emailAddress, $addressObject->personal); @@ -615,8 +608,7 @@ class mail_activesync implements activesync_plugin_write, activesync_plugin_send } $ccCount = 0; foreach((array)$ccMailAddr as $address) { - $address_array = imap_rfc822_parse_adrlist((get_magic_quotes_gpc()?stripslashes($address):$address),''); - foreach((array)$address_array as $addressObject) { + foreach(emailadmin_imapbase::parseAddressList((get_magic_quotes_gpc()?stripslashes($address):$address)) as $addressObject) { $emailAddress = $addressObject->mailbox. (!empty($addressObject->host) ? '@'.$addressObject->host : ''); if ($ClientSideMeetingRequest === true && $allowSendingInvitations == 'sendifnocalnotif' && calendar_boupdate::email_update_requested($emailAddress)) continue; $mailObject->AddCC($emailAddress, $addressObject->personal); @@ -625,8 +617,7 @@ class mail_activesync implements activesync_plugin_write, activesync_plugin_send } $bccCount = 0; foreach((array)$bccMailAddr as $address) { - $address_array = imap_rfc822_parse_adrlist((get_magic_quotes_gpc()?stripslashes($address):$address),''); - foreach((array)$address_array as $addressObject) { + foreach(emailadmin_imapbase::parseAddressList((get_magic_quotes_gpc()?stripslashes($address):$address)) as $addressObject) { $emailAddress = $addressObject->mailbox. (!empty($addressObject->host) ? '@'.$addressObject->host : ''); if ($ClientSideMeetingRequest === true && $allowSendingInvitations == 'sendifnocalnotif' && calendar_boupdate::email_update_requested($emailAddress)) continue; $mailObject->AddBCC($emailAddress, $addressObject->personal); @@ -928,8 +919,7 @@ class mail_activesync implements activesync_plugin_write, activesync_plugin_send } if (count($folderArray) > 0) { foreach((array)$bccMailAddr as $address) { - $address_array = imap_rfc822_parse_adrlist((get_magic_quotes_gpc()?stripslashes($address):$address),''); - foreach((array)$address_array as $addressObject) { + foreach(emailadmin_imapbase::parseAddressList((get_magic_quotes_gpc()?stripslashes($address):$address)) as $addressObject) { $emailAddress = $addressObject->mailbox. (!empty($addressObject->host) ? '@'.$addressObject->host : ''); $mailAddr[] = array($emailAddress, $addressObject->personal); } @@ -1122,33 +1112,29 @@ class mail_activesync implements activesync_plugin_write, activesync_plugin_send $mailObject->Subject = $headers['SUBJECT']; $mailObject->MessageID = $headers['MESSAGE-ID']; // from - $address_array = imap_rfc822_parse_adrlist((get_magic_quotes_gpc()?stripslashes($headers['FROM']):$headers['FROM']),''); - foreach((array)$address_array as $addressObject) { + foreach(emailadmin_imapbase::parseAddressList((get_magic_quotes_gpc()?stripslashes($headers['FROM']):$headers['FROM'])) as $addressObject) { //debugLog(__METHOD__.__LINE__.'Address to add (FROM):'.array2string($addressObject)); - if ($addressObject->host == '.SYNTAX-ERROR.') continue; + if (!$addressObject->valid) continue; $mailObject->From = $addressObject->mailbox. (!empty($addressObject->host) ? '@'.$addressObject->host : ''); $mailObject->FromName = $addressObject->personal; //error_log(__METHOD__.__LINE__.'Address to add (FROM):'.array2string($addressObject)); } // to - $address_array = imap_rfc822_parse_adrlist((get_magic_quotes_gpc()?stripslashes($headers['TO']):$headers['TO']),''); - foreach((array)$address_array as $addressObject) { + foreach(emailadmin_imapbase::parseAddressList((get_magic_quotes_gpc()?stripslashes($headers['TO']):$headers['TO'])) as $addressObject) { //debugLog(__METHOD__.__LINE__.'Address to add (TO):'.array2string($addressObject)); - if ($addressObject->host == '.SYNTAX-ERROR.') continue; + if (!$addressObject->valid) continue; $mailObject->AddAddress($addressObject->mailbox. (!empty($addressObject->host) ? '@'.$addressObject->host : ''),$addressObject->personal); } // CC - $address_array = imap_rfc822_parse_adrlist((get_magic_quotes_gpc()?stripslashes($headers['CC']):$headers['CC']),''); - foreach((array)$address_array as $addressObject) { + foreach(emailadmin_imapbase::parseAddressList((get_magic_quotes_gpc()?stripslashes($headers['CC']):$headers['CC'])) as $addressObject) { //debugLog(__METHOD__.__LINE__.'Address to add (CC):'.array2string($addressObject)); - if ($addressObject->host == '.SYNTAX-ERROR.') continue; + if (!$addressObject->valid) continue; $mailObject->AddCC($addressObject->mailbox. (!empty($addressObject->host) ? '@'.$addressObject->host : ''),$addressObject->personal); } // AddReplyTo - $address_array = imap_rfc822_parse_adrlist((get_magic_quotes_gpc()?stripslashes($headers['REPLY-TO']):$headers['REPLY-TO']),''); - foreach((array)$address_array as $addressObject) { + foreach(emailadmin_imapbase::parseAddressList((get_magic_quotes_gpc()?stripslashes($headers['REPLY-TO']):$headers['REPLY-TO'])) as $addressObject) { //debugLog(__METHOD__.__LINE__.'Address to add (ReplyTO):'.array2string($addressObject)); - if ($addressObject->host == '.SYNTAX-ERROR.') continue; + if (!$addressObject->valid) continue; $mailObject->AddReplyTo($addressObject->mailbox. (!empty($addressObject->host) ? '@'.$addressObject->host : ''),$addressObject->personal); } $Header = $Body = ''; // we do not use Header and Body we use the MailObject diff --git a/mail/inc/class.mail_compose.inc.php b/mail/inc/class.mail_compose.inc.php index f2a9644959..93fc3e4245 100644 --- a/mail/inc/class.mail_compose.inc.php +++ b/mail/inc/class.mail_compose.inc.php @@ -1140,9 +1140,7 @@ class mail_compose //error_log(__METHOD__.__LINE__.array2string(array('key'=>$key,'value'=>$value))); $value = htmlspecialchars_decode($value,ENT_COMPAT); $value = str_replace("\"\"",'"',$value); - $address_array = imap_rfc822_parse_adrlist((get_magic_quotes_gpc()?stripslashes($value):$value), ''); - //unset($content[strtolower($destination)]); - foreach((array)$address_array as $addressObject) { + foreach(emailadmin_imapbase::parseAddressList($value) as $addressObject) { if ($addressObject->host == '.SYNTAX-ERROR.') continue; $address = imap_rfc822_write_address($addressObject->mailbox,$addressObject->host,$addressObject->personal); //$address = mail_bo::htmlentities($address, $this->displayCharset); @@ -1491,9 +1489,9 @@ class mail_compose $this->sessionData['messageFolder'] = $_folder; $this->sessionData['isDraft'] = true; foreach((array)$headers['CC'] as $val) { - $rfcAddr=imap_rfc822_parse_adrlist($val, ''); + $rfcAddr=emailadmin_imapbase::parseAddressList($val); $_rfcAddr = $rfcAddr[0]; - if ($_rfcAddr->host=='.SYNTAX-ERROR.') continue; + if (!$_rfcAddr->valid) continue; if($_rfcAddr->mailbox == 'undisclosed-recipients' || (empty($_rfcAddr->mailbox) && empty($_rfcAddr->host)) ) { continue; } @@ -1512,9 +1510,9 @@ class mail_compose $this->sessionData['to'][] = $val; continue; } - $rfcAddr=imap_rfc822_parse_adrlist($val, ''); + $rfcAddr=emailadmin_imapbase::parseAddressList($val); $_rfcAddr = $rfcAddr[0]; - if ($_rfcAddr->host=='.SYNTAX-ERROR.') continue; + if (!$_rfcAddr->valid) continue; if($_rfcAddr->mailbox == 'undisclosed-recipients' || (empty($_rfcAddr->mailbox) && empty($_rfcAddr->host)) ) { continue; } @@ -1528,9 +1526,9 @@ class mail_compose } foreach((array)$headers['REPLY-TO'] as $val) { - $rfcAddr=imap_rfc822_parse_adrlist($val, ''); + $rfcAddr=emailadmin_imapbase::parseAddressList($val); $_rfcAddr = $rfcAddr[0]; - if ($_rfcAddr->host=='.SYNTAX-ERROR.') continue; + if (!$_rfcAddr->valid) continue; if($_rfcAddr->mailbox == 'undisclosed-recipients' || (empty($_rfcAddr->mailbox) && empty($_rfcAddr->host)) ) { continue; } @@ -1544,9 +1542,9 @@ class mail_compose } foreach((array)$headers['BCC'] as $val) { - $rfcAddr=imap_rfc822_parse_adrlist($val, ''); + $rfcAddr=emailadmin_imapbase::parseAddressList($val); $_rfcAddr = $rfcAddr[0]; - if ($_rfcAddr->host=='.SYNTAX-ERROR.') continue; + if (!$_rfcAddr->valid) continue; if($_rfcAddr->mailbox == 'undisclosed-recipients' || (empty($_rfcAddr->mailbox) && empty($_rfcAddr->host)) ) { continue; } @@ -2153,56 +2151,22 @@ class mail_compose } // Expand any mailing lists - foreach(array('to','cc','bcc') as $field) + foreach(array( + 'to' => 'AddAddress', + 'cc' => 'AddCC', + 'bcc' => 'AddBCC', + 'replyto' => 'AddReplyto') as $field => $method) { - $_formData[$field] = self::resolveEmailAddressList($_formData[$field]); - } + if ($field != 'replyto') $_formData[$field] = self::resolveEmailAddressList($_formData[$field]); - foreach((array)$_formData['to'] as $address) { - $address_array = imap_rfc822_parse_adrlist((get_magic_quotes_gpc()?stripslashes($address):$address), ''); - foreach((array)$address_array as $addressObject) { - if ($addressObject->host == '.SYNTAX-ERROR.') continue; - $_emailAddress = $addressObject->mailbox. (!empty($addressObject->host) ? '@'.$addressObject->host : ''); - $emailAddress = $addressObject->mailbox. (!empty($addressObject->host) ? '@'.$mail_bo->idna2->encode($addressObject->host) : ''); - #$emailName = $mail_bo->encodeHeader($addressObject->personal, 'q'); - #$_mailObject->AddAddress($emailAddress, $emailName); - $_mailObject->AddAddress($emailAddress, str_replace(array('@'),' ',($addressObject->personal?$addressObject->personal:$_emailAddress))); - } - } - - foreach((array)$_formData['cc'] as $address) { - $address_array = imap_rfc822_parse_adrlist((get_magic_quotes_gpc()?stripslashes($address):$address),''); - foreach((array)$address_array as $addressObject) { - if ($addressObject->host == '.SYNTAX-ERROR.') continue; - $_emailAddress = $addressObject->mailbox. (!empty($addressObject->host) ? '@'.$addressObject->host : ''); - $emailAddress = $addressObject->mailbox. (!empty($addressObject->host) ? '@'.$mail_bo->idna2->encode($addressObject->host) : ''); - #$emailName = $mail_bo->encodeHeader($addressObject->personal, 'q'); - #$_mailObject->AddCC($emailAddress, $emailName); - $_mailObject->AddCC($emailAddress, str_replace(array('@'),' ',($addressObject->personal?$addressObject->personal:$_emailAddress))); - } - } - - foreach((array)$_formData['bcc'] as $address) { - $address_array = imap_rfc822_parse_adrlist((get_magic_quotes_gpc()?stripslashes($address):$address),''); - foreach((array)$address_array as $addressObject) { - if ($addressObject->host == '.SYNTAX-ERROR.') continue; - $_emailAddress = $addressObject->mailbox. (!empty($addressObject->host) ? '@'.$addressObject->host : ''); - $emailAddress = $addressObject->mailbox. (!empty($addressObject->host) ? '@'.$mail_bo->idna2->encode($addressObject->host) : ''); - #$emailName = $mail_bo->encodeHeader($addressObject->personal, 'q'); - #$_mailObject->AddBCC($emailAddress, $emailName); - $_mailObject->AddBCC($emailAddress, str_replace(array('@'),' ',($addressObject->personal?$addressObject->personal:$_emailAddress))); - } - } - - foreach((array)$_formData['replyto'] as $address) { - $address_array = imap_rfc822_parse_adrlist((get_magic_quotes_gpc()?stripslashes($address):$address),''); - foreach((array)$address_array as $addressObject) { - if ($addressObject->host == '.SYNTAX-ERROR.') continue; - $_emailAddress = $addressObject->mailbox. (!empty($addressObject->host) ? '@'.$addressObject->host : ''); - $emailAddress = $addressObject->mailbox. (!empty($addressObject->host) ? '@'.$addressObject->host : ''); - #$emailName = $mail_bo->encodeHeader($addressObject->personal, 'q'); - #$_mailObject->AddBCC($emailAddress, $emailName); - $_mailObject->AddReplyto($emailAddress, str_replace(array('@'),' ',($addressObject->personal?$addressObject->personal:$_emailAddress))); + foreach((array)$_formData[$field] as $address) + { + foreach(emailadmin_imapbase::parseAddressList($address) as $addressObject) { + if (!$addressObject->valid) continue; + $_emailAddress = $addressObject->mailbox. (!empty($addressObject->host) ? '@'.$addressObject->host : ''); + $emailAddress = $addressObject->mailbox. (!empty($addressObject->host) ? '@'.$mail_bo->idna2->encode($addressObject->host) : ''); + $_mailObject->$method($emailAddress, str_replace(array('@'),' ',($addressObject->personal?$addressObject->personal:$_emailAddress))); + } } } @@ -2491,8 +2455,7 @@ class mail_compose $this->createMessage($mail, $_formData, $identity); $this->sessionData['bcc'] = self::resolveEmailAddressList($this->sessionData['bcc']); foreach((array)$this->sessionData['bcc'] as $address) { - $address_array = imap_rfc822_parse_adrlist((get_magic_quotes_gpc()?stripslashes($address):$address),''); - foreach((array)$address_array as $addressObject) { + foreach(emailadmin_imapbase::parseAddressList($address) as $addressObject) { $emailAddress = $addressObject->mailbox. (!empty($addressObject->host) ? '@'.$addressObject->host : ''); $mailAddr[] = array($emailAddress, $addressObject->personal); } @@ -2768,8 +2731,7 @@ class mail_compose if (count($folder) > 0 || $_formData['to_infolog'] == 'on' || $_formData['to_tracker'] == 'on') { //error_log(__METHOD__.__LINE__.array2string($this->sessionData['bcc'])); foreach((array)$this->sessionData['bcc'] as $address) { - $address_array = imap_rfc822_parse_adrlist((get_magic_quotes_gpc()?stripslashes($address):$address),''); - foreach((array)$address_array as $addressObject) { + foreach(emailadmin_imapbase::parseAddressList($address) as $addressObject) { $emailAddress = $addressObject->mailbox. (!empty($addressObject->host) ? '@'.$addressObject->host : ''); $mailAddr[] = array($emailAddress, $addressObject->personal); } From 8a827671b80cd3c747dbb2279ff2bba82eb05888 Mon Sep 17 00:00:00 2001 From: Ralf Becker Date: Mon, 20 Oct 2014 06:49:23 +0000 Subject: [PATCH 69/97] sending mail was failing after r49065 --- mail/inc/class.mail_compose.inc.php | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/mail/inc/class.mail_compose.inc.php b/mail/inc/class.mail_compose.inc.php index 93fc3e4245..d376d3f415 100644 --- a/mail/inc/class.mail_compose.inc.php +++ b/mail/inc/class.mail_compose.inc.php @@ -1414,9 +1414,9 @@ class mail_compose function generateRFC822Address($_addressObject) { - if(!empty($_addressObject->personal) && !empty($_addressObject->mailbox) && !empty($_addressObject->host)) { + if($_addressObject->personal && $_addressObject->mailbox && $_addressObject->host) { return sprintf('"%s" <%s@%s>', $this->mail_bo->decode_header($_addressObject->personal), $_addressObject->mailbox, $this->mail_bo->decode_header($_addressObject->host,'FORCE')); - } elseif(!empty($_addressObject->mailbox) && !empty($_addressObject->host)) { + } elseif($_addressObject->mailbox && $_addressObject->host) { return sprintf("%s@%s", $_addressObject->mailbox, $this->mail_bo->decode_header($_addressObject->host,'FORCE')); } else { return $this->mail_bo->decode_header($_addressObject->mailbox,true); @@ -1492,7 +1492,7 @@ class mail_compose $rfcAddr=emailadmin_imapbase::parseAddressList($val); $_rfcAddr = $rfcAddr[0]; if (!$_rfcAddr->valid) continue; - if($_rfcAddr->mailbox == 'undisclosed-recipients' || (empty($_rfcAddr->mailbox) && empty($_rfcAddr->host)) ) { + if($_rfcAddr->mailbox == 'undisclosed-recipients' || (!$_rfcAddr->mailbox && !$_rfcAddr->host) ) { continue; } $keyemail=$_rfcAddr->mailbox.'@'.$_rfcAddr->host; @@ -1513,7 +1513,7 @@ class mail_compose $rfcAddr=emailadmin_imapbase::parseAddressList($val); $_rfcAddr = $rfcAddr[0]; if (!$_rfcAddr->valid) continue; - if($_rfcAddr->mailbox == 'undisclosed-recipients' || (empty($_rfcAddr->mailbox) && empty($_rfcAddr->host)) ) { + if($_rfcAddr->mailbox == 'undisclosed-recipients' || (!$_rfcAddr->mailbox && !$_rfcAddr->host) ) { continue; } $keyemail=$_rfcAddr->mailbox.'@'.$_rfcAddr->host; @@ -2163,8 +2163,8 @@ class mail_compose { foreach(emailadmin_imapbase::parseAddressList($address) as $addressObject) { if (!$addressObject->valid) continue; - $_emailAddress = $addressObject->mailbox. (!empty($addressObject->host) ? '@'.$addressObject->host : ''); - $emailAddress = $addressObject->mailbox. (!empty($addressObject->host) ? '@'.$mail_bo->idna2->encode($addressObject->host) : ''); + $_emailAddress = $addressObject->mailbox. ($addressObject->host ? '@'.$addressObject->host : ''); + $emailAddress = $addressObject->mailbox. ($addressObject->host ? '@'.$mail_bo->idna2->encode($addressObject->host) : ''); $_mailObject->$method($emailAddress, str_replace(array('@'),' ',($addressObject->personal?$addressObject->personal:$_emailAddress))); } } @@ -2456,7 +2456,7 @@ class mail_compose $this->sessionData['bcc'] = self::resolveEmailAddressList($this->sessionData['bcc']); foreach((array)$this->sessionData['bcc'] as $address) { foreach(emailadmin_imapbase::parseAddressList($address) as $addressObject) { - $emailAddress = $addressObject->mailbox. (!empty($addressObject->host) ? '@'.$addressObject->host : ''); + $emailAddress = $addressObject->mailbox. ($addressObject->host ? '@'.$addressObject->host : ''); $mailAddr[] = array($emailAddress, $addressObject->personal); } } @@ -2732,7 +2732,7 @@ class mail_compose //error_log(__METHOD__.__LINE__.array2string($this->sessionData['bcc'])); foreach((array)$this->sessionData['bcc'] as $address) { foreach(emailadmin_imapbase::parseAddressList($address) as $addressObject) { - $emailAddress = $addressObject->mailbox. (!empty($addressObject->host) ? '@'.$addressObject->host : ''); + $emailAddress = $addressObject->mailbox. ($addressObject->host ? '@'.$addressObject->host : ''); $mailAddr[] = array($emailAddress, $addressObject->personal); } } From d0208328bc9665a0066dc83c3fcb7ef1cf19d7bf Mon Sep 17 00:00:00 2001 From: Ralf Becker Date: Mon, 20 Oct 2014 09:14:24 +0000 Subject: [PATCH 70/97] * Calendar: fixed planner by category view was showing all categories under "None" --- calendar/inc/class.calendar_uiviews.inc.php | 36 +++++++++++---------- 1 file changed, 19 insertions(+), 17 deletions(-) diff --git a/calendar/inc/class.calendar_uiviews.inc.php b/calendar/inc/class.calendar_uiviews.inc.php index 36f9a420e4..b9155c5d35 100644 --- a/calendar/inc/class.calendar_uiviews.inc.php +++ b/calendar/inc/class.calendar_uiviews.inc.php @@ -613,7 +613,7 @@ class calendar_uiviews extends calendar_ui if ($this->debug > 0) $this->bo->debug_message('uiviews::month(weeks=%1) date=%2',True,$weeks,$this->date); $this->use_time_grid = !$this->cal_prefs['use_time_grid'] || $this->cal_prefs['use_time_grid'] == 'all'; // all views - + // Merge print if($weeks) { @@ -657,7 +657,7 @@ class calendar_uiviews extends calendar_ui $GLOBALS['egw_info']['flags']['app_header'] .= ': '.lang(adodb_date('F',$this->bo->date2ts($this->date))).' '.$this->year; $navHeader = lang(adodb_date('F',$this->bo->date2ts($this->date))).' '.$this->year; - + $days =& $this->bo->search(array( 'start' => $this->first, 'end' => $this->last, @@ -684,13 +684,13 @@ class calendar_uiviews extends calendar_ui $content .= $this->timeGridWidget($this->tagWholeDayOnTop($week),$weeks == 2 ? 30 : 60,200,'',$title,0,$week_start+WEEK_s >= $this->last); } - + $navHeader = '
' .html::a_href(html::image('phpgwapi','left',lang('previous'),$options=' alt="<<"'),array( 'menuaction' => $this->view_menuaction, 'date' => date('Ymd',strtotime("-".$weekNavH, $weeks? $this->first: $this->bo->date2ts($this->date))), )). ' '.$navHeader; - + $navHeader = $navHeader.' '.html::a_href(html::image('phpgwapi','right',lang('next'),$options=' alt=">>"'),array( 'menuaction' => $this->view_menuaction, 'date' => date('Ymd',strtotime("+".$weekNavH, $weeks? $this->first: $this->bo->date2ts($this->date))), @@ -813,7 +813,7 @@ class calendar_uiviews extends calendar_ui $this->last = strtotime("+$days days",$this->first) - 1; $GLOBALS['egw_info']['flags']['app_header'] .= ': '.lang('Four days view').' '.$this->bo->long_date($this->first,$this->last); $navHeader =lang('Four days view').' '.$this->bo->long_date($this->first,$this->last); - } + } else { $wd_start = $this->first = $this->datetime->get_weekday_start($this->year,$this->month,$this->day); @@ -850,18 +850,18 @@ class calendar_uiviews extends calendar_ui # # $class = $class == 'row_on' ? 'th' : 'row_on'; //echo "

weekdaystarts='".$this->cal_prefs['weekdaystarts']."', get_weekday_start($this->year,$this->month,$this->day)=".date('l Y-m-d',$wd_start).", first=".date('l Y-m-d',$this->first)."

\n"; - + $navHeader = '
' .html::a_href(html::image('phpgwapi','left',lang('previous'),$options=' alt="<<"'),array( 'menuaction' => $this->view_menuaction, 'date' => date('Ymd',$this->first-$days*DAY_s), )). ''.$navHeader; - + $navHeader = $navHeader.''.html::a_href(html::image('phpgwapi','right',lang('next'),$options=' alt=">>"'),array( 'menuaction' => $this->view_menuaction, 'date' => date('Ymd',$this->last+$days*DAY_s), )).'
'; - + $merge = $this->merge(); if($merge) { @@ -898,12 +898,12 @@ class calendar_uiviews extends calendar_ui if (count($users) > $headerCounter) { $content .= $navHeader; - } + } } } - + $content = $navHeader.$content; - + if (!$home) { $this->do_header(); @@ -984,7 +984,7 @@ class calendar_uiviews extends calendar_ui unset($holidays); $cols[0] =& $this->timeGridWidget($this->tagWholeDayOnTop($dayEvents),$this->cal_prefs['interval'],450,'','',$owner); - + if (count($users) > 1) { $navHeader = '
' @@ -992,13 +992,13 @@ class calendar_uiviews extends calendar_ui 'menuaction' => $this->view_menuaction, 'date' => date('Ymd',$this->first-1), )). ''.$this->bo->long_date($this->first,0,false,true); - + $navHeader = $navHeader.''.html::a_href(html::image('phpgwapi','right',lang('next'),$options=' alt=">>"'),array( 'menuaction' => $this->view_menuaction, 'date' => date('Ymd',$this->last+1), )).'
'; } - + // only show todo's for a single user if (count($users) == 1 && ($todos = $this->get_todos($todo_label)) !== false) { @@ -1026,7 +1026,7 @@ class calendar_uiviews extends calendar_ui $cols[0] = $navHeader . $cols[0]; echo $cols[0]; } - + } else { @@ -1903,7 +1903,7 @@ class calendar_uiviews extends calendar_ui } // Helps client-side to bind handler to events with specific types tag $resizableHelper = $this->bo->date2string($event['start']). '|' .$this->bo->format_date($event['start'],false) . '|' . $this->cal_prefs['interval'].'|'.$eventTypeTag; - + $html = $indent.'
'.$prefix_icon."\n".$html."\n". @@ -2296,9 +2296,11 @@ class calendar_uiviews extends calendar_ui if (!is_array($cat2sort)) { $cat2sort = array(); + $cat_filter = $this->cat_id ? (array)$this->cat_id : array(); foreach((array)$this->categories->return_sorted_array(0,false,'','','',true) as $data) { - if (in_array($data['parent'], (array)$this->cat_id) || in_array($data['id'], (array)$this->cat_id)) // cat is a direct sub of $this->cat_id + if (in_array($data['parent'], $cat_filter) || in_array($data['id'], $cat_filter) || + !$data['parent'] && !$cat_filter) // cat is a direct sub of $this->cat_id { $cat2sort[$data['id']] = $data['id']; $sort2label[$data['id']] = stripslashes($data['name']); From 96d9f14d1267de002422e43d8bb209e2e9839b3b Mon Sep 17 00:00:00 2001 From: Hadi Nategh Date: Mon, 20 Oct 2014 11:41:59 +0000 Subject: [PATCH 71/97] More progress of commit r49059 --- .../js/et2_extension_nextmatch_controller.js | 5 +- etemplate/templates/default/etemplate2.css | 13 ++- filemanager/js/app.js | 79 ------------------- phpgwapi/js/egw_action/egw_action_dragdrop.js | 33 ++++---- 4 files changed, 35 insertions(+), 95 deletions(-) diff --git a/etemplate/js/et2_extension_nextmatch_controller.js b/etemplate/js/et2_extension_nextmatch_controller.js index 1ba5baed86..2058344f72 100644 --- a/etemplate/js/et2_extension_nextmatch_controller.js +++ b/etemplate/js/et2_extension_nextmatch_controller.js @@ -335,7 +335,10 @@ var et2_nextmatch_controller = et2_dataview_controller.extend(et2_IDataProvider, this.append('
'); }, span); } - return helper; + // As we wanted to have a general defaul helper interface, we return null here and not using customize helper for links + // TODO: Need to decide if we need to create a customized helper interface for links anyway + //return helper; + return null; },true); } if(this._actionLinks.indexOf(drag_action.id) < 0) diff --git a/etemplate/templates/default/etemplate2.css b/etemplate/templates/default/etemplate2.css index 3d7225eee8..13eae7dd60 100644 --- a/etemplate/templates/default/etemplate2.css +++ b/etemplate/templates/default/etemplate2.css @@ -1643,6 +1643,7 @@ div.et2_image_tooltipPopup { div.et2_egw_action_ddHelper { } +/* The last div which shows Ctrl tip to user*/ div.et2_egw_action_ddHelper_tip { position: relative; text-overflow: ellipsis; @@ -1664,10 +1665,20 @@ table.et2_egw_action_ddHelper_row tr { background: none; max-height: 20px; } +/* Apply to all displaied rows in order to get same result*/ table.et2_egw_action_ddHelper_row * { white-space: nowrap !important; + max-height: 15px; + overflow:hidden; + text-overflow: ellipsis; + max-width: 400px; } -span.et2_egw_action_ddHelper_itemCnt { +/* Last row of items which shows the number of more items*/ +tr.et2_egw_action_ddHelper_moreRow{ + +} +/* The big total item counter*/ +span.et2_egw_action_ddHelper_itemsCnt { background: transparent; position: absolute; left: 20px; diff --git a/filemanager/js/app.js b/filemanager/js/app.js index b75bbeaaa7..17d4044517 100644 --- a/filemanager/js/app.js +++ b/filemanager/js/app.js @@ -612,85 +612,6 @@ app.classes.filemanager = AppJS.extend( return false; }, - /** - * Get drag helper, called on drag start - * - * @param {egwAction} _action - * @param {array} _elems - * @return some dome objects - */ - drag: function(_action, _elems) - { - var icons = []; - for (var i = 0; i < _elems.length; i++) - { - var data = egw.dataGetUIDdata(_elems[i].id); - var src = egw.mime_icon(data.data.mime, data.data.path); - - if (_elems[i].getFocused()) - { - icons.unshift(src); - } - else - { - icons.push(src); - } - } - - // Only take a maximum of 10 icons - var maxCnt = 10; - - var div = $j(document.createElement("div")) - .css({ - position: 'absolute', - top: '0px', - left: '0px', - width: '300px' - }); - - var lastIcon = ""; - var idx = 0; - - for (var i = 0; i < icons.length; i++) - { - if (icons[i] != lastIcon) - { - lastIcon = icons[i]; - - // Create a stack of images - var img = $j(document.createElement('img')); - img.css({ - position: 'absolute', - 'z-index': 10000-i, - top: idx*5, - left: idx*5, - opacity: (maxCnt - idx) / maxCnt - }); - img.attr('src', icons[i]); - div.append(img); - - idx++; - if (idx == maxCnt) - { - break; - } - } - } - var text = $j(document.createElement('div')).css({left: '30px', position: 'absolute'}); - // add filename or number of files for multiple files - text.text(_elems.length > 1 ? _elems.length+' '+this.egw.lang('files') : this.basename(_elems[0].id)); - div.append(text); - - // Add notice of Ctrl key, if supported - if(window.FileReader && 'draggable' in document.createElement('span') && - navigator && navigator.userAgent.indexOf('Chrome') >= 0) - { - var key = ["Mac68K","MacPPC","MacIntel"].indexOf(window.navigator.platform) < 0 ? 'Ctrl' : 'Command'; - text.append('
' + this.egw.lang('Hold %1 to drag files to your computer',key)); - } - return div; - }, - /** * Change readonly state for given directory * diff --git a/phpgwapi/js/egw_action/egw_action_dragdrop.js b/phpgwapi/js/egw_action/egw_action_dragdrop.js index 4fce6ee152..a3ac2ae195 100644 --- a/phpgwapi/js/egw_action/egw_action_dragdrop.js +++ b/phpgwapi/js/egw_action/egw_action_dragdrop.js @@ -84,22 +84,15 @@ function egwDragActionImplementation() ai.selected = []; // Define default helper DOM + // default helper also can be called later in application code in order to customization ai.defaultDDHelper = function (_selected) { // Table containing clone of rows var table = $j(document.createElement("table")).addClass('egwGridView_grid et2_egw_action_ddHelper_row'); // tr element to use as last row to show lable more ... - var moreRow = $j(document.createElement('tr')).addClass('et2_egw_action_ddHelper_tip'); + var moreRow = $j(document.createElement('tr')).addClass('et2_egw_action_ddHelper_moreRow'); // Main div helper container var div = $j(document.createElement("div")).append(table); - // Lable to show number of items - var spanCnt = $j(document.createElement('span')) - .addClass('et2_egw_action_ddHelper_itemsCnt') - .appendTo(div); - - // TODO: get the right drag item next to the number - var itemsLabel = ''; - spanCnt.text(_selected.length + itemsLabel); var rows = []; // Maximum number of rows to show @@ -117,14 +110,26 @@ function egwDragActionImplementation() index++; if (index == maxRows) { + // Lable to show number of items + var spanCnt = $j(document.createElement('span')) + .addClass('et2_egw_action_ddHelper_itemsCnt') + .appendTo(div); + + // TODO: get the right drag item next to the number + var itemLabel = ''; + spanCnt.text(_selected.length + itemLabel); + var restRows = _selected.length - maxRows; - if (restRows) moreRow.text((_selected.length - maxRows) +' '+egw.lang('more selected ...')); + if (restRows) + { + moreRow.text((_selected.length - maxRows) +' '+egw.lang('more %1 selected ...', itemLabel)); + } table.append(moreRow); break; } } - var text = $j(document.createElement('div')).addClass('et2_egw_action_ddHelper_textArea'); + var text = $j(document.createElement('div')).addClass('et2_egw_action_ddHelper_tip'); div.append(text); // Add notice of Ctrl key, if supported @@ -132,9 +137,9 @@ function egwDragActionImplementation() navigator && navigator.userAgent.indexOf('Chrome') >= 0) { var key = ["Mac68K","MacPPC","MacIntel"].indexOf(window.navigator.platform) < 0 ? 'Ctrl' : 'Command'; - - text.text(egw.lang('Hold %1 to drag %2 to your computer',key, itemsLabel)); + text.text(egw.lang('Hold %1 to drag %2 to your computer',key, itemLabel)); } + // Final html DOM return as helper structor return div; } @@ -288,7 +293,7 @@ function egwDragActionImplementation() } // Return an empty div if the helper dom node is not set - return $j(document.createElement("div")).addClass('et2_egw_action_ddHelper'); + return ai.defaultDDHelper(ai.selected);//$j(document.createElement("div")).addClass('et2_egw_action_ddHelper'); }, "start": function(e) { return ai.helper != null; From fb35fdb8c2cee91e626e8c519b11b82730b02763 Mon Sep 17 00:00:00 2001 From: Hadi Nategh Date: Mon, 20 Oct 2014 12:25:43 +0000 Subject: [PATCH 72/97] Fix cc, bcc fileds, sent from AB to an open compose dialog, overflow the others --- mail/js/app.js | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/mail/js/app.js b/mail/js/app.js index d1d7a59796..71a4205d22 100644 --- a/mail/js/app.js +++ b/mail/js/app.js @@ -190,7 +190,7 @@ app.classes.mail = AppJS.extend( case 'mail.compose': var that = this; this.mail_isMainWindow = false; - this.compose_fieldExpander_hide(); + this.compose_fieldExpander_init(); // Set autosaving interval to 2 minutes for compose message window.setInterval(function (){ @@ -200,7 +200,7 @@ app.classes.mail = AppJS.extend( /* Control focus actions on subject to handle expanders properly.*/ jQuery("#mail-compose_subject").on({ focus:function(){ - that.compose_fieldExpander_hide(); + that.compose_fieldExpander_init(); that.compose_fieldExpander(); } }); @@ -581,6 +581,7 @@ app.classes.mail = AppJS.extend( if (content['cc'] || content['bcc']) { this.compose_fieldExpander(); + this.compose_fieldExpander_init(); } return success; }, @@ -3617,10 +3618,10 @@ app.classes.mail = AppJS.extend( }, /** - * Hide Folder, Cc and Bcc rows from the compose popup - * -Only fields which have no content should get hidden + * Set expandable fields (Folder, Cc and Bcc) based on their content + * - Only fields which have no content should get hidden */ - compose_fieldExpander_hide: function () + compose_fieldExpander_init: function () { var widgets = { cc:{ From 6529bed2a65e9e3e6b5121a190e1343308e0fcd6 Mon Sep 17 00:00:00 2001 From: Hadi Nategh Date: Mon, 20 Oct 2014 12:58:47 +0000 Subject: [PATCH 73/97] * Addressbook: Fix tab order between zip code and city in AB edit dialog --- addressbook/templates/default/edit.xet | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/addressbook/templates/default/edit.xet b/addressbook/templates/default/edit.xet index 6fc186aac6..d700332ab1 100644 --- a/addressbook/templates/default/edit.xet +++ b/addressbook/templates/default/edit.xet @@ -234,7 +234,7 @@ - + From 9b05ccc4add07eab203a9f8166601948a9b732fb Mon Sep 17 00:00:00 2001 From: Hadi Nategh Date: Mon, 20 Oct 2014 13:27:44 +0000 Subject: [PATCH 74/97] Show both date and time for before today time. - Fix mails in the list not showing date and time under date column. --- etemplate/js/et2_widget_date.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/etemplate/js/et2_widget_date.js b/etemplate/js/et2_widget_date.js index db4db4d288..0f7a1ca22d 100644 --- a/etemplate/js/et2_widget_date.js +++ b/etemplate/js/et2_widget_date.js @@ -815,10 +815,11 @@ var et2_date_ro = et2_valueWidget.extend([et2_IDetachedDOM], { display = date(this.egw().preference('timeformat') == '24' ? 'H:i' : 'g:i a', this.date); } - // Before today - just the date + // Before today - date and time else { - display = date(this.egw().preference('dateformat'), this.date); + display = date(this.egw().preference('dateformat') + " " + + (this.egw().preference('timeformat') == '24' ? 'H:i' : 'g:i a'), this.date); } break; case "date": From f61c898b86e5d30363da5b6f7d85a8c88e1c60ba Mon Sep 17 00:00:00 2001 From: Hadi Nategh Date: Mon, 20 Oct 2014 14:07:38 +0000 Subject: [PATCH 75/97] Style images properly located inside drag's helper row --- etemplate/templates/default/etemplate2.css | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/etemplate/templates/default/etemplate2.css b/etemplate/templates/default/etemplate2.css index 13eae7dd60..3287347533 100644 --- a/etemplate/templates/default/etemplate2.css +++ b/etemplate/templates/default/etemplate2.css @@ -1673,6 +1673,10 @@ table.et2_egw_action_ddHelper_row * { text-overflow: ellipsis; max-width: 400px; } +table.et2_egw_action_ddHelper_row img { + width: 15px !important; + height: 15px !important; +} /* Last row of items which shows the number of more items*/ tr.et2_egw_action_ddHelper_moreRow{ From 10eb33cbddfd4c22fca3c774fef6e208d0a10527 Mon Sep 17 00:00:00 2001 From: Hadi Nategh Date: Mon, 20 Oct 2014 15:03:31 +0000 Subject: [PATCH 76/97] Get drag out action working again --- phpgwapi/js/egw_action/egw_action_dragdrop.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/phpgwapi/js/egw_action/egw_action_dragdrop.js b/phpgwapi/js/egw_action/egw_action_dragdrop.js index a3ac2ae195..fb2bf4c694 100644 --- a/phpgwapi/js/egw_action/egw_action_dragdrop.js +++ b/phpgwapi/js/egw_action/egw_action_dragdrop.js @@ -166,7 +166,7 @@ function egwDragActionImplementation() for(var i = 0; i < groups.drag.length; i++) { // dragType 'file' says it can be dragged as a file - if(groups.drag[i].link.actionObj.dragType == 'file') + if(groups.drag[i].link.actionObj.dragType == 'file' || groups.drag[i].link.actionObj.dragType.indexOf('file') > -1) { action = groups.drag[i].link.actionObj; break; From dec861fb3e4a55d93e8b302e1317c86d44f6bd5d Mon Sep 17 00:00:00 2001 From: Ralf Becker Date: Mon, 20 Oct 2014 18:08:03 +0000 Subject: [PATCH 77/97] do not use full path of attachments in compose popup --- mail/inc/class.mail_compose.inc.php | 38 ++++++++++++++++++++--------- mail/js/app.js | 14 +++++------ 2 files changed, 32 insertions(+), 20 deletions(-) diff --git a/mail/inc/class.mail_compose.inc.php b/mail/inc/class.mail_compose.inc.php index d376d3f415..f08c6eaeaf 100644 --- a/mail/inc/class.mail_compose.inc.php +++ b/mail/inc/class.mail_compose.inc.php @@ -1745,25 +1745,34 @@ class mail_compose function getAttachment() { - if(isset($_GET['filename'])) $attachment['filename'] = $_GET['filename']; if(isset($_GET['tmpname'])) $attachment['tmp_name'] = $_GET['tmpname']; if(isset($_GET['name'])) $attachment['name'] = $_GET['name']; //if(isset($_GET['size'])) $attachment['size'] = $_GET['size']; if(isset($_GET['type'])) $attachment['type'] = $_GET['type']; //error_log(__METHOD__.__LINE__.array2string($_GET)); - if (isset($attachment['filename']) && parse_url($attachment['filename'],PHP_URL_SCHEME) == 'vfs') + if (isset($attachment['tmp_name']) && parse_url($attachment['tmp_name'],PHP_URL_SCHEME) == 'vfs') { egw_vfs::load_wrapper('vfs'); + $attachment['attachment'] = file_get_contents($attachment['tmp_name']); + } + // attachment data in temp_dir, only use basename of given name, to not allow path traversal + elseif(!file_exists($tmp_path = $GLOBALS['egw_info']['server']['temp_dir'].SEP.basename($attachment['tmp_name']))) + { + header('HTTP/1.1 404 Not found'); + die('Attachment '.htmlspecialchars($attachment['tmp_name']).' NOT found!'); + } + else + { + $attachment['attachment'] = file_get_contents($tmp_path); } - $attachment['attachment'] = file_get_contents($attachment['tmp_name']); //error_log(__METHOD__.__LINE__.' FileSize:'.filesize($attachment['tmp_name'])); if ($_GET['mode'] != "save") { if (strtoupper($attachment['type']) == 'TEXT/DIRECTORY') { $sfxMimeType = $attachment['type']; - $buff = explode('.',$attachment['filename']); + $buff = explode('.',$attachment['tmp_name']); $suffix = ''; if (is_array($buff)) $suffix = array_pop($buff); // take the last extension to check with ext2mime if (!empty($suffix)) $sfxMimeType = mime_magic::ext2mime($suffix); @@ -1821,12 +1830,10 @@ class mail_compose } } //error_log(__METHOD__.__LINE__.'->'.array2string($attachment)); - $filename = ($attachment['name']?$attachment['name']:($attachment['filename']?$attachment['filename']:$mailbox.'_uid'.$uid.'_part'.$part)); - html::content_header($filename,$attachment['type'],0,True,($_GET['mode'] == "save")); + html::content_header($attachment['name'], $attachment['type'], 0, True, $_GET['mode'] == "save"); echo $attachment['attachment']; - $GLOBALS['egw']->common->egw_exit(); - exit; + common::egw_exit(); } /** @@ -2284,22 +2291,29 @@ class mail_compose break; } - } else { + } + else + { if (isset($attachment['file']) && parse_url($attachment['file'],PHP_URL_SCHEME) == 'vfs') { egw_vfs::load_wrapper('vfs'); + $tmp_path = $attachment['file']; + } + else // non-vfs file has to be in temp_dir + { + $tmp_path = $GLOBALS['egw_info']['server']['temp_dir'].SEP.basename($attachment['file']); } if (isset($attachment['type']) && stripos($attachment['type'],"text/calendar; method=")!==false ) { - $_mailObject->AltExtended = file_get_contents($attachment['file']); + $_mailObject->AltExtended = file_get_contents($tmp_path); $_mailObject->AltExtendedContentType = $attachment['type']; } else { $_mailObject->AddAttachment ( - $attachment['file'], + $tmp_path, $_mailObject->EncodeHeader($attachment['name']), - (strtoupper($attachment['type'])=='MESSAGE/RFC822'?'7bit':'base64'), + strtoupper($attachment['type'])=='MESSAGE/RFC822' ? '7bit' : 'base64', $attachment['type'] ); } diff --git a/mail/js/app.js b/mail/js/app.js index 71a4205d22..30f593afc6 100644 --- a/mail/js/app.js +++ b/mail/js/app.js @@ -2363,7 +2363,6 @@ app.classes.mail = AppJS.extend( case 'TEXT/CALENDAR': case 'TEXT/X-VCALENDAR': url += 'menuaction=mail.mail_compose.getAttachment'; // todo compose for Draft folder - url += '&filename='+attgrid.file; url += '&tmpname='+attgrid.tmp_name; url += '&name='+attgrid.name; //url += '&size='+attgrid.size; @@ -2408,7 +2407,6 @@ app.classes.mail = AppJS.extend( */ default: url += 'menuaction=mail.mail_compose.getAttachment'; // todo compose for Draft folder - url += '&filename='+attgrid.file; url += '&tmpname='+attgrid.tmp_name; url += '&name='+attgrid.name; //url += '&size='+attgrid.size; @@ -2860,10 +2858,10 @@ app.classes.mail = AppJS.extend( messages['all'] = _allMessagesChecked; if (messages['all']=='cancel') return false; if (messages['all']) messages['activeFilters'] = this.mail_getActiveFilters(_action); - - // Make sure a default target folder is set in case of drop target is parent 0 (mail account name) + + // Make sure a default target folder is set in case of drop target is parent 0 (mail account name) if (!target.match(/::/g)) target += '::INBOX'; - + var self = this; egw.json('mail.mail_ui.ajax_copyMessages',[target, messages, 'move'], function(){self.unlock_tree();}) .sendRequest(); @@ -3636,15 +3634,15 @@ app.classes.mail = AppJS.extend( widget:{}, jQClass: '.mailComposeJQueryFolder' }}; - + for(var widget in widgets) { var expanderBtn = widget + '_expander'; widgets[widget].widget = this.et2.getWidgetById(widget); // Add expander button widget to the widgets object widgets[expanderBtn] = {widget:this.et2.getWidgetById(expanderBtn)}; - - if (typeof widgets[widget].widget != 'undefined' + + if (typeof widgets[widget].widget != 'undefined' && typeof widgets[expanderBtn].widget != 'undefined' && widgets[widget].widget.get_value().length == 0) { From f3e099174d72b55b0583f70aa5c49de093070107 Mon Sep 17 00:00:00 2001 From: Nathan Gray Date: Mon, 20 Oct 2014 18:17:31 +0000 Subject: [PATCH 78/97] Add filter (change #5) to show linked files, and files from linked entries. --- filemanager/inc/class.filemanager_ui.inc.php | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/filemanager/inc/class.filemanager_ui.inc.php b/filemanager/inc/class.filemanager_ui.inc.php index 3b49a13a1d..616883ed24 100644 --- a/filemanager/inc/class.filemanager_ui.inc.php +++ b/filemanager/inc/class.filemanager_ui.inc.php @@ -463,7 +463,7 @@ class filemanager_ui '2' => 'Directories sorted in', '3' => 'Show hidden files', '4' => 'All subdirectories', - '5' => 'Files from next subdirectory', + '5' => 'Files from links', '0' => 'Files from subdirectories', ); @@ -793,12 +793,13 @@ class filemanager_ui 'mindepth' => 1, 'maxdepth' => $maxdepth, 'dirsontop' => $filter <= 1, - 'type' => $filter && $filter != 5 ? ($filter == 4 ? 'd' : null) : 'f', + 'type' => $filter && $filter != 5 ? ($filter == 4 ? 'd' : null) : ($filter == 5 ? 'F':'f'), 'order' => $query['order'], 'sort' => $query['sort'], 'limit' => (int)$query['num_rows'].','.(int)$query['start'], 'need_mime' => true, 'name_preg' => $namefilter, 'hidden' => $filter == 3, + 'follow' => $filter == 5, ),true) as $path => $row) { //echo $path; _debug_array($row); From 1df5500d28d720653632811385e7a4893410d2c8 Mon Sep 17 00:00:00 2001 From: Ralf Becker Date: Tue, 21 Oct 2014 08:58:08 +0000 Subject: [PATCH 79/97] * Admin/LDAP: LDAP extra attributes homedirector and loginshell were not stored (home set to /dev/null) --- admin/inc/class.admin_account.inc.php | 13 ++++++++++- admin/js/app.js | 2 +- admin/templates/default/account.xet | 2 +- phpgwapi/inc/class.common.inc.php | 32 ++++++++++++++++++--------- 4 files changed, 35 insertions(+), 14 deletions(-) diff --git a/admin/inc/class.admin_account.inc.php b/admin/inc/class.admin_account.inc.php index 2b5a6e3515..ec301a4b53 100644 --- a/admin/inc/class.admin_account.inc.php +++ b/admin/inc/class.admin_account.inc.php @@ -135,7 +135,8 @@ class admin_account 'changepassword', 'anonymous', 'mustchangepassword', 'account_passwd', 'account_passwd2', 'account_primary_group', - 'account_expires' + 'account_expires', + 'homedirectory', 'loginshell', ) as $c_name => $a_name) { if (is_int($c_name)) $c_name = $a_name; @@ -265,6 +266,16 @@ class admin_account if (!$data['account_lid'] && !$data['account_id']) return; // makes no sense to check before + // set home-directory when account_lid is entered, but only for new accounts + if ($changed == 'account_lid' && !$data['account_id'] && + $GLOBALS['egw_info']['server']['ldap_extra_attributes'] && + $GLOBALS['egw_info']['server']['ldap_account_home']) + { + egw_json_response::get()->assign('addressbook-edit_homedirectory', 'value', + $GLOBALS['egw_info']['server']['ldap_account_home'].'/'.preg_replace('/[^a-z0-9_.-]/i', '', + common::transliterate($data['account_lid']))); + } + // set dummy membership to get no error about no members yet $data['account_memberships'] = array($data['account_primary_user'] = $GLOBALS['egw_info']['user']['account_primary_group']); diff --git a/admin/js/app.js b/admin/js/app.js index f04bb558d1..0d6f17f268 100644 --- a/admin/js/app.js +++ b/admin/js/app.js @@ -800,7 +800,7 @@ app.classes.admin = AppJS.extend( var _buttons = [ {"button_id": "delete[cancel]","text": this.egw.lang('Cancel'), id: 'delete[cancel]', image: 'cancel', "default":true}, {"button_id": "delete[delete]","text": this.egw.lang('Delete'), id: 'delete[delete]', image: 'delete'}, - {"button_id": "delete[subs]","text": this.egw.lang('Delete including sub-enteries'), id: 'delete[subs]', image: 'delete'}, + {"button_id": "delete[subs]","text": this.egw.lang('Delete including sub-enteries'), id: 'delete[subs]', image: 'delete'} ]; var action = _action; var self = this; diff --git a/admin/templates/default/account.xet b/admin/templates/default/account.xet index 12e87e1381..0b0b091603 100644 --- a/admin/templates/default/account.xet +++ b/admin/templates/default/account.xet @@ -46,7 +46,7 @@ - + diff --git a/phpgwapi/inc/class.common.inc.php b/phpgwapi/inc/class.common.inc.php index 61d47c63b3..b69e6a81bd 100644 --- a/phpgwapi/inc/class.common.inc.php +++ b/phpgwapi/inc/class.common.inc.php @@ -1202,6 +1202,25 @@ class common return $h12.':'.$min.$sec.$ampm; } + /** + * convert all european special chars to ascii + * + * @param string $str + * @return string + */ + public static function transliterate($str) + { + static $extra = array( + 'ß' => 'ss', + ' ' => '', + ); + $entities = htmlentities($str, ENT_QUOTES,translation::charset()); + $extra_replaced = str_replace(array_keys($extra),array_values($extra),$entities); + $umlauts = preg_replace('/&([aAuUoO])uml;/','\\1e',$extra_replaced); // replace german umlauts with the letter plus one 'e' + $accents = preg_replace('/&([a-zA-Z])(grave|acute|circ|ring|cedil|tilde|slash|uml);/','\\1',$umlauts); // remove all types of acents + return preg_replace('/&([a-zA-Z]+|#[0-9]+|);/','',$accents); // remove all other entities + } + /** * Format an email address according to the system standard * @@ -1210,24 +1229,15 @@ class common * @param string $first firstname * @param string $last lastname * @param string $account account-name (lid) - * @param string $domain=null domain-name or null to use eGW's default domain $GLOBALS['egw_info']['server']['mail_suffix] + * @param string $domain =null domain-name or null to use eGW's default domain $GLOBALS['egw_info']['server']['mail_suffix] * @return string with email address */ static function email_address($first,$last,$account,$domain=null) { //echo "

common::email_address('$first','$last','$account')"; - // convert all european special chars to ascii, (c) RalfBecker-AT-egroupware.org ;-) - static $extra = array( - 'ß' => 'ss', - ' ' => '', - ); foreach (array('first','last','account') as $name) { - $entities = htmlentities($$name,ENT_QUOTES,translation::charset()); - $extra_replaced = str_replace(array_keys($extra),array_values($extra),$entities); - $umlauts = preg_replace('/&([aAuUoO])uml;/','\\1e',$extra_replaced); // replace german umlauts with the letter plus one 'e' - $accents = preg_replace('/&([a-zA-Z])(grave|acute|circ|ring|cedil|tilde|slash|uml);/','\\1',$umlauts); // remove all types of acents - $$name = preg_replace('/&([a-zA-Z]+|#[0-9]+|);/','',$accents); // remove all other entities + $$name = self::transliterate($$name); } //echo " --> ('$first', '$last', '$account')"; if (!$first && !$last) // fallback to the account-name, if real names contain only special chars From 23c0285825a18694273c6abbb03266583d856e10 Mon Sep 17 00:00:00 2001 From: Hadi Nategh Date: Tue, 21 Oct 2014 08:58:33 +0000 Subject: [PATCH 80/97] Make sure the this object is avaliable before addressing it. -Fix error in calender views which was happening after dnd --- phpgwapi/js/jsapi/egw_json.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/phpgwapi/js/jsapi/egw_json.js b/phpgwapi/js/jsapi/egw_json.js index aceae865fc..6292e72c37 100644 --- a/phpgwapi/js/jsapi/egw_json.js +++ b/phpgwapi/js/jsapi/egw_json.js @@ -407,7 +407,7 @@ egw.extend('json', egw.MODULE_WND_LOCAL, function(_app, _wnd) { egw_topWindow().location.href = res.data.url; } // json request was originating from a different window --> redirect that one - else if(this.DOMContainer && this.DOMContainer.ownerDocument.defaultView != window) + else if(this && this.DOMContainer && this.DOMContainer.ownerDocument.defaultView != window) { this.DOMContainer.ownerDocument.location.href = res.data.url; } From 5391fd16742e5bcc22d2c598d84d83340ce0d40e Mon Sep 17 00:00:00 2001 From: Ralf Becker Date: Tue, 21 Oct 2014 09:15:42 +0000 Subject: [PATCH 81/97] only show apps available to user --- .../inc/class.preferences_settings.inc.php | 24 +++++++++---------- 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/preferences/inc/class.preferences_settings.inc.php b/preferences/inc/class.preferences_settings.inc.php index 566027753b..7bc9d10f93 100644 --- a/preferences/inc/class.preferences_settings.inc.php +++ b/preferences/inc/class.preferences_settings.inc.php @@ -36,8 +36,8 @@ class preferences_settings /** * Edit preferences * - * @param array $content=null - * @param string $msg='' + * @param array $content =null + * @param string $msg ='' */ function index(array $content=null, $msg='') { @@ -155,14 +155,14 @@ class preferences_settings } $sel_options = $readonlys = null; - $content = $this->get_content($appname, $type, $sel_options, $readonlys, $preserve['types'], $tpl); - $preserve['appname'] = $preserve['old_appname'] = $content['appname']; - $preserve['type'] = $preserve['old_type'] = $content['type']; - if (isset($old_tab)) $content['tabs'] = $old_tab; + $data = $this->get_content($appname, $type, $sel_options, $readonlys, $preserve['types'], $tpl); + $preserve['appname'] = $preserve['old_appname'] = $data['appname']; + $preserve['type'] = $preserve['old_type'] = $data['type']; + if (isset($old_tab)) $data['tabs'] = $old_tab; if ($msg) egw_framework::message($msg, $msg_type ? $msg_type : 'error'); - $tpl->exec('preferences.preferences_settings.index', $content, $sel_options, $readonlys, $preserve, 2); + $tpl->exec('preferences.preferences_settings.index', $data, $sel_options, $readonlys, $preserve, 2); } /** @@ -173,7 +173,7 @@ class preferences_settings * @param array $types setting-name => type * @param string $appname appname or 'common' * @param string $type 'user', 'default', 'forced' - * @param boolean $only_verify=false + * @param boolean $only_verify =false * @return string with verification error or null on success */ function process_array(array &$repository, array $values, array $types, $appname, $type, $only_verify=false) @@ -455,7 +455,7 @@ class preferences_settings $sel_options['appname'] = array(); foreach($GLOBALS['egw']->hooks->hook_implemented('settings') as $app) { - if ($app != 'preferences' && $GLOBALS['egw_info']['apps'][$app]) + if ($app != 'preferences' && $GLOBALS['egw_info']['user']['apps'][$app]) { $sel_options['appname'][$app] = $GLOBALS['egw_info']['apps'][$app]['title']; } @@ -499,7 +499,7 @@ class preferences_settings * * @param string|array $default default value(s) to get label for * @param array $values values optional including optgroups - * @param boolean $lang=true + * @param boolean $lang =true * @return string comma-separated and translated labels */ protected static function get_default_label($default, array $values, $lang=true) @@ -543,8 +543,8 @@ class preferences_settings * Sets $this->appname and $this->settings * * @param string $appname appname or 'common' - * @param string $type='user' 'default', 'forced', 'user' or 'group' - * @param int|string $account_id=null account_id for user or group prefs, or "forced" or "default" + * @param string $type ='user' 'default', 'forced', 'user' or 'group' + * @param int|string $account_id =null account_id for user or group prefs, or "forced" or "default" * @return boolean */ protected function call_hook($appname, $type='user', $account_id=null) From c1c4b7c68246c6bb42c44b9e1989a090bb76ce4c Mon Sep 17 00:00:00 2001 From: Hadi Nategh Date: Tue, 21 Oct 2014 11:48:25 +0000 Subject: [PATCH 82/97] Replace the drag out trigger key from Ctrl (or Command key in Mac) to Shift key. - Make Ctrl key as a standard dnd action disabling, and being able to select content by holding Ctrl key. --- phpgwapi/js/egw_action/egw_action_dragdrop.js | 150 ++++++++++-------- 1 file changed, 81 insertions(+), 69 deletions(-) diff --git a/phpgwapi/js/egw_action/egw_action_dragdrop.js b/phpgwapi/js/egw_action/egw_action_dragdrop.js index fb2bf4c694..78204cb996 100644 --- a/phpgwapi/js/egw_action/egw_action_dragdrop.js +++ b/phpgwapi/js/egw_action/egw_action_dragdrop.js @@ -132,12 +132,11 @@ function egwDragActionImplementation() var text = $j(document.createElement('div')).addClass('et2_egw_action_ddHelper_tip'); div.append(text); - // Add notice of Ctrl key, if supported + // Add notice of Shift key, if supported if('draggable' in document.createElement('span') && navigator && navigator.userAgent.indexOf('Chrome') >= 0) { - var key = ["Mac68K","MacPPC","MacIntel"].indexOf(window.navigator.platform) < 0 ? 'Ctrl' : 'Command'; - text.text(egw.lang('Hold %1 to drag %2 to your computer',key, itemLabel)); + text.text(egw.lang('Hold Shift to drag %1 to your computer', itemLabel)); } // Final html DOM return as helper structor return div; @@ -174,93 +173,106 @@ function egwDragActionImplementation() } if(action) { + // As we are planning to remove this trigger key for drag out action in the future, and as + // we have a standard key (ctrl or command key in Mac) in nm context menu to be able to + // disable the context menu, therefore, we will make the Ctrl key a standard key to disabling dd actions too, + // which means user also would be able to select content by holding ctrl key. /** - * We found an action with dragType 'file', so by holding Ctrl + * We found an action with dragType 'file', so by holding Shift * key & dragging, user can drag from browser to system. * The global data store must provide a full, absolute URL in 'download_url' * and a mime in 'mime'. * * Unfortunately, Native DnD to drag the file conflicts with jQueryUI draggable, * which handles all the other DnD actions. We get around this by: - * 1. Require the user indicate a file drag with Ctrl key + * 1. Require the user indicate a file drag with Shift key * 2. Disable jQueryUI draggable, then turn on native draggable attribute * This way we can at least toggle which one is operating, so they * both work alternately if not together. */ - // Native DnD - Doesn't play nice with jQueryUI Sortable - // Tell jQuery to include this property - jQuery.event.props.push('dataTransfer'); + // Native DnD - Doesn't play nice with jQueryUI Sortable + // Tell jQuery to include this property + jQuery.event.props.push('dataTransfer'); - $j(node).off("mousedown") - .on("mousedown", function(event) { - $j(node).draggable("option","disabled",event.ctrlKey || event.metaKey); - $j(this).attr("draggable", event.ctrlKey || event.metaKey ? "true" : ""); + $j(node).off("mousedown") + .on("mousedown", function(event) { + $j(node).draggable("option","disabled",event.shiftKey || event.metaKey || event.ctrlKey); + $j(this).attr("draggable", event.shiftKey || event.metaKey ? "true" : ""); - // Disabling draggable adds some UI classes, but we don't care so remove them - $j(node).removeClass("ui-draggable-disabled ui-state-disabled"); - if(!(event.ctrlKey || event.metaKey) || !this.addEventListener) return; - }) - .on("dragstart", function(event) { - if(event.dataTransfer == null) { - return; - } - event.dataTransfer.effectAllowed="copy"; + // Disabling draggable adds some UI classes, but we don't care so remove them + $j(node).removeClass("ui-draggable-disabled ui-state-disabled"); + if(!(event.shiftKey || event.metaKey) || !this.addEventListener) return; + }) + .on("dragstart", function(event) { + if(event.dataTransfer == null) { + return; + } + event.dataTransfer.effectAllowed="copy"; - // Get all selected - // Multiples aren't supported by event.dataTransfer, yet, so - // select only the row they clicked on. - // var selected = _context.getSelectedLinks('drag'); - var selected = [_context]; - _context.parent.setAllSelected(false); - _context.setSelected(true); + // Get all selected + // Multiples aren't supported by event.dataTransfer, yet, so + // select only the row they clicked on. + // var selected = _context.getSelectedLinks('drag'); + var selected = [_context]; + _context.parent.setAllSelected(false); + _context.setSelected(true); - // Set file data - for(var i = 0; i < selected.length; i++) - { - var data = selected[i].data || egw.dataGetUIDdata(selected[i].id).data || {}; - if(data && data.mime && data.download_url) + // Set file data + for(var i = 0; i < selected.length; i++) { - var url = data.download_url; - - // NEED an absolute URL - if (url[0] == '/') url = egw.link(url); - // egw.link adds the webserver, but that might not be an absolute URL - try again - if (url[0] == '/') url = window.location.origin+url; - - // Unfortunately, dragging files is currently only supported by Chrome - if(navigator && navigator.userAgent.indexOf('Chrome')) + var data = selected[i].data || egw.dataGetUIDdata(selected[i].id).data || {}; + if(data && data.mime && data.download_url) { - event.dataTransfer.setData("DownloadURL", data.mime+':'+data.name+':'+url); - } - else - { - // Include URL as a fallback - event.dataTransfer.setData("text/uri-list", url); + var url = data.download_url; + + // NEED an absolute URL + if (url[0] == '/') url = egw.link(url); + // egw.link adds the webserver, but that might not be an absolute URL - try again + if (url[0] == '/') url = window.location.origin+url; + + // Unfortunately, dragging files is currently only supported by Chrome + if(navigator && navigator.userAgent.indexOf('Chrome')) + { + event.dataTransfer.setData("DownloadURL", data.mime+':'+data.name+':'+url); + } + else + { + // Include URL as a fallback + event.dataTransfer.setData("text/uri-list", url); + } } } - } - if(event.dataTransfer.types.length == 0) - { - // No file data? Abort: drag does nothing - event.preventDefault(); - return; - } + if(event.dataTransfer.types.length == 0) + { + // No file data? Abort: drag does nothing + event.preventDefault(); + return; + } - // Create drag icon - _callback.call(_context, _context, ai); - // Drag icon must be visible for setDragImage() - we'll remove it on drag - $j("body").append(ai.helper); - event.dataTransfer.setDragImage(ai.helper[0],-12,-12); - }) - .on("drag", function(e) { - // Remove the helper, it has been copied into the dataTransfer object now - // Hopefully user didn't notice it... - if(e.dataTransfer != null) - { - ai.helper.remove(); - } - }); + // Create drag icon + _callback.call(_context, _context, ai); + // Drag icon must be visible for setDragImage() - we'll remove it on drag + $j("body").append(ai.helper); + event.dataTransfer.setDragImage(ai.helper[0],-12,-12); + }) + .on("drag", function(e) { + // Remove the helper, it has been copied into the dataTransfer object now + // Hopefully user didn't notice it... + if(e.dataTransfer != null) + { + ai.helper.remove(); + } + }); } + else + { + //Disable dragging to be able to select content of item by holding Ctrl then selecting content + $j(node).off("mousedown") + .on("mousedown", function(event) { + $j(node).draggable("option","disabled",event.ctrlKey || event.metaKey); + }); + } + $j(node).draggable( { "distance": 20, From b6f69d3a4c162931d8937c6009ad75842ca1a7e7 Mon Sep 17 00:00:00 2001 From: Ralf Becker Date: Tue, 21 Oct 2014 12:17:00 +0000 Subject: [PATCH 83/97] "No filters" in InfoLog was not removing a search --- infolog/js/app.js | 1 + 1 file changed, 1 insertion(+) diff --git a/infolog/js/app.js b/infolog/js/app.js index 5b79d2fcce..d544b337a3 100644 --- a/infolog/js/app.js +++ b/infolog/js/app.js @@ -151,6 +151,7 @@ app.classes.infolog = AppJS.extend( setState: function(state) { if (typeof state.state.action == 'undefined') state.state.action = null; + if (typeof state.state.search == 'undefined') state.state.search = null; return this._super.apply(this, arguments); }, From 549431fb736e34a5877cf39b3683f07b315f60f3 Mon Sep 17 00:00:00 2001 From: Hadi Nategh Date: Tue, 21 Oct 2014 13:35:19 +0000 Subject: [PATCH 84/97] Revert commit r49090, because it breaks special ctrl key and click handling in nm. Need more investigation to solve it --- phpgwapi/js/egw_action/egw_action_dragdrop.js | 150 ++++++++---------- 1 file changed, 69 insertions(+), 81 deletions(-) diff --git a/phpgwapi/js/egw_action/egw_action_dragdrop.js b/phpgwapi/js/egw_action/egw_action_dragdrop.js index 78204cb996..fb2bf4c694 100644 --- a/phpgwapi/js/egw_action/egw_action_dragdrop.js +++ b/phpgwapi/js/egw_action/egw_action_dragdrop.js @@ -132,11 +132,12 @@ function egwDragActionImplementation() var text = $j(document.createElement('div')).addClass('et2_egw_action_ddHelper_tip'); div.append(text); - // Add notice of Shift key, if supported + // Add notice of Ctrl key, if supported if('draggable' in document.createElement('span') && navigator && navigator.userAgent.indexOf('Chrome') >= 0) { - text.text(egw.lang('Hold Shift to drag %1 to your computer', itemLabel)); + var key = ["Mac68K","MacPPC","MacIntel"].indexOf(window.navigator.platform) < 0 ? 'Ctrl' : 'Command'; + text.text(egw.lang('Hold %1 to drag %2 to your computer',key, itemLabel)); } // Final html DOM return as helper structor return div; @@ -173,106 +174,93 @@ function egwDragActionImplementation() } if(action) { - // As we are planning to remove this trigger key for drag out action in the future, and as - // we have a standard key (ctrl or command key in Mac) in nm context menu to be able to - // disable the context menu, therefore, we will make the Ctrl key a standard key to disabling dd actions too, - // which means user also would be able to select content by holding ctrl key. /** - * We found an action with dragType 'file', so by holding Shift + * We found an action with dragType 'file', so by holding Ctrl * key & dragging, user can drag from browser to system. * The global data store must provide a full, absolute URL in 'download_url' * and a mime in 'mime'. * * Unfortunately, Native DnD to drag the file conflicts with jQueryUI draggable, * which handles all the other DnD actions. We get around this by: - * 1. Require the user indicate a file drag with Shift key + * 1. Require the user indicate a file drag with Ctrl key * 2. Disable jQueryUI draggable, then turn on native draggable attribute * This way we can at least toggle which one is operating, so they * both work alternately if not together. */ - // Native DnD - Doesn't play nice with jQueryUI Sortable - // Tell jQuery to include this property - jQuery.event.props.push('dataTransfer'); + // Native DnD - Doesn't play nice with jQueryUI Sortable + // Tell jQuery to include this property + jQuery.event.props.push('dataTransfer'); - $j(node).off("mousedown") - .on("mousedown", function(event) { - $j(node).draggable("option","disabled",event.shiftKey || event.metaKey || event.ctrlKey); - $j(this).attr("draggable", event.shiftKey || event.metaKey ? "true" : ""); + $j(node).off("mousedown") + .on("mousedown", function(event) { + $j(node).draggable("option","disabled",event.ctrlKey || event.metaKey); + $j(this).attr("draggable", event.ctrlKey || event.metaKey ? "true" : ""); - // Disabling draggable adds some UI classes, but we don't care so remove them - $j(node).removeClass("ui-draggable-disabled ui-state-disabled"); - if(!(event.shiftKey || event.metaKey) || !this.addEventListener) return; - }) - .on("dragstart", function(event) { - if(event.dataTransfer == null) { - return; - } - event.dataTransfer.effectAllowed="copy"; + // Disabling draggable adds some UI classes, but we don't care so remove them + $j(node).removeClass("ui-draggable-disabled ui-state-disabled"); + if(!(event.ctrlKey || event.metaKey) || !this.addEventListener) return; + }) + .on("dragstart", function(event) { + if(event.dataTransfer == null) { + return; + } + event.dataTransfer.effectAllowed="copy"; - // Get all selected - // Multiples aren't supported by event.dataTransfer, yet, so - // select only the row they clicked on. - // var selected = _context.getSelectedLinks('drag'); - var selected = [_context]; - _context.parent.setAllSelected(false); - _context.setSelected(true); + // Get all selected + // Multiples aren't supported by event.dataTransfer, yet, so + // select only the row they clicked on. + // var selected = _context.getSelectedLinks('drag'); + var selected = [_context]; + _context.parent.setAllSelected(false); + _context.setSelected(true); - // Set file data - for(var i = 0; i < selected.length; i++) + // Set file data + for(var i = 0; i < selected.length; i++) + { + var data = selected[i].data || egw.dataGetUIDdata(selected[i].id).data || {}; + if(data && data.mime && data.download_url) { - var data = selected[i].data || egw.dataGetUIDdata(selected[i].id).data || {}; - if(data && data.mime && data.download_url) + var url = data.download_url; + + // NEED an absolute URL + if (url[0] == '/') url = egw.link(url); + // egw.link adds the webserver, but that might not be an absolute URL - try again + if (url[0] == '/') url = window.location.origin+url; + + // Unfortunately, dragging files is currently only supported by Chrome + if(navigator && navigator.userAgent.indexOf('Chrome')) { - var url = data.download_url; - - // NEED an absolute URL - if (url[0] == '/') url = egw.link(url); - // egw.link adds the webserver, but that might not be an absolute URL - try again - if (url[0] == '/') url = window.location.origin+url; - - // Unfortunately, dragging files is currently only supported by Chrome - if(navigator && navigator.userAgent.indexOf('Chrome')) - { - event.dataTransfer.setData("DownloadURL", data.mime+':'+data.name+':'+url); - } - else - { - // Include URL as a fallback - event.dataTransfer.setData("text/uri-list", url); - } + event.dataTransfer.setData("DownloadURL", data.mime+':'+data.name+':'+url); + } + else + { + // Include URL as a fallback + event.dataTransfer.setData("text/uri-list", url); } } - if(event.dataTransfer.types.length == 0) - { - // No file data? Abort: drag does nothing - event.preventDefault(); - return; - } + } + if(event.dataTransfer.types.length == 0) + { + // No file data? Abort: drag does nothing + event.preventDefault(); + return; + } - // Create drag icon - _callback.call(_context, _context, ai); - // Drag icon must be visible for setDragImage() - we'll remove it on drag - $j("body").append(ai.helper); - event.dataTransfer.setDragImage(ai.helper[0],-12,-12); - }) - .on("drag", function(e) { - // Remove the helper, it has been copied into the dataTransfer object now - // Hopefully user didn't notice it... - if(e.dataTransfer != null) - { - ai.helper.remove(); - } - }); + // Create drag icon + _callback.call(_context, _context, ai); + // Drag icon must be visible for setDragImage() - we'll remove it on drag + $j("body").append(ai.helper); + event.dataTransfer.setDragImage(ai.helper[0],-12,-12); + }) + .on("drag", function(e) { + // Remove the helper, it has been copied into the dataTransfer object now + // Hopefully user didn't notice it... + if(e.dataTransfer != null) + { + ai.helper.remove(); + } + }); } - else - { - //Disable dragging to be able to select content of item by holding Ctrl then selecting content - $j(node).off("mousedown") - .on("mousedown", function(event) { - $j(node).draggable("option","disabled",event.ctrlKey || event.metaKey); - }); - } - $j(node).draggable( { "distance": 20, From af048109ee546ab8d4ac56cb5449e599332d5899 Mon Sep 17 00:00:00 2001 From: Ralf Becker Date: Tue, 21 Oct 2014 13:51:37 +0000 Subject: [PATCH 85/97] adding singular and plural name of app entries to link registry under "entry"/"entries" plus translations for apps which need them different from app-name --- addressbook/inc/class.addressbook_hooks.inc.php | 2 ++ addressbook/lang/egw_de.lang | 5 +---- addressbook/lang/egw_en.lang | 1 + calendar/inc/class.calendar_hooks.inc.php | 2 ++ calendar/lang/egw_de.lang | 3 ++- calendar/lang/egw_en.lang | 2 ++ filemanager/inc/class.filemanager_hooks.inc.php | 2 ++ filemanager/lang/egw_de.lang | 3 ++- filemanager/lang/egw_en.lang | 3 ++- phpgwapi/inc/class.egw_link.inc.php | 4 +++- phpgwapi/js/jsapi/egw_links.js | 3 ++- 11 files changed, 21 insertions(+), 9 deletions(-) diff --git a/addressbook/inc/class.addressbook_hooks.inc.php b/addressbook/inc/class.addressbook_hooks.inc.php index ba6c46d960..76dc999a78 100644 --- a/addressbook/inc/class.addressbook_hooks.inc.php +++ b/addressbook/inc/class.addressbook_hooks.inc.php @@ -383,6 +383,8 @@ class addressbook_hooks ), ), 'merge' => true, + 'entry' => 'Contact', + 'entries' => 'Contacts', ); return $links; } diff --git a/addressbook/lang/egw_de.lang b/addressbook/lang/egw_de.lang index 9aa897255b..1bd003e292 100644 --- a/addressbook/lang/egw_de.lang +++ b/addressbook/lang/egw_de.lang @@ -105,7 +105,6 @@ charset of file addressbook de Zeichensatz der Datei check all addressbook de Alle auswählen choose an icon for this contact type admin de Wählen Sie ein Icon für diesen Kontakt Typ choose charset addressbook de Zeichensatz auswählen -choose export options addressbook de Wählen Sie die Export Optionen choose owner of imported data addressbook de Wählen Sie den Besitzer der importierten Daten chosse an etemplate for this contact type admin de Wählen Sie ein eTemplate für diesen Kontakt Typ city common de Stadt @@ -130,6 +129,7 @@ contact repository admin de Speicherort Kontakte contact saved addressbook de Kontakt gespeichert contact settings admin de Kontakt Einstellungen contactform addressbook de Kontaktformular +contacts common de Kontakte contacts and account contact-data to ldap admin de Kontakte und Kontaktdaten der Benutzer nach LDAP contacts and account contact-data to sql admin de Kontakte und Kontaktdaten der Benutzer nach SQL contacts to ldap admin de Kontakte nach LDAP @@ -369,7 +369,6 @@ phone numbers common de Telefonnummern photo addressbook de Foto please enter a name for that field ! addressbook de Bitte geben Sie einen Namen für das Feld an! please select only one category addressbook de Bitte nur eine Kategorie auswählen -please update the templatename in your customfields section! addressbook de Passen Sie bitte in der Sektion "Benutzerdefinierte Felder" Ihren Templatenamen an! postal common de Postanschrift pref addressbook de bevorzugt preferred email address to use in distribution lists addressbook de Bevorzugte E-Mailadresse für Verteilerlisten @@ -461,7 +460,6 @@ type addressbook de Typ un-delete addressbook de Gelöschte Adressen wieder herstellen unable to convert "%1" to account id. using plugin setting (%2) for owner. addressbook de Umwandelung von "%1" zu Benutzer ID nicht möglich. Einstellung des Plug-ins (%2) wird für diesen Benutzer verwendet. unable to delete addressbook de Löschen nicht möglich -unable to import into %1, using %2 addressbook de Import in %1 nicht möglich wenn %2 verwendet wird unique id (uid) addressbook de Eindeutige ID (UID) unique id
(to update existing records) addressbook de Eindeutige ID
(um existierende Datensätze zu aktualisieren) update a single entry by passing the fields. addressbook de Aktualisiert einen einzelnen Eintrag durch Übergabe seiner Felder. @@ -486,7 +484,6 @@ verification addressbook de Verifikation view linked infolog entries addressbook de Verknüpfte InfoLog Einträge anzeigen warning!! ldap is valid only if you are not using contacts for accounts storage! admin de WARNUNG!! LDAP darf nur verwendet werden, wenn sie die Benutzerkonten nicht im Adressbuch speichern! warning: all contacts found will be deleted! addressbook de WARNUNG: Alle gefundenen Kontakte werden gelöscht ! -warning: template "%1" not found, using default template instead. addressbook de WARNUNG: Template "%1" nicht gefunden, das Standard-Template wird stattdessen benutzt. weekday addressbook de Wochentag what should links to the addressbook display in other applications. empty values will be left out. you need to log in anew, if you change this setting! addressbook de Was sollen Verknüpfungen zum Adressbuch in anderen Anwendungen anzeigen. Leere Werte werden ausgelassen. Sie müssen sich neu anmelden, wenn Sie hier eine Änderung vornehmen! when viewing a contact, show linked entries from the selected application addressbook de Bei der Ansicht eine Kontaktes werden nur Links zu folgenden Anwendungen geziegt diff --git a/addressbook/lang/egw_en.lang b/addressbook/lang/egw_en.lang index c5c01e7b95..a0c6cab547 100644 --- a/addressbook/lang/egw_en.lang +++ b/addressbook/lang/egw_en.lang @@ -129,6 +129,7 @@ contact repository admin en Contact repository contact saved addressbook en Contact saved contact settings admin en Contact settings contactform addressbook en Contact form +contacts common en Contacts contacts and account contact-data to ldap admin en Contacts and account contact data to LDAP contacts and account contact-data to sql admin en Contacts and account contact data to SQL contacts to ldap admin en Contacts to LDAP diff --git a/calendar/inc/class.calendar_hooks.inc.php b/calendar/inc/class.calendar_hooks.inc.php index ec1d682360..0dfa10e8d7 100644 --- a/calendar/inc/class.calendar_hooks.inc.php +++ b/calendar/inc/class.calendar_hooks.inc.php @@ -50,6 +50,8 @@ class calendar_hooks ), ), 'merge' => true, + 'entry' => 'Event', + 'entries' => 'Events', ); } diff --git a/calendar/lang/egw_de.lang b/calendar/lang/egw_de.lang index 9d7dbcdfcf..760535acb9 100644 --- a/calendar/lang/egw_de.lang +++ b/calendar/lang/egw_de.lang @@ -197,11 +197,13 @@ error: saving the event !!! calendar de Fehler beim Speichern des Termins !!! error: starttime has to be before the endtime !!! calendar de Fehler: Startzeit muss vor Endzeit liegen !!! error: the entry has been updated since you opened it for editing! calendar de Fehler: Der Eintrag wurde geändert seit Sie ihn zum Bearbeiten geöffnet haben! error: you can't shift a series from the past! calendar de Eine abgelaufene Serie kann nicht mehr verschoben werden! +event common de Termin event copied - the copy can now be edited calendar de Termin kopiert - die Kopie kann jetzt bearbeitet werden event deleted calendar de Termin gelöscht event details follow calendar de Details zum Termin folgen event saved calendar de Termin gespeichert event will occupy the whole day calendar de Termin nimmt den ganzen Tag ein +events common de Termine every user can invite other users and groups admin de Jeder Benutzer kann andere Benutzer und Gruppen einladen example {{if n_prefix~mr~hello mr.~hello ms.}} - search the field "n_prefix", for "mr", if found, write hello mr., else write hello ms. calendar de Beispiel: "{{IF n_prefix~Herr~Sehr geehrter~Sehr geehrte}}" - suche in dem Feld "n_prefix" nach "Herr", wenn gefunden, schreibe "Sehr geehrter", wenn nicht gefunden schreibe "Sehr geehrte". Es ist auch möglich anstatt fixer Werte, den Wert eines andren Feldes zu übernehmen. Beispiel (Land wird nur dann angezeigt, denn es nicht DEUTSCHLAND ist: } example {{letterprefixcustom n_prefix title n_family}} - example: mr dr. james miller calendar de Beispiel für {{LETTERPREFIXCUSTOM n_prefix title n_family}} - Beispiel: Herr Dr. James Miller @@ -305,7 +307,6 @@ last changed calendar de letzte Änderung lastname of person to notify calendar de Nachname der zu benachrichtigenden Person length of the time interval calendar de Länge des Zeitintervalls limit number of description lines in list view (default 5, 0 for no limit) calendar de Anzahl Zeilen der Beschreibung in der Listenansicht (voreingestellt 5, 0 für alle) -link calendar de Termin-URL link to view the event calendar de Verweis (Weblink) um den Termin anzuzeigen links calendar de Verknüpfungen links and attached files calendar de Verknüpfungen zu anderen Datensätzen und angefügten Dateien diff --git a/calendar/lang/egw_en.lang b/calendar/lang/egw_en.lang index 368fcde3ba..d2492dfb65 100644 --- a/calendar/lang/egw_en.lang +++ b/calendar/lang/egw_en.lang @@ -197,11 +197,13 @@ error: saving the event !!! calendar en Error saving the event! error: starttime has to be before the endtime !!! calendar en Error: Start time has to be before the end time !!! error: the entry has been updated since you opened it for editing! calendar en Error: The entry has been updated since you opened it for editing! error: you can't shift a series from the past! calendar en Error: You can't shift a series from the past! +event common en Event event copied - the copy can now be edited calendar en Event copied - the copy can now be edited. event deleted calendar en Event deleted. event details follow calendar en Event details follow event saved calendar en Event saved. event will occupy the whole day calendar en Event will occupy the whole day +events common en Events every user can invite other users and groups admin en Every user can invite other users and groups example {{if n_prefix~mr~hello mr.~hello ms.}} - search the field "n_prefix", for "mr", if found, write hello mr., else write hello ms. calendar en Example {{IF n_prefix~Mr~Hello Mr.~Hello Ms.}} - search the field "n_prefix", for "Mr", if found, write Hello Mr., else write Hello Ms. example {{letterprefixcustom n_prefix title n_family}} - example: mr dr. james miller calendar en Example {{LETTERPREFIXCUSTOM n_prefix title n_family}} - Example: Mr Dr. James Miller diff --git a/filemanager/inc/class.filemanager_hooks.inc.php b/filemanager/inc/class.filemanager_hooks.inc.php index 9f30f926ab..18b89e301f 100644 --- a/filemanager/inc/class.filemanager_hooks.inc.php +++ b/filemanager/inc/class.filemanager_hooks.inc.php @@ -276,6 +276,8 @@ class filemanager_hooks ), ), 'merge' => true, + 'entry' => 'File', + 'entries' => 'Files', ); } } diff --git a/filemanager/lang/egw_de.lang b/filemanager/lang/egw_de.lang index 962f21400d..115c95f17b 100644 --- a/filemanager/lang/egw_de.lang +++ b/filemanager/lang/egw_de.lang @@ -116,7 +116,7 @@ extended acl filemanager de Erweiterte Zugriffsrechte failed to change permissions of %1! filemanager de Konnte Zugriffsrechte von %1 nicht ändern! failed to create directory! filemanager de Konnte Verzeichnis nicht anlegen! favorites filemanager de Favoriten -file filemanager de Datei +file common de Datei file %1 already exists filemanager de Es gibt schon eine Datei %1 file %1 could not be created. filemanager de Die Datei %1 konnte nicht erzeugt werden file %1 may be too big. contact your systemadministrator for further info filemanager de Die Datei %1 ist eventuell zu gross. Kontaktieren Sie Ihren Systemadministrator für weiterreichende Informationen. @@ -128,6 +128,7 @@ filemanager common de Dateimanager filemanager configuration admin de Konfiguration Dateimanager filemanager fields: filemanager de Dateimanager Felder: filename must not be empty! filemanager de Dateinamen darf nicht leer sein! +files common de Dateien files from subdirectories filemanager de Dateien aus Unterverzeichnissen files in this directory filemanager de Dateien in diesem Verzeichnis filesystem check reported no problems. filemanager de Überprüfung des Dateisystem ergab keine Probleme. diff --git a/filemanager/lang/egw_en.lang b/filemanager/lang/egw_en.lang index 584e351b23..3035012ced 100644 --- a/filemanager/lang/egw_en.lang +++ b/filemanager/lang/egw_en.lang @@ -116,7 +116,7 @@ extended acl filemanager en Extended ACL failed to change permissions of %1! filemanager en Failed to change permissions of %1! failed to create directory! filemanager en Failed to create directory! favorites filemanager en Favorites -file filemanager en File +file common en File file %1 already exists filemanager en File %1 already exists file %1 could not be created. filemanager en File %1 could not be created. file %1 may be too big. contact your systemadministrator for further info filemanager en File %1 might be too big. @@ -128,6 +128,7 @@ filemanager common en File Manager filemanager configuration admin en File Manager configuration filemanager fields: filemanager en Filemanager fields: filename must not be empty! filemanager en File name must not be empty! +files common en Files files from subdirectories filemanager en Files from sub directories files in this directory filemanager en Files in this directory filesystem check reported no problems. filemanager en Filesystem check reported no problems. diff --git a/phpgwapi/inc/class.egw_link.inc.php b/phpgwapi/inc/class.egw_link.inc.php index e6c6db0862..228414e35b 100644 --- a/phpgwapi/inc/class.egw_link.inc.php +++ b/phpgwapi/inc/class.egw_link.inc.php @@ -69,6 +69,8 @@ * 'edit_popup' => '400x300', * 'name' => 'Some name', // Name to use instead of app-name * 'icon' => 'app/icon', // Optional icon to use instead of app-icon + * 'entry' => 'Contact', // Optional name for single entry of app, eg. "contact" used instead of appname + * 'entries' => 'Contacts', // Optional name for multiple entries of app, eg. "contacts" used instead of appname * 'mime' => array( // Optional register mime-types application can open * 'text/something' => array( * 'mime_id' => 'path', // one of id (path) or url is required! @@ -250,7 +252,7 @@ class egw_link extends solink 'edit','edit_id','edit_popup', 'list','list_popup', 'name','icon','query', - 'mime', + 'mime','entry','entries', ))); } } diff --git a/phpgwapi/js/jsapi/egw_links.js b/phpgwapi/js/jsapi/egw_links.js index 06f1df02e9..ed219e303e 100644 --- a/phpgwapi/js/jsapi/egw_links.js +++ b/phpgwapi/js/jsapi/egw_links.js @@ -454,7 +454,8 @@ egw.extend('links', egw.MODULE_GLOBAL, function() var apps = self.link_app_list('add'); for(var app in apps) { - var option = jQuery(document.createElement('option')).attr('value', app).text(self.lang(apps[app])); + var option = jQuery(document.createElement('option')).attr('value', app) + .text(self.lang(self.link_get_registry(app,'entry') || apps[app])); select.append(option); } }); From 510469e60b11041aeb13631a581b41e3152acf77 Mon Sep 17 00:00:00 2001 From: Hadi Nategh Date: Tue, 21 Oct 2014 14:31:18 +0000 Subject: [PATCH 86/97] Implement to set proper lable for dragging item for the drag helper --- phpgwapi/js/egw_action/egw_action_dragdrop.js | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/phpgwapi/js/egw_action/egw_action_dragdrop.js b/phpgwapi/js/egw_action/egw_action_dragdrop.js index fb2bf4c694..fe12d96268 100644 --- a/phpgwapi/js/egw_action/egw_action_dragdrop.js +++ b/phpgwapi/js/egw_action/egw_action_dragdrop.js @@ -97,6 +97,8 @@ function egwDragActionImplementation() var rows = []; // Maximum number of rows to show var maxRows = 3; + // item label + var itemLabel = egw.lang(egw.link_get_registry(egw.app_name(),_selected.length > 1?'entries':'entry')||egw.app_name()); var index = 0; for (var i = 0; i < _selected.length;i++) @@ -114,11 +116,9 @@ function egwDragActionImplementation() var spanCnt = $j(document.createElement('span')) .addClass('et2_egw_action_ddHelper_itemsCnt') .appendTo(div); - - // TODO: get the right drag item next to the number - var itemLabel = ''; - spanCnt.text(_selected.length + itemLabel); + spanCnt.text(_selected.length +' '+ itemLabel); + // Number of not shown rows var restRows = _selected.length - maxRows; if (restRows) { @@ -134,7 +134,7 @@ function egwDragActionImplementation() // Add notice of Ctrl key, if supported if('draggable' in document.createElement('span') && - navigator && navigator.userAgent.indexOf('Chrome') >= 0) + navigator && navigator.userAgent.indexOf('Chrome') >= 0 && egw.app_name() == 'filemanager') // currently only filemanager supports drag out { var key = ["Mac68K","MacPPC","MacIntel"].indexOf(window.navigator.platform) < 0 ? 'Ctrl' : 'Command'; text.text(egw.lang('Hold %1 to drag %2 to your computer',key, itemLabel)); @@ -312,7 +312,8 @@ function egwDragActionImplementation() && helperTop <= (dTarget.height() + dTarget.offset().top) + tipTelorance) { var key = ["Mac68K","MacPPC","MacIntel"].indexOf(window.navigator.platform) < 0 ? 'Ctrl' : 'Command'; - egw.message(egw.lang('Hold %1 key to select content.', key),'info'); + // Comment this out ATM till we get the ctrl and content selection functionality working + //egw.message(egw.lang('Hold %1 key to select content.', key),'info'); } // Invalid target return true; From 0f8015e2b810748b479b51ba99f4baf8d10e44a3 Mon Sep 17 00:00:00 2001 From: Ralf Becker Date: Tue, 21 Oct 2014 14:38:20 +0000 Subject: [PATCH 87/97] entry name for mail app --- mail/inc/class.mail_hooks.inc.php | 2 ++ mail/lang/egw_de.lang | 1 + mail/lang/egw_en.lang | 1 + 3 files changed, 4 insertions(+) diff --git a/mail/inc/class.mail_hooks.inc.php b/mail/inc/class.mail_hooks.inc.php index 99fd95ad1e..fe679ae04d 100644 --- a/mail/inc/class.mail_hooks.inc.php +++ b/mail/inc/class.mail_hooks.inc.php @@ -99,6 +99,8 @@ class mail_hooks 'mime_popup' => '870xegw_getWindowOuterHeight()', ), ), + 'entry' => 'Mail', + 'entries' => 'Mails', ); } diff --git a/mail/lang/egw_de.lang b/mail/lang/egw_de.lang index 336d2a008f..6441dc9503 100644 --- a/mail/lang/egw_de.lang +++ b/mail/lang/egw_de.lang @@ -238,6 +238,7 @@ mail source mail de Nachrichtenquelltext anzeigen mail-address mail de Mail-Adresse mailaccount mail de Mailkonto mailinglist mail de Mailingliste +mails common de Mails mark all as read mail de alle als Gelesen markieren mark as deleted mail de als gelöscht markieren match: mail de Übereinstimmung: diff --git a/mail/lang/egw_en.lang b/mail/lang/egw_en.lang index c47afb66ef..eba425b684 100644 --- a/mail/lang/egw_en.lang +++ b/mail/lang/egw_en.lang @@ -239,6 +239,7 @@ mail source mail en Mail Source mail-address mail en Mail-Address mailaccount mail en Mailaccount mailinglist mail en Mailinglist +mails common en Mails mailserver reported:\n%1 \ndo you want to proceed by deleting the selected messages immediately (click ok)?\nif not, please try to empty your trashfolder before continuing. (click cancel) mail en mailserver reported:\n%1 \ndo you want to proceed by deleting the selected messages immediately (click ok)?\nif not, please try to empty your trashfolder before continuing. (click cancel) mark all as read mail en mark all as read mark as deleted mail en mark as deleted From a86a891e077dbdc949deddb0c3ebf0e47818c502 Mon Sep 17 00:00:00 2001 From: Ralf Becker Date: Tue, 21 Oct 2014 14:47:15 +0000 Subject: [PATCH 88/97] some translations --- phpgwapi/lang/egw_de.lang | 1 + phpgwapi/lang/egw_en.lang | 1 + 2 files changed, 2 insertions(+) diff --git a/phpgwapi/lang/egw_de.lang b/phpgwapi/lang/egw_de.lang index 52d55ab48f..025a42a363 100644 --- a/phpgwapi/lang/egw_de.lang +++ b/phpgwapi/lang/egw_de.lang @@ -373,6 +373,7 @@ height common de Höhe help common de Hilfe high common de Hoch highest common de Höchste +hold %1 to drag %2 to your computer common de %1 Taste halten um %2 auf Ihren Computer zu ziehen holy see (vatican city state) common de VATICAN home common de Home home email common de private E-Mail diff --git a/phpgwapi/lang/egw_en.lang b/phpgwapi/lang/egw_en.lang index ce13c2cbe3..3171771508 100644 --- a/phpgwapi/lang/egw_en.lang +++ b/phpgwapi/lang/egw_en.lang @@ -373,6 +373,7 @@ height common en Height help common en Help high common en High highest common en Highest +hold %1 to drag %2 to your computer common en Hold %1 to drag %2 to your computer holy see (vatican city state) common en HOLY SEE (VATICAN CITY STATE) home common en Home home email common en Home email From 1e9db74ea6444b49766d183ffaccbd3f5d8b2a18 Mon Sep 17 00:00:00 2001 From: Ralf Becker Date: Tue, 21 Oct 2014 14:49:04 +0000 Subject: [PATCH 89/97] pending Dutch translations from our translation server --- addressbook/lang/egw_nl.lang | 64 +++++++++++++++++++++++++++++--- phpgwapi/lang/egw_nl.lang | 71 +++++++++++++++++++++++++++++++++++- 2 files changed, 129 insertions(+), 6 deletions(-) diff --git a/addressbook/lang/egw_nl.lang b/addressbook/lang/egw_nl.lang index e9621491a2..e1e49e1d1e 100644 --- a/addressbook/lang/egw_nl.lang +++ b/addressbook/lang/egw_nl.lang @@ -26,6 +26,7 @@ add a single entry by passing the fields. addressbook nl Voeg een enkele invoer add appointment addressbook nl Voeg een afspraak toe add business email of whole distribution list? addressbook nl Voeg bedrijfsemail of hele distributielijst toe? add custom field addressbook nl Aangepast veld toevoegen +add customfield to links of addressbook, which displays in other applications. the default value is none customfield. addressbook nl Voeg aangepast veld toe aan links van adresboek, welke zichtbaar is in andere applicaties. De standaard waarde is geen aangepast veld. add emails of whole distribution list? addressbook nl Voeg emailadressen van de hele distributie lijst toe? add or delete categories addressbook nl Toevoegen of verwijderen categoriën add to bcc addressbook nl Voeg aan BCc toe @@ -108,6 +109,7 @@ choose owner of imported data addressbook nl Kies de eigenaar van de geimporteer chosse an etemplate for this contact type admin nl Kies een eTemplate voor dit type contact city common nl Stad city (private) addressbook nl Stad (prive) +cleanup addressbook fields (apply if synchronization creates duplicates) addressbook nl Verwijder adresboek velden, toepassen wanneer synchronisatie duplicaten aanmaakt company common nl Bedrijf company name addressbook nl Bedrijfsnaam configuration common nl Instelling @@ -151,6 +153,7 @@ csv-filename addressbook nl CSV-Bestandsnaam custom addressbook nl Aangepast custom etemplate for the contactform addressbook nl Aangepaste eTemplate voor het contactformulier custom fields addressbook nl Aangepast velden +data exchange settings addressbook nl Data exchange instellingen debug output in browser addressbook nl Debug resultaat in browser default addressbook nl standaard default action on double-click addressbook nl Standaard actie bij dubbel klikken @@ -160,6 +163,7 @@ default document to insert contacts addressbook nl Standaard document om contact default file as format addressbook nl Standaard bestand als formaat default filter addressbook nl Standaard filter default format for fileas, eg. for new entries. addressbook nl Standaard formaat voor bestanden, bijvoorbeeld voor nieuwe invoer. +default is to open email addresses in egroupware email application, if user has access to it. addressbook nl Standaard is om EMail (berichten) adressen te openen in EGroupware EMail (berichten) applicatie, als de gebruiker er toegang toe heeft. defines which email address (business or home) to use as the preferred one for distribution lists in mail. addressbook nl Bepaalt welk emailadres (zakelijk of prive) gebruikt moet worden als het voorkeursadres voor distributielijsten in email. delete a single entry by passing the id. addressbook nl Verwijder een enkel record door het ID. te geven delete selected distribution list! addressbook nl Verwijder de geselecteerde distributielijst! @@ -195,33 +199,49 @@ edit phonenumbers addressbook nl Telefoonnummers wijzigen edit phonenumbers - addressbook nl Telefoonnummers wijzigen - either the configured email addesses are wrong or the mail configuration. addressbook nl Of de ingestelde emailadressen zijn onjuist of de email configuratie is onjuist. email & internet addressbook nl Email & Internet +email (private) addressbook nl email (prive) email addresses (comma separated) to send the contact data addressbook nl Emailadressen (kommagescheiden) om de contactgegevens te verzenden empty admin nl Leeg empty for all addressbook nl leeg voor allen enable an extra private addressbook addressbook nl Maak een extra persoonlijk adresboek mogelijk +enclosure addressbook nl Bijgesloten end addressbook nl Einde enter the path to the exported file here addressbook nl Geef folder voor het te exporteren bestand hier aan error deleting the contact !!! addressbook nl Fout bij verwijderen van contact !!! error saving the contact !!! addressbook nl Fout bij bewaren van contact !!! error: the entry has been updated since you opened it for editing! addressbook nl Fout: het record is gewijzigd sinds jij bent begonnen met wijzigen! +example {{if n_prefix~mr~hello mr.~hello ms.}} - search the field "n_prefix", for "mr", if found, write hello mr., else write hello ms. addressbook nl Example {{IF n_prefix~Mr~Hello Mr.~Hello Ms.}} - search the field "n_prefix", for "Mr", if found, write Hello Mr., else write Hello Ms. +example {{letterprefixcustom n_prefix title n_family}} - example: mr dr. james miller preferences nl Example {{LETTERPREFIXCUSTOM n_prefix title n_family}} - Example: Mr Dr. James Miller +example {{nelf role}} - if field role is not empty, you will get a new line with the value of field role addressbook nl Example {{NELF role}} - if field role is not empty, you will get a new line with the value of field role +example {{nelfnv role}} - if field role is not empty, set a lf without any value of the field addressbook nl Example {{NELFNV role}} - if field role is not empty, set a LF without any value of the field existing links addressbook nl Bestaande links +exists addressbook nl Bestaat export as csv addressbook nl Exporteer als CSV export as vcard addressbook nl Exporteer als VCard export contacts addressbook nl Exporteer contacten +export definition to use for nextmatch export addressbook nl Export definitie om te gebruiken bij nextmatch export +export definitition to use for nextmatch export addressbook nl Export definitie om te gebruiken bij nextmatch export export file name addressbook nl Exporteer bestandsnaam export from addressbook addressbook nl Exporteer vanuit adresboek export selection addressbook nl Exporteer selectie exported addressbook nl geexporteerd +exports contacts from your addressbook into a csv file. addressbook nl Exporteer contacten uit adresboek in CSV bestand formaat exports contacts from your addressbook into a csv file. csv means 'comma seperated values'. however in the options tab you can also choose other seperators. addressbook nl Exporteert contacten van jouw adresboek in een CSV bestand. CSV betekent 'Komma gescheiden waarden'. Maar in het Opties tabblad kun je ook andere scheidingstekens kiezen. +exports contacts from your addressbook into a vcard file. addressbook nl Exporteer contacten uit uw adresboek in vCard bestand formaat. extra addressbook nl Extra +extra encodings addressbook nl Extra versleutelen +extra private addressbook nl Extra prive failed to change %1 organisation member(s) (insufficent rights) !!! addressbook nl wijzigen van %1 organisatie lid/leden mislukte (onvoldoende rechten) !!! +favorites addressbook nl Favorieten fax addressbook nl Fax +fax (private) addressbook nl fac (prive) fax number common nl Faxnummer field %1 has been added ! addressbook nl Veld 1% is toegevoegd ! field %1 has been updated ! addressbook nl Veld 1% bijgewerkt ! field name addressbook nl Veldnaam fields for the csv export addressbook nl Velden voor de CSV export fields the user is allowed to edit himself admin nl Velden die de gebruiker zelf mag wijzigen +fields to copy when copying an address? admin nl Velden welke moeten worden gekopieerd als er een adres wordt gekopieerd fields to show in address list addressbook nl Velden die zichtbaar zijn in adreslijst fieldseparator addressbook nl Scheidingssymbool velden for read only ldap admin nl voor alleen-lezen LDAP @@ -230,6 +250,7 @@ freebusy uri addressbook nl Vrij/bezet URI full name addressbook nl Volledige naam general admin nl Algemeen general fields: addressbook nl Algemene velden: +general settings addressbook nl Algemene instellingen geo addressbook nl GEO global categories addressbook nl Algemene categoriën grant addressbook access common nl Geen toegang adresboek @@ -249,8 +270,10 @@ home state addressbook nl Privé Provincie / Staat / Streek home street addressbook nl Privé Straat home zip code addressbook nl Privé Postcode how many contacts should non-admins be able to export admin nl Hoeveel contactpersonen moet een niet-beheerder kunnen exporteren +html link to the current record addressbook nl HTML link naar huidig record icon addressbook nl Icoon if accounts are already in ldap admin nl indien accounts reeds in LDAP zitten +ignore first line addressbook nl Negeer eerste regel import addressbook nl Importeren import contacts addressbook nl Contacten Importeren import csv-file into addressbook addressbook nl CSV-bestand importeren naar adresboek @@ -261,12 +284,16 @@ import from outlook addressbook nl Importeren vanuit Outlook import multiple vcard addressbook nl Importeer Meervoudige VCard import next set addressbook nl Importeren volgende serie import_instructions addressbook nl In Netscape, open het adressenboek en selecteer Exporteren in het menu Bestand. Het bestand wordt geexporteerd in het LDIF formaat.

Of, in Outlook, selecteer de contacten-folder, kies Importeren en Exporteren... in het menu Bestand en exportoor als komma gescheiden tekst bestand (CSV).

Of, in Palm Desktop 4.0 of nieuwer, kies het adresboek en selecteer Exporteren in het menu Bestand, het bestand wordt geëxporteerd in het VCard formaat. +importer's personal addressbook nl Importeer eigen imports contacts into your addressbook from a csv file. csv means 'comma seperated values'. however in the options tab you can also choose other seperators. addressbook nl Importeert contacten in je adresboek vanuit een CSV bestand. CSV betekent 'Komma gescheiden waarden'. Maar in het Opties tabblad kun je ook andere scheidingstekens kiezen. -in %1 days (%2) is %3's birthday. addressbook nl Over %1 dagen (%2) is %3 jarig. -income addressbook nl Inkomen +imports contacts into your addressbook from a vcard file. addressbook nl Importeer contacten in uw adresboek van een bestand in vCard +in %1 days (%2) is %3's birthday. addressbook nl Over %1 dagen (%2) is %3 jarig. +income addressbook nl Inkomend infolog addressbook nl InfoLog +insert addressbook nl Voeg toe insufficent rights to delete this list! addressbook nl Niet genoeg rechten om deze lijst te verwijderen! international addressbook nl Internationaal +internet addressbook nl Internet label addressbook nl Label last date addressbook nl Laatste datum last modified addressbook nl laatste wijziging @@ -278,16 +305,22 @@ ldif addressbook nl LDIF line 2 addressbook nl Regel 2 link title for contacts show addressbook nl Linktitel voor contactenweergave links addressbook nl Links +links and attached files addressbook nl Link naar bijgesloten bestand +links to specified application. example: {{links/infolog}} addressbook nl Verwijst naar een specifieke applicatie. Voorbeeld: {{links/infolog}} list all categories addressbook nl Toon alle categoriën list all customfields addressbook nl Toon alle op maat gemaakte velden list already exists! addressbook nl Lijst bestaat reeds! list created addressbook nl Lijst aangemaakt list creation failed, no rights! addressbook nl Lijst aanmaak is mislukt, geen rechten! +list of files linked to the current record addressbook nl Lijs van bestanden gelinkt aan huidig record load vcard addressbook nl Laad VCard location addressbook nl Locatie locations addressbook nl locaties +mail vcard addressbook nl Mail VCard +main categories in their own field addressbook nl Hoofd categorien in hun eigen veld manage mapping addressbook nl Beheer de mappings mark records as private addressbook nl Markeer records als privé +merge contacts addressbook nl Voeg contacten samen merge into first or account, deletes all other! addressbook nl Samenvoegen in eerste of in account, verwijdert alle andere! merged addressbook nl samengevoegd message after submitting the form addressbook nl Bericht na verzenden van het formulier @@ -296,9 +329,11 @@ middle name addressbook nl Tweede naam migration finished addressbook nl Migratie voltooid migration to ldap admin nl Migratie naar LDAP mobile addressbook nl Mobiel -mobile phone addressbook nl Mobilofoon +mobile phone addressbook nl Mobiele telefoon +mobile phone (private) addressbook nl Mobiele telefoon (prive) modem phone addressbook nl Telefoonmodem more ... addressbook nl Meer ... +move to addressbook addressbook nl Verplaats naar adresboek moved addressbook nl verplaatst multiple vcard addressbook nl Meervoudige VCard name for the distribution list addressbook nl Naam voor de distributielijst @@ -308,9 +343,14 @@ new contact submitted by %1 at %2 addressbook nl Nieuwe contact toegevoegd op %2 new window opened to edit infolog for your selection addressbook nl Nieuw venster geopend waarin de Infolog voor uw selectie bewerkt wordt next date addressbook nl Volgende datum no categories selected addressbook nl geen categorieën geselecteerd +no fallback addressbook nl Geen aktie ongedaan maken no vcard addressbook nl Geen VCard number addressbook nl Nummer/aantal number of records to read (%1) addressbook nl Aantal adressen om te lezen (%1) +open email addresses in external mail program addressbook nl Open EMal adres in extern mail (berichten)programma +open for editing? addressbook nl Open voor bewerken ? +open infolog crm view preferences nl Open Infolog CRM beeld +open tracking system crm view preferences nl Open Tracking systeem CRM beeld options for type admin nl Opties voor type organisation addressbook nl organisatie organisations addressbook nl Organisaties @@ -328,13 +368,14 @@ phone numbers common nl Telefoonnummers photo addressbook nl Foto please enter a name for that field ! addressbook nl Vul een naam voor dat veld in svp ! please select only one category addressbook nl Kies svp slechts één categorie -please update the templatename in your customfields section! addressbook nl Actualiseer svp de template naam in je aangepaste velden sectie! postal common nl Postadres pref addressbook nl voorkeur preferred email address to use in distribution lists addressbook nl Voorkeurs emailadres tbv distributielijsten preferred phone addressbook nl voorkeurstelefoon preferred type of email address to add for distribution lists addressbook nl voorkeurs type van emailadres om toe te voegen aan distributielijsten prefix addressbook nl Voorvoegsel +prevent deleting of contacts admin nl Voorkom het verwijderen van contacten +private custom fields addressbook nl Eigen aangepaste velden public key addressbook nl Public Key publish into groups: addressbook nl Publiceer in groepen: read a list / search for entries. addressbook nl Lees een lijst / zoek naar records. @@ -343,6 +384,9 @@ read a single entry by passing the id and fieldlist. addressbook nl Lees een rec read only addressbook nl alleen-lezen record access addressbook nl Bestandstoegang record owner addressbook nl Bestand eigenaar +recovered addressbook nl Teruggezet +region addressbook nl Regio +remove from distribution list addressbook nl Verwijder van distributielijst remove selected contacts from distribution list addressbook nl Verwijder geselecteerde contacten van de distributielijst removed from distribution list addressbook nl verwijderd van de distributielijst repetition addressbook nl Herhaling @@ -351,11 +395,13 @@ required fields * addressbook nl verplichte velden * role addressbook nl Rol room addressbook nl Kamer search for '%1' addressbook nl Zoek naar '%1' +search letter addressbook nl Zoek letter select a portrait format jpeg photo. it will be resized to 60 pixel width. addressbook nl Kies een portretformaat jpeg foto. Het wordt aangepast tot 60 pixels breedte. select a view addressbook nl Kies een weergave select addressbook type addressbook nl Kies een adresboek type select all addressbook nl Alles selecteren select an action or addressbook to move to addressbook nl Kies een actie of adresboek om te verplaatsen +select an action or addressbook to move to... addressbook nl Selecteer een actie of adresboek om naar te verplaatsen... select migration type admin nl Kies een migratie type select multiple contacts for a further action addressbook nl Kies meerdere contacten voor verdere actie select phone number as prefered way of contact addressbook nl kies een telefoonnummer als een voorkeur tot contact @@ -366,6 +412,7 @@ selected contacts addressbook nl geselecteerde contacten send emailcopy to receiver addressbook nl Zend een kopie mail naar ontvanger send succeeded to %1 common nl Zend verzenden gelukt naar %1 seperator addressbook nl Scheidingsteken +set full name and file as field in contacts of all users (either all or only empty values) admin nl Zet volledige naam en 'bestand als'in contacten van alle gebruikers. Of allemaal vullen of allemaal leeg laten set only full name addressbook nl Geef alleen de volledig naam should the columns photo and home address always be displayed, even if they are empty. addressbook nl Moeten de kolommen foto en thuisadres altijd worden weergegeven, zelfs als ze leeg zijn? show addressbook nl Toon @@ -374,6 +421,7 @@ show infolog entries for this organisation addressbook nl InfoLog details van de show the contacts of this organisation addressbook nl Toon de contacten van deze organisatie similar contacts found: addressbook nl Zelfde contacten gevonden: size of popup (wxh, eg.400x300, if a popup should be used) admin nl Afmetingen van de popup (BxH, bijvoorbeeld 400x300, als een popup nodig is) +stadt addressbook nl Plaats start admin nl Start startrecord addressbook nl Startbestand state common nl Provincie / Staat / Streek @@ -393,6 +441,7 @@ the anonymous user has probably no add rights for this addressbook. addressbook the anonymous user needs add rights for it! addressbook nl De anonieme gebruiker moet hier toevoegrechten voor hebben! the anonymous user needs read it! addressbook nl De anonieme gebruiker moet hier leesrechten voor hebben! the following document-types are supported: addressbook nl De volgende document types worden ondersteund : +the zip extension is needed, to insert contact data in openoffice or msoffice documents. addressbook nl Zip extensie is nodig, om contact data te importeren in OpenOffice of MSOffice documenten there was an error saving your data :-( addressbook nl Er ontstond een fout tijdens het opslaan van uw gegevens :-( this module displays a contactform, that stores direct into the addressbook. addressbook nl Deze module toont een contact formulier dat gegevens direct opslaat in het adresboek. this module displays block from a adddressbook group. addressbook nl Deze module toont een blok van een adresboek groep. @@ -400,6 +449,7 @@ this person's first name was not in the address book. addressbook nl De voornaam this person's last name was not in the address book. addressbook nl De achternaam van deze persoon staat niet het adresboek. timezone addressbook nl Tijdszone title addressbook nl functie titel +titles of any entries linked to the current record, excluding attached files addressbook nl Namen van alle invoer gelinked aan huidig record, behalve de bijgesloten bestanden to many might exceed your execution-time-limit addressbook nl Te veel kan mogelijk uw uitvoertijd van dit programme overschrijden today is %1's birthday! common nl Vandaag is %1 jarig ! tomorrow is %1's birthday. common nl Morgen is %1 jarig @@ -407,8 +457,10 @@ translation addressbook nl Vertaling two of: %1 addressbook nl Twee van: %1 type addressbook nl Type un-delete addressbook nl Verwijderen ongedaan maken +unable to convert "%1" to account id. using plugin setting (%2) for owner. addressbook nl Niet mogelijk om %1 te converteren naar account ID. Gebruik de settings (%2) van de pligin van de eigenaar. unable to delete addressbook nl Niet mogelijk te verwijderen unique id (uid) addressbook nl Uniek ID (UID) +unique id
(to update existing records) addressbook nl Uniek ID
voor bijwerken bestaand record update a single entry by passing the fields. addressbook nl Een record bijwerken door de velden te geven update fields by edited organisations? admin nl Velden van de gewijzigde organisatie bijwerken? updated addressbook nl Bijgewerkt @@ -421,6 +473,7 @@ use an extra category tab? addressbook nl Een extra categorie tabblad gebruiken? use an extra tab for private custom fields? admin nl Een extra categorie tabblad gebruiken voor persoonlijke aangepaste velden? use country list addressbook nl Gebruik Landenlijst use setup for a full account-migration admin nl gebruik setup voor een volledige accountmigratie +use this tag for addresslabels. put the content, you want to repeat, between two tags. addressbook nl Gebruik deze tag voor adres labels.Plaats de inhoud welke je wilt herhalen tussen twee tags. used for links and for the own sorting of the list addressbook nl gebruikt voor links en voor eigen sortering van de lijst user preference addressbook nl Gebrukers voorkeur vcard common nl VCard @@ -430,14 +483,15 @@ verification addressbook nl Verificatie view linked infolog entries addressbook nl Gekoppelde InfoLog details bekijken warning!! ldap is valid only if you are not using contacts for accounts storage! admin nl WAARSCHUWING!! Alleen LDAP gebruiken als het adresboek niet wordt gebruikt om de accounts op te slaan! warning: all contacts found will be deleted! addressbook nl WAARSCHUWING: Alle gevonden contacten worden verwijderd! -warning: template "%1" not found, using default template instead. addressbook nl WAARSCHUWING: Template "%1" niet gevonden, gebruik de standaard template in plaats hiervan. weekday addressbook nl Werkdag what should links to the addressbook display in other applications. empty values will be left out. you need to log in anew, if you change this setting! addressbook nl Wat moeten koppelingen aan het adresboek in andere toepassingen moeten weergeven. Lege waardes worden genegeerd. U moet opnieuw inloggen, als u deze instelling wijzigt! +when viewing a contact, show linked entries from the selected application addressbook nl Bij het bekijken van een contact, toon de verbonden invoer van de geselecteerde invoer where to add the email address addressbook nl waar moet het emailadres toegevoegd worden which address format should the addressbook use for countries it does not know the address format. if the address format of a country is known, it uses it independent of this setting. addressbook nl Welk adresformaat moet het adresboek gebruiken voor landen waarvan het adresformaat nog niet bekend is. Indien het adresformaat van een land bekend is wordt het ongeacht deze instelling gebruikt. which addressbook should be selected when adding a contact and you have no add rights to the current addressbook. addressbook nl Welke adresboek moet geselecteerd worden wanneer een contact wordt bijgevoegd EN wanneer u geen toevoegingsrecht heb bij het huidige adresboek. which charset should be used for the csv export. the system default is the charset of this egroupware installation. addressbook nl Welke karakterset moet gebruikt worden voor de CSV export. De systeem standaard is de karakterset van deze eGroupWare installatie. which charset should be used for the vcard export. addressbook nl Welke karakterset moet gebruikt worden voor de vCard export +which charset should be used for the vcard import and export. addressbook nl Welke karakterset moet gebruikt worden voor de vCard im - export. which fields should be exported. all means every field stored in the addressbook incl. the custom fields. the business or home address only contains name, company and the selected address. addressbook nl Welke velden moeten geexporteerd worden. "Alles" betekent elke veld dat in het adresboek is opgeslagen, inclusief de aangepaste velden. "Alleen bedrijf adres of privé adres" betekent dat alleen naam, organisatie en het gekozen adres wordt geexporteerd. whole query addressbook nl gehele query work email if given, else home email addressbook nl Zakelijke emailadres indien bekend, ander privé emailadres diff --git a/phpgwapi/lang/egw_nl.lang b/phpgwapi/lang/egw_nl.lang index 385b7b0f4e..69de7dffd2 100644 --- a/phpgwapi/lang/egw_nl.lang +++ b/phpgwapi/lang/egw_nl.lang @@ -1,6 +1,10 @@ +%1 active file(s) with same name as directory inactivated! admin nl %1 actieve bestand(en) met dezelfde naam als de map op niet-actief gezet! +%1 directories %2 found! admin nl 1% mappen van %2 gevonden %1 email addresses inserted common nl %1 emailadres ingevoerd %1 file common nl %1 bestand %1 is not executable by the webserver !!! common nl %1 kan niet door de webserver uitgevoerd worden !!! +%1 proxy of %2 common nl %1 proxy van %2 +%1 to sync groupdav nl %1 om te synchroniseren %1choose an other directory%2
or make %3 writeable by webserver common nl %1Kies een andere map%2
of maak %3 schrijfbaar voor de webserver %1egroupware%2 is a multi-user, web-based groupware suite written in %3php%4. common nl %1eGroupWare%2 is een multi-user, web-gebaseerde groupware suite, geschreven in %3PHP%4. (session restored in %1 seconds) common nl (sessie hersteld in %1 seconden) @@ -33,25 +37,37 @@ account is expired common nl Account is verlopen accounts common nl Accounts acl common nl ACL action common nl Actie +action when category is an email address groupdav nl Actie wanneer categorie een email adres is actions common nl Acties active common nl Actief +active directory requires ssl or tls to change passwords! common nl om een wachtwoord van Active directory te wijzigen is SSL of TLS vereist add common nl Toevoegen add %1 category for common nl %1 Categorie toevoegen voor +add a new contact common nl Voeg een nieuw contact toe add category common nl Categorie toevoegen +add current common nl Voeg huidige toe add shortcut common nl Snelkoppeling toevoegen add sub common nl Sub toevoegen +add user to responsibles groupdav nl Voeg gebruiker toe aan verantwoordelijkheden +add user to responsibles, removing evtl. previous category user groupdav nl voeg gebruikers toe aan verantwoordelijkheden, verwijder eventuele eerdere categorieën van gebruiker addressbook common nl Adresboek +addressbooks to sync in addition to personal addressbook groupdav nl Adresboeken welke ook moeten worden gesynchroniseerd buiten het eigen adresboek. admin common nl Beheer administration common nl Beheer afghanistan common nl AFGHANISTAN albania common nl ALBANIË algeria common nl ALGERIJE all common nl Alles +all addressbooks groupdav nl Alle adresboeken all fields common nl alle velden +all in one groupdav nl Alles in een all languages common nl alle talen +allows to modify responsible users from devices not supporting them, by setting email address of a user as category. groupdav nl Sta verantwoordelijke gebruikers toe om apparaten welke niet door hun worden ondersteund te wijzigen, doormiddel van gebruik Mail adres of een gebruiker als categorie. alphabet common nl a,b,c,d,e,f,g,h,i,j,k,l,m,n,o,p,q,r,s,t,u,v,w,x,y,z +alt common nl Alt alternate style-sheet: common nl Afwisselende style-sheet: american samoa common nl AMERIKAANS SAMOA +an admin required that you must change your password upon login. common nl Administrator geeft aan dat u, uw wachtwoord moet wijzigen bij aanmelden. an error happened common nl Een fout trad op an existing and by the webserver readable directory enables the image browser and upload. common nl Een bestaande EN door de webserver leesbare directory maakt het mogelijk om door afbeeldingen te bladeren en te uploaden. and common nl En @@ -62,6 +78,7 @@ antarctica common nl ANTARTICA antigua and barbuda common nl ANTIGUA EN BARBUDA application common nl Toepassing apply common nl Toepassen +apply the action on the whole query, not only the shown entries common nl Voer de aktie uit op voor de gehele query, NIET alleen voor de getoonde invoer april common nl April are you sure you want to delete these entries ? common nl Weet u zeker dat uw deze records wilt verwijderen? are you sure you want to delete this entry ? common nl Weet u zeker dat u dit item wilt verwijderen? @@ -74,6 +91,7 @@ austria common nl OOSTENRIJK author common nl Auteur autohide sidebox menu's common nl Automatisch menu's in de zijlijn verbergen autohide sidebox menus common nl Automatisch menu's in de zijlijn verbergen +automatic update check failed, you need to check manually! common nl Automatische bijwerken niet gelukt, u moet handmatig bijwerken ! automatically hide the sidebox menu's? common nl Automatisch de menu's in de zijlijn verbergen? automatically hide the sidebox menus? common nl Automatisch de menu's in de zijlijn verbergen? autosave default category common nl Automatisch standaard categorie opslaan @@ -110,11 +128,16 @@ bulgarian common nl Bulgaars burkina faso common nl BURKINA FASO burundi common nl BURUNDI calendar common nl Agenda +calendars to sync in addition to personal calendar groupdav nl Agenda's welke naast de eigen agenda moet worden gesynchroniseerd cambodia common nl CAMBODJA cameroon common nl KAMEROEN +can't create directory %1 to connect found unconnected nodes to it! admin nl Kan de map %1 niet maken op connectie niet aangesloten nodes gevonden ! canada common nl CANADA cancel common nl Annuleren cannot replace %1 because it is a directory common nl Kan %1 niet vervangen omdat het een map is +cannot set a category as parent, which is part of this categorys subtree! common nl Kan geen categorie als ouder instellen, welke onderdeel is van de sub categorie van deze categorie boom +cannot set this cat as its own parent! common nl Kan deze categorie als zijn eigen ouder aanmaken +cant open %1, needs ziparchive common nl Kan 1 niet openen, ZipArchief is benodigd cant open '%1' for %2 common nl Kan '%1' niet openen voor %2 cape verde common nl KAAPVERDIË caption common nl Opmerking @@ -123,12 +146,14 @@ categories for common nl categorieën voor category common nl Categorie category %1 has been added ! common nl Categorie %1 is toegevoegd category %1 has been updated ! common nl Categorie %1 is bijgewerkt +category owner common nl Eigenaar Categorie cayman islands common nl KAAIMAN EILANDEN cc common nl Cc centered common nl gecentreerd central african republic common nl CENTRAAL-AFRIKAANSE REPUBLIEK chad common nl CHAD change common nl Veranderen +change owner common nl Verander eigenaar charset common nl utf-8 check installation common nl Controleer installatie check now common nl Nu controleren @@ -139,27 +164,37 @@ choose a background color for the icons common nl Kies een achtergrondkleur voor choose a background image. common nl Kies een achtergrondafbeelding. choose a background style. common nl Kies een achtergrondstijl. choose a text color for the icons common nl KIes een tekstkleur voor de pictogrammen +choose file... common nl Kies bestand... choose the category common nl Kies de categorie choose the parent category common nl Kies bovenliggende categorie +choose time common nl Kies tijd +chosen parent category no longer exists common nl De gekozen hoofd categorie bestaat niet meer christmas island common nl CHRISTMASEILAND clear common nl Wissen clear form common nl Formulier wissen +clear window common nl Wis Scherm click common nl Klik click here to resume your egroupware session. common nl Klik hier om je eGroupWare sessie voort te zetten. click or mouse over to show menus common nl Klik or Mouse Over om menus weer te geven click or mouse over to show menus? common nl Klik or Mouse Over om menus weer te geven? click this image on the navbar: %1 common nl Klik deze afbeelding op de navigatiebalk: %1 +clients not explicitly stating a limit get limited to these many days. a too high limit may cause problems with some clients. groupdav nl Als Client geen specifieke limiet heeft wordt deze gelimiteerd naar aantal dagen. Een te hoge limiet kan met sommige Clients problemen veroorzaken close common nl Sluiten close sidebox common nl Zijkant box sluiten cocos (keeling) islands common nl COCOS (KEELING) EILANDEN +collection empty. common nl Verzameling leeg. +collection listing common nl Verzameling overzicht colombia common nl COLOMBIA common preferences common nl Algemene voorkeuren comoros common nl COMOROS company common nl Bedrijf +confirmation required common nl Bevestiging vereist congo common nl DEMOCRATISCHE REPUBLIEK CONGO congo, the democratic republic of the common nl DEMOCRATISCHE REPUBLIEK CONGO contacting server... common nl Verbinden met server... +content type common nl Type Inhoud cook islands common nl COOKEILANDEN +cookies are required to login to this site login nl Cookies zijn nodig om aan te melden op deze site copy common nl Kopiëren costa rica common nl COSTA RICA cote d ivoire common nl IVOORKUST @@ -167,6 +202,7 @@ could not contact server. operation timed out! common nl Kon geen verbinding met create common nl Maken created by common nl Gemaakt door croatia common nl KROATIË +ctrl common nl Ctrl cuba common nl CUBA currency common nl Munteenheid current common nl Huidig @@ -183,9 +219,13 @@ december common nl December default category common nl Standaard Categorie default height for the windows common nl Standaard hoogte voor de vensters default width for the windows common nl Standaard breedte voor de vensters +del common nl Verwinder delete common nl Verwijderen delete category common nl Categorie verwijderen +delete file common nl Verwijder bestand delete row common nl Rij verwijderen +delete selected entries? common nl Verwijder geselecteerde invoer +delete these entries common nl Verwijder deze invoer delete this entry common nl dit infolog verwijderen denmark common nl DENEMARKEN description common nl Beschrijving @@ -202,6 +242,8 @@ disable the execution a bugfixscript for internet explorer 5.5 and higher to sho disabled common nl Uitgeschakeld display %s first jscalendar nl %s eerst tonen djibouti common nl DJIBOUTI +do not notify common nl Geen melding maken +do not notify of these changes common nl Maak geen melding over deze wijzigingen do you also want to delete all subcategories ? common nl Wilt u ook alle subcategorieën wissen doctype: common nl DOCTYPE: document properties common nl Documenteigenschappen @@ -212,6 +254,7 @@ domestic common nl thuis dominica common nl DOMINICA dominican republic common nl DOMINICAANSE REPUBLIEK done common nl Gereed +dos international common nl DOS Internationaal download common nl Download drag to move jscalendar nl Sleep om te verplaatsen e-mail common nl Email @@ -229,6 +272,8 @@ egypt common nl EGYPTE el salvador common nl EL SALVADOR email common nl Email email-address of the user, eg. "%1" common nl emailadres van de gebruiker, bijv. "%1" +empty file common nl Leeg bestand +enable logging groupdav nl Zet log aan enabled common nl Geactiveerd end date common nl Einddatum end time common nl Eindtijd @@ -243,9 +288,11 @@ error creating %1 %2 directory common nl Error tijdens het creëren van %1 %2 di error deleting %1 %2 directory common nl Error tijdens het verwijderen van %1 %2 directory error renaming %1 %2 directory common nl Error tijdens het hernoemen van %1 %2 directory estonia common nl ESTONIA +etag common nl ETag ethiopia common nl ETHIOPIË everything common nl Alles exact common nl exact +failed to change password. common nl Wachtwoord wijzigen is niet gelukt failed to contact server or invalid response from server. try to relogin. contact admin in case of faliure. common nl Verbinden met server is mislukt of een ongeldige reactie is ontvangen. Probeer opnieuw in te loggen. Neem contact op met de beheerder indien het mislukt. falkland islands (malvinas) common nl FALKLAND EILANDEN faroe islands common nl LAND EILANDEN @@ -296,6 +343,7 @@ group has been deleted common nl Groep is verwijderd group has been updated common nl Groep is bijgewerkt group name common nl groepsnaam group public common nl Toegang voor groep +groupdav common nl CalDAV, CardCAV en GroupDAV server groups common nl Groepen groups with permission for %1 common nl Groepen met toegangsrechten voor %1 groups without permission for %1 common nl Groepen zonder toegangsrechten voor %1 @@ -314,6 +362,7 @@ highest common nl Hoogste holy see (vatican city state) common nl VATICAANSTAD home common nl Start home email common nl email thuis +home-accounts common nl Gebruiker accounts honduras common nl HONDURAS hong kong common nl HONG KONG how many icons should be shown in the navbar (top of the page). additional icons go into a kind of pulldown menu, callable by the icon on the far right side of the navbar. common nl Hoe veel programma-iconen moeten worden weergegeven in de navigatiebalk. Extra iconen worden weergegeven in een uitklapmenu. @@ -330,6 +379,8 @@ indonesia common nl INDONESIË insert all %1 addresses of the %2 contacts in %3 common nl Voer alle %1 adressen in van de %2 contacten in %3 insert column after common nl Kolom invoegen achter insert column before common nl Kolom invoegen voor +insert in %1 common nl Voeg toe in %1 +insert in dokument common nl Voeg toe aan document insert row after common nl Rij invoegen onder insert row before common nl Rij invoegen boven international common nl Internationaal @@ -516,6 +567,8 @@ permission denied! common nl Toestemming geweigerd! permissions to the files/users directory common nl privileges aan de bestanden/gebruikers toekennen personal common nl Persoonlijk peru common nl PERU +pg down common nl Pagina naar benenden +pg up common nl Pagina omhoog philippines common nl FILIPIJNEN phone number common nl telefoonnummer phpgwapi common nl eGroupWare API @@ -532,7 +585,10 @@ poland common nl POLEN portugal common nl PORTUGAL postal common nl Post powered by common nl Ondersteund door +pre common nl Voor preferences common nl Voorkeuren +preferences for the %1 template set preferences nl Voorkeur voor set template %1 +prev common nl Vorige prev. month (hold for menu) jscalendar nl Vorige maand (vasthouden voor menu) prev. year (hold for menu) jscalendar nl Vorig jaar (vasthouden voor menu) previous page common nl Vorige pagina @@ -543,6 +599,7 @@ priority common nl Prioriteit private common nl Privé programs common nl Programma's project common nl Project +properties common nl Eigenschappen public common nl publiek puerto rico common nl PUERTO RICO qatar common nl QATAR @@ -601,6 +658,7 @@ select user common nl Selecteer gebruiker select work email address common nl Selecteer emailadres werk selection common nl Selectie send common nl Verzend +send succeeded to %1 common nl Verzenden naar %1 gelukt senegal common nl SENEGAL september common nl September serbia common nl Servië @@ -612,12 +670,14 @@ session has been killed common nl Sessie is beëindigd setup common nl Setup setup main menu common nl Setup hoofdmenu seychelles common nl SEYCHELLEN +shift common nl Shift show all common nl laat alles zien show all categorys common nl Alle categorieën weergegeven show as topmenu common nl Toon als Topmenu show clock? common nl Klok weergeven? show home and logout button in main application bar? common nl Home en logout knop weergeven in de toepassingen balk? show in sidebox common nl Toon in zijkant blok +show log of following device groupdav nl Toon log van het volgende apparaat show logo's on the desktop. common nl Logo's op het bureaublad weergeven. show menu common nl toon menu show page generation time common nl Verlooptijd van de pagina's weergeven? @@ -639,7 +699,9 @@ somalia common nl SOMALIË sorry, your login has expired login nl Excuses, uw sessie is verlopen south africa common nl ZUID AFRIKA south georgia and the south sandwich islands common nl ZUID-GEORGIA EN DE ZUIDELIJKE SANDWICHEILANDEN +space common nl Spatie spain common nl SPANJE +special characters common nl speciale karakters sri lanka common nl SRI LANKA start date common nl Startdatum start time common nl Starttijd @@ -658,6 +720,7 @@ swaziland common nl SWAZILAND sweden common nl ZWEDEN switzerland common nl ZWITSERLAND syrian arab republic common nl SYRIË +tab common nl Tab table properties common nl Tabel-eigenschappen taiwan common nl TAIWAN/TAIPEI tajikistan common nl TADZJIKISTAN @@ -701,7 +764,9 @@ tuvalu common nl TUVALU type common nl Type uganda common nl UGANDA ukraine common nl OEKRAÏNE +un-delete common nl Verwijderen ongedaan maken underline common nl Onderstrepen +unicode common nl Unicode united arab emirates common nl VERENIGDE ARABISCHE EMIRATEN united kingdom common nl VERENIGD KONINKRIJK united states common nl VERENIGDE STATES @@ -712,6 +777,7 @@ update the clock per minute or per second common nl De klok per minuut of per se upload common nl Upload upload directory does not exist, or is not writeable by webserver common nl Upload map bestaat niet of is niet schrijfbaar voor de webserver upload requires the directory to be writable by the webserver! common nl Upload vereist dat de directory door de webserver beschreven kan worden! +uppercase letters common nl hoofdletters url common nl URL uruguay common nl URUGUAY use button to search for common nl Gebruik knop om te zoeken naar @@ -729,6 +795,7 @@ value common nl Waarde vanuatu common nl VANUATU venezuela common nl VENEZUELA version common nl Versie +vfs upload directory common nl VFS map voor upload viet nam common nl VIETNAM view common nl Bekijken virgin islands, british common nl BRITSE MAAGDENEILANDEN @@ -753,6 +820,8 @@ written by: common nl Geschreven door: year common nl Jaar yemen common nl JEMEN yes common nl Ja +yes - delete common nl Ja -Verwijder +yes - delete including sub-entries common nl Ja - Verwijder inclusief alle onderliggende invoer you are required to change your password during your first login common nl U moet nadat u voor het eerst bent ingelogd uw wachtwoord wijzigen. you can customize how many icons and toolbars the editor shows. common nl U kunt instellen hoeveel pictogrammen en taakbalken de editor moet tonen. you have been successfully logged out login nl U bent succesvol afgemeld @@ -770,7 +839,7 @@ your message could not be sent!
common nl Uw bericht kon niet your message has been sent common nl Uw bericht is verzonden your search returned %1 matchs common nl Uw zoekopdracht leverde %1 items op your search returned 1 match common nl Uw zoekopdracht leverde 1 item op -your session could not be verified. login nl Uw sessie kon niet geverifieerd worden +your session timed out, please log in again login nl Uw sessie s verlopen, meld u alstublieft opnieuw aan your settings have been updated common nl Uw instellingen zijn gewijzigd zambia common nl ZAMBIA zimbabwe common nl ZIMBABWE From 0722b0a3285dcd78e5dd30f881b6d08ba9e4acae Mon Sep 17 00:00:00 2001 From: Hadi Nategh Date: Tue, 21 Oct 2014 15:24:20 +0000 Subject: [PATCH 90/97] Fix dnd helper stack order --- etemplate/templates/default/etemplate2.css | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/etemplate/templates/default/etemplate2.css b/etemplate/templates/default/etemplate2.css index 3287347533..51fa307d0f 100644 --- a/etemplate/templates/default/etemplate2.css +++ b/etemplate/templates/default/etemplate2.css @@ -1641,7 +1641,7 @@ div.et2_image_tooltipPopup { /*egw_action_ddHelper*/ div.et2_egw_action_ddHelper { - + z-index: 999; } /* The last div which shows Ctrl tip to user*/ div.et2_egw_action_ddHelper_tip { From 29f84a83f9d9371d95b13cfde163cc86d20d83c2 Mon Sep 17 00:00:00 2001 From: Nathan Gray Date: Tue, 21 Oct 2014 16:32:47 +0000 Subject: [PATCH 91/97] If link is missing needed information so it won't work, don't look like a link --- etemplate/js/et2_widget_link.js | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/etemplate/js/et2_widget_link.js b/etemplate/js/et2_widget_link.js index ca35e7c01a..2b1be8c0a3 100644 --- a/etemplate/js/et2_widget_link.js +++ b/etemplate/js/et2_widget_link.js @@ -1142,10 +1142,15 @@ var et2_link = et2_valueWidget.extend([et2_IDetachedDOM], this.link.unbind(); if(_value.id && _value.app) { + this.link.addClass("et2_link"); this.link.click( function(){ self.egw().open(_value, "", "view",null,_value.app,_value.app); }); } + else + { + this.link.removeClass("et2_link"); + } if(!_value.title) { var self = this; var node = this.link[0]; From 66611329e9284081db62259da6a7d26afb46b5da Mon Sep 17 00:00:00 2001 From: Ralf Becker Date: Tue, 21 Oct 2014 17:58:10 +0000 Subject: [PATCH 92/97] fix return value of close and several IDE warning --- phpgwapi/cron/asyncservices.php | 2 +- .../inc/class.sqlfs_stream_wrapper.inc.php | 32 +++++++++++-------- 2 files changed, 19 insertions(+), 15 deletions(-) diff --git a/phpgwapi/cron/asyncservices.php b/phpgwapi/cron/asyncservices.php index 4de95e8345..a8b062546d 100644 --- a/phpgwapi/cron/asyncservices.php +++ b/phpgwapi/cron/asyncservices.php @@ -12,7 +12,7 @@ * @version $Id$ */ -if (!isset($_REQUEST['domain'])) $_REQUEST['domain'] = isset($_SERVER['argv'][1]) ? $_SERVER['argv'][1] : 'default'; +if (!isset($_REQUEST['domain'])) $_REQUEST['domain'] = isset($_SERVER['argv'][1]) ? $_SERVER['argv'][1] : 'ralfsmacbook.local';//'default'; $path_to_egroupware = realpath(dirname(__FILE__).'/../..'); // need to be adapted if this script is moved somewhere else // remove the comment from one of the following lines to enable loging diff --git a/phpgwapi/inc/class.sqlfs_stream_wrapper.inc.php b/phpgwapi/inc/class.sqlfs_stream_wrapper.inc.php index 49f4819949..fa70c3756e 100644 --- a/phpgwapi/inc/class.sqlfs_stream_wrapper.inc.php +++ b/phpgwapi/inc/class.sqlfs_stream_wrapper.inc.php @@ -163,7 +163,7 @@ class sqlfs_stream_wrapper implements iface_stream_wrapper * * Normaly not necessary, as it is automatically cleared/updated, UNLESS egw_vfs::$user changes! * - * @param string $path='/' + * @param string $path ='/' */ public static function clearstatcache($path='/') { @@ -185,7 +185,7 @@ class sqlfs_stream_wrapper implements iface_stream_wrapper * - STREAM_REPORT_ERRORS If this flag is set, you are responsible for raising errors using trigger_error() during opening of the stream. * If this flag is not set, you should not raise any errors. * @param string &$opened_path full path of the file/resource, if the open was successfull and STREAM_USE_PATH was set - * @param array $overwrite_new=null if set create new file with values overwriten by the given ones + * @param array $overwrite_new =null if set create new file with values overwriten by the given ones * @return boolean true if the ressource was opened successful, otherwise false */ function stream_open ( $url, $mode, $options, &$opened_path, array $overwrite_new=null ) @@ -377,6 +377,10 @@ class sqlfs_stream_wrapper implements iface_stream_wrapper } } } + else + { + $ret = true; + } $ret = fclose($this->opened_stream) && $ret; unset(self::$stat_cache[$this->opened_path]); @@ -818,8 +822,8 @@ class sqlfs_stream_wrapper implements iface_stream_wrapper * This is not (yet) a stream-wrapper function, but it's necessary and can be used static * * @param string $url - * @param int $time=null modification time (unix timestamp), default null = current time - * @param int $atime=null access time (unix timestamp), default null = current time, not implemented in the vfs! + * @param int $time =null modification time (unix timestamp), default null = current time + * @param int $atime =null access time (unix timestamp), default null = current time, not implemented in the vfs! */ static function touch($url,$time=null,$atime=null) { @@ -895,7 +899,7 @@ class sqlfs_stream_wrapper implements iface_stream_wrapper * Chgrp command, not yet a stream-wrapper function, but necessary * * @param string $url - * @param int $group + * @param int $owner * @return boolean */ static function chgrp($url,$owner) @@ -975,8 +979,8 @@ class sqlfs_stream_wrapper implements iface_stream_wrapper /** * This method is called immediately when your stream object is created for examining directory contents with opendir(). * - * @param string $path URL that was passed to opendir() and that this object is expected to explore. - * @param $options + * @param string $url URL that was passed to opendir() and that this object is expected to explore. + * @param int $options * @return booelan */ function dir_opendir ( $url, $options ) @@ -1033,7 +1037,7 @@ class sqlfs_stream_wrapper implements iface_stream_wrapper * The last 3 digits are exactly the same thing as what you pass to chmod. * 040000 defines a directory, and 0100000 defines a file. * - * @param string $path + * @param string $url * @param int $flags holds additional flags set by the streams API. It can hold one or more of the following values OR'd together: * - STREAM_URL_STAT_LINK For resources with the ability to link to other resource (such as an HTTP Location: forward, * or a filesystem symlink). This flag specified that only information about the link itself should be returned, @@ -1042,7 +1046,7 @@ class sqlfs_stream_wrapper implements iface_stream_wrapper * - STREAM_URL_STAT_QUIET If this flag is set, your wrapper should not raise any errors. If this flag is not set, * you are responsible for reporting errors using the trigger_error() function during stating of the path. * stat triggers it's own warning anyway, so it makes no sense to trigger one by our stream-wrapper! - * @param boolean $eacl_access=null allows extending classes to pass the value of their check_extended_acl() method (no lsb!) + * @param boolean $eacl_access =null allows extending classes to pass the value of their check_extended_acl() method (no lsb!) * @return array */ static function url_stat ( $url, $flags, $eacl_access=null ) @@ -1246,7 +1250,7 @@ class sqlfs_stream_wrapper implements iface_stream_wrapper * * The readlink value is read by url_stat or dir_opendir and therefore cached in the stat-cache. * - * @param string $url + * @param string $path * @return string|boolean content of the symlink or false if $url is no symlink (or not found) */ static function readlink($path) @@ -1371,9 +1375,9 @@ class sqlfs_stream_wrapper implements iface_stream_wrapper * Only root, the owner of the path or an eGW admin (only if there's no owner but a group) are allowd to set eACL's! * * @param string $path string with path - * @param int $rights=null rights to set, or null to delete the entry - * @param int|boolean $owner=null owner for whom to set the rights, null for the current user, or false to delete all rights for $path - * @param int $fs_id=null fs_id to use, to not query it again (eg. because it's already deleted) + * @param int $rights =null rights to set, or null to delete the entry + * @param int|boolean $owner =null owner for whom to set the rights, null for the current user, or false to delete all rights for $path + * @param int $fs_id =null fs_id to use, to not query it again (eg. because it's already deleted) * @return boolean true if acl is set/deleted, false on error */ static function eacl($path,$rights=null,$owner=null,$fs_id=null) @@ -1837,7 +1841,7 @@ class sqlfs_stream_wrapper implements iface_stream_wrapper * Read properties for a ressource (file, dir or all files of a dir) * * @param array|string|int $path_ids (array of) string with path or integer fs_id - * @param string $ns='http://egroupware.org/' namespace if propfind should be limited to a single one, use null for all + * @param string $ns ='http://egroupware.org/' namespace if propfind should be limited to a single one, use null for all * @return array|boolean false on error ($path_ids does not exist), array with props (values for keys 'name', 'ns', 'value'), or * fs_id/path => array of props for $depth==1 or is_array($path_ids) */ From 5331a3b2140f69f2726270697a1e79dd6756d399 Mon Sep 17 00:00:00 2001 From: Ralf Becker Date: Tue, 21 Oct 2014 18:00:48 +0000 Subject: [PATCH 93/97] revert accidental commit --- phpgwapi/cron/asyncservices.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/phpgwapi/cron/asyncservices.php b/phpgwapi/cron/asyncservices.php index a8b062546d..4de95e8345 100644 --- a/phpgwapi/cron/asyncservices.php +++ b/phpgwapi/cron/asyncservices.php @@ -12,7 +12,7 @@ * @version $Id$ */ -if (!isset($_REQUEST['domain'])) $_REQUEST['domain'] = isset($_SERVER['argv'][1]) ? $_SERVER['argv'][1] : 'ralfsmacbook.local';//'default'; +if (!isset($_REQUEST['domain'])) $_REQUEST['domain'] = isset($_SERVER['argv'][1]) ? $_SERVER['argv'][1] : 'default'; $path_to_egroupware = realpath(dirname(__FILE__).'/../..'); // need to be adapted if this script is moved somewhere else // remove the comment from one of the following lines to enable loging From 3273c578f13e4232cd16666d1c793547d575f7c7 Mon Sep 17 00:00:00 2001 From: Hadi Nategh Date: Wed, 22 Oct 2014 08:28:16 +0000 Subject: [PATCH 94/97] Fix extra scrollbar on calendar tab details --- calendar/templates/default/edit.xet | 2 +- calendar/templates/pixelegg/app.css | 4 ++-- calendar/templates/pixelegg/app.less | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/calendar/templates/default/edit.xet b/calendar/templates/default/edit.xet index 842f26ff48..aed70b7315 100644 --- a/calendar/templates/default/edit.xet +++ b/calendar/templates/default/edit.xet @@ -27,7 +27,7 @@ - + diff --git a/calendar/templates/pixelegg/app.css b/calendar/templates/pixelegg/app.css index 09db451db8..5cea362c9e 100755 --- a/calendar/templates/pixelegg/app.css +++ b/calendar/templates/pixelegg/app.css @@ -11,7 +11,7 @@ * @package calendar * @version $Id$ */ -/* $Id: app.css 48162 2014-08-21 12:20:44Z hnategh $ */ +/* $Id: app.css 48463 2014-09-04 13:37:46Z ralfbecker $ */ /* Header classes */ tr.dialogHeader td, tr.dialogHeader2 td, @@ -1053,7 +1053,7 @@ div#calendar-edit #calendar-edit_calendar-edit-details .et2_hbox_right { overflow-y: auto; } div#calendar-edit #calendar-edit_calendar-edit-details .et2_selectbox .ui-multiselect-checkboxes { - min-height: 290px; + min-height: 229px; } div#calendar-edit #calendar-edit_calendar-edit-details .et2_selectbox .ui-multiselect-checkboxes li { text-indent: -20px; diff --git a/calendar/templates/pixelegg/app.less b/calendar/templates/pixelegg/app.less index aa95017ecd..676f0967a8 100755 --- a/calendar/templates/pixelegg/app.less +++ b/calendar/templates/pixelegg/app.less @@ -477,7 +477,7 @@ div#calendar-edit{ .calendar_category_details{margin: 0 1em; width: 20em;} .et2_hbox_right {overflow-y: auto;} - .et2_selectbox .ui-multiselect-checkboxes {min-height: 290px;} + .et2_selectbox .ui-multiselect-checkboxes {min-height: 229px;} .et2_selectbox .ui-multiselect-checkboxes li {text-indent: -20px;} //selectbox From d9b44802761341a2d2a6a440cf04fa30e9e6ed70 Mon Sep 17 00:00:00 2001 From: Hadi Nategh Date: Wed, 22 Oct 2014 08:50:10 +0000 Subject: [PATCH 95/97] Keep toolbar actions in single line -Fix mail display toolbar delete action jumps to second line --- etemplate/templates/default/etemplate2.css | 1 + 1 file changed, 1 insertion(+) diff --git a/etemplate/templates/default/etemplate2.css b/etemplate/templates/default/etemplate2.css index 51fa307d0f..d39be9870a 100644 --- a/etemplate/templates/default/etemplate2.css +++ b/etemplate/templates/default/etemplate2.css @@ -1489,6 +1489,7 @@ div.ui-toolbar-menulist{ float:left; width:80%; height:24px; + white-space: nowrap; } .et2_toolbarDropArea{ border: 1px dashed lightgray; From cd27be40ace781ea7acc3cd04a173aae3ef3fd8e Mon Sep 17 00:00:00 2001 From: Ralf Becker Date: Wed, 22 Oct 2014 14:05:25 +0000 Subject: [PATCH 96/97] copy 14.1 changelog to trunk to satisfy update checker --- doc/rpm-build/debian.changes | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/doc/rpm-build/debian.changes b/doc/rpm-build/debian.changes index 2baf3502b1..0ed52ef53b 100644 --- a/doc/rpm-build/debian.changes +++ b/doc/rpm-build/debian.changes @@ -1,3 +1,24 @@ +egroupware-epl (14.1.20141021-1) hardy; urgency=low + + * SECURITY: path traversal revealing arbitrary files on server fixed, please update ASAP + * All apps: Drag and drop entries between lists to link them + * Mail: fix copy/move of mails between accounts + * Mail: fix for messed up plain-text signature in some cases + * Mail: import and display of mails failed, if personal part of addresses contains valid encoded utf-8 characters + * Calendar: make custom fields available in table plugins for document merge + * Calendar: fixed planner by category view was showing all categories under "None" + * Addressbook: with double-click preference set to edit, CRM-view did not open when selected in menu + * Addressbook: Fix tab order between zip code and city in AB edit dialog + * InfoLog: allow to (re-)set view of entries link to contacts via favorites + * Tracker: async job data of tracker got mangled by asyncservice + * Timesheet: fix (un)setting project for adding, editing and save&new timesheets + * News: fix broken automatic periodical import of news + * Filemanager: fixed super-user not able to create top-level directory, eg. /test + * Admin/LDAP: show LDAP extra attributes shell/homedir, if enabled in setup + * Admin/LDAP: LDAP extra attributes homedirector and loginshell were not stored (home set to /dev/null) + + -- Ralf Becker Tue, 21 Oct 2014 17:33:58 +0200 + egroupware-epl (14.1.20141010-1) hardy; urgency=low * Mail: fix download/saving of mail or attachments lead to redirect loop on next refresh From fb0059b796da85b0839493df553160803f173e1b Mon Sep 17 00:00:00 2001 From: Hadi Nategh Date: Wed, 22 Oct 2014 15:12:48 +0000 Subject: [PATCH 97/97] Use jquery-tap-and-hold plugin for taphold event on touch devices --- phpgwapi/js/egw_action/egw_action_popup.js | 3 +- .../js/jquery/jquery-tap-and-hold/README.txt | 19 +++ .../examples/example1.html | 17 +++ .../jquery-tap-and-hold/jquery.tapandhold.js | 136 ++++++++++++++++++ 4 files changed, 174 insertions(+), 1 deletion(-) create mode 100644 phpgwapi/js/jquery/jquery-tap-and-hold/README.txt create mode 100644 phpgwapi/js/jquery/jquery-tap-and-hold/examples/example1.html create mode 100644 phpgwapi/js/jquery/jquery-tap-and-hold/jquery.tapandhold.js diff --git a/phpgwapi/js/egw_action/egw_action_popup.js b/phpgwapi/js/egw_action/egw_action_popup.js index fdce0e208a..aa3ab5d8b7 100644 --- a/phpgwapi/js/egw_action/egw_action_popup.js +++ b/phpgwapi/js/egw_action/egw_action_popup.js @@ -12,6 +12,7 @@ /*egw:uses jquery.jquery; egw_menu; + /phpgwapi/js/jquery/jquery-tap-and-hold/jquery.tapandhold.js; */ if (typeof window._egwActionClasses == "undefined") @@ -253,7 +254,7 @@ function egwPopupActionImplementation() $j(_node).bind('taphold', contextHandler); } else { $j(_node).on('contextmenu', contextHandler); - } + } } ai.doRegisterAction = function(_aoi, _callback, _context) diff --git a/phpgwapi/js/jquery/jquery-tap-and-hold/README.txt b/phpgwapi/js/jquery/jquery-tap-and-hold/README.txt new file mode 100644 index 0000000000..8f30cea8ed --- /dev/null +++ b/phpgwapi/js/jquery/jquery-tap-and-hold/README.txt @@ -0,0 +1,19 @@ +jQuery - Tap and Hold +===================== + + This jQuery plugin lets you detect a tap and hold event on touch interfaces. + +How to use it? + + 1) Add the jQuery Tap and Hold plugin into your HTML + + + + 2) Bind a tap and hold handler function to the tap and hold event of an element. + + $("#myDiv").bind("taphold", function(event){ + alert("This is a tap and hold!"); + }); + +You can check a working example in examples/example1.html + diff --git a/phpgwapi/js/jquery/jquery-tap-and-hold/examples/example1.html b/phpgwapi/js/jquery/jquery-tap-and-hold/examples/example1.html new file mode 100644 index 0000000000..731038c825 --- /dev/null +++ b/phpgwapi/js/jquery/jquery-tap-and-hold/examples/example1.html @@ -0,0 +1,17 @@ + + + jQuery - Tap and Hold + + + + + +

+ + \ No newline at end of file diff --git a/phpgwapi/js/jquery/jquery-tap-and-hold/jquery.tapandhold.js b/phpgwapi/js/jquery/jquery-tap-and-hold/jquery.tapandhold.js new file mode 100644 index 0000000000..aae3495bf5 --- /dev/null +++ b/phpgwapi/js/jquery/jquery-tap-and-hold/jquery.tapandhold.js @@ -0,0 +1,136 @@ +/** + * Copyright (c) 2011 Zauber S.A. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * @author Guido Marucci Blas - guido@zaubersoftware.com + * @description Adds a handler for a custom event 'taphold' that handles a + * tap and hold on touch interfaces. + */ +(function($) { + var TAP_AND_HOLD_TRIGGER_TIMER = 1000; + var MAX_DISTANCE_ALLOWED_IN_TAP_AND_HOLD_EVENT = 40; + var TOUCHSTART = "touchstart"; + var TOUCHEND = "touchend"; + var TOUCHMOVE = "touchmove"; + + // For debugging only + // var TOUCHSTART = "mousedown"; + // var TOUCHEND = "mouseup"; + // var TOUCHMOVE = "mousemove"; + + var tapAndHoldTimer = null; + + function calculateEuclideanDistance(x1, y1, x2, y2) { + var diffX = (x2 - x1); + var diffY = (y2 - y1); + return Math.sqrt((diffX * diffX) + (diffY * diffY)); + }; + + function onTouchStart(event) { + var e = event.originalEvent; + + // Only start detector if and only if one finger is over the widget + if (!e.touches || (e.targetTouches.length === 1 && e.touches.length === 1)) { + startTapAndHoldDetector.call(this, event) + var element = $(this); + element.bind(TOUCHMOVE, onTouchMove); + element.bind(TOUCHEND, onTouchEnd); + } else { + stopTapAndHoldDetector.call(this); + } + }; + + function onTouchMove(event) { + if (tapAndHoldTimer == null) { + return; + } + + var e = event.originalEvent; + var x = (e.changedTouches) ? e.changedTouches[0].pageX: e.pageX; + var y = (e.changedTouches) ? e.changedTouches[0].pageY: e.pageY; + + var tapAndHoldPoint = $(this).data("taphold.point"); + var euclideanDistance = calculateEuclideanDistance(tapAndHoldPoint.x, tapAndHoldPoint.y, x, y); + + if (euclideanDistance > MAX_DISTANCE_ALLOWED_IN_TAP_AND_HOLD_EVENT) { + stopTapAndHoldDetector.call(this); + } + }; + + function onTouchEnd(event) { + stopTapAndHoldDetector.call(this); + }; + + function onTapAndHold(event) { + clear.call(this); + $(this).data("taphold.handler").call(this, event); + }; + + function clear() { + tapAndHoldTimer = null; + $(this).unbind(TOUCHMOVE, onTouchMove); + $(this).unbind(TOUCHEND, onTouchEnd); + }; + + function startTapAndHoldDetector(event) { + if (tapAndHoldTimer != null) { + return; + } + var self = this; + tapAndHoldTimer = setTimeout(function(){ + onTapAndHold.call(self, event) + }, TAP_AND_HOLD_TRIGGER_TIMER); + + // Stores tap x & y + var e = event.originalEvent; + var tapAndHoldPoint = {}; + tapAndHoldPoint.x = (e.changedTouches) ? e.changedTouches[0].pageX: e.pageX; + tapAndHoldPoint.y = (e.changedTouches) ? e.changedTouches[0].pageY: e.pageY; + $(this).data("taphold.point", tapAndHoldPoint); + }; + + function stopTapAndHoldDetector() { + clearTimeout(tapAndHoldTimer); + clear.call(this); + }; + + $.event.special["taphold"] = { + setup: function() { + + }, + + add: function(handleObj) { + $(this).data("taphold.handler", handleObj.handler); + if (handleObj.data) { + $(this).bind(TOUCHSTART, handleObj.data, onTouchStart); + } else { + $(this).bind(TOUCHSTART, onTouchStart); + } + }, + + remove: function(handleObj) { + stopTapAndHoldDetector.call(this); + if (handleObj.data) { + $(this).unbind(TOUCHSTART, handleObj.data, onTouchStart); + } else { + $(this).unbind(TOUCHSTART, onTouchStart); + } + }, + + teardown: function() { + + } + }; + +})(jQuery);