diff --git a/calendar/inc/class.calendar_ui.inc.php b/calendar/inc/class.calendar_ui.inc.php index feb4c3d8bf..0b48a1c471 100644 --- a/calendar/inc/class.calendar_ui.inc.php +++ b/calendar/inc/class.calendar_ui.inc.php @@ -477,16 +477,7 @@ class calendar_ui */ function _select_box($title,$name,$options,$baseurl='') { - if ($baseurl) // we append the value to the baseurl - { - if (substr($baseurl,-1) != '=') $baseurl .= strpos($baseurl,'?') === False ? '?' : '&'; - $onchange="egw_appWindow('calendar').location='$baseurl'+this.value;"; - } - else // we add $name=value to the actual location - { - $onchange="var win=egw_appWindow('calendar'); win.location=win.location+(win.location.search.length ? '&' : '?')+'".$name."='+this.value;"; - } - $select = ' \n"; @@ -665,11 +656,10 @@ class calendar_ui // Search $blur = addslashes(html::htmlspecialchars(lang('Search').'...')); - $value = @$_POST['keywords'] ? html::htmlspecialchars($_POST['keywords']) : $blur; + $value = @$_POST['keywords'] ? html::htmlspecialchars($_POST['keywords']) : ''; $file[++$n] = array( 'text' => html::form('', + ' placeholder="'.$blur.'" title="'.lang('Search').'">', '','/index.php',array('menuaction'=>'calendar.calendar_uilist.listview')), 'no_lang' => True, 'link' => False, @@ -717,34 +707,13 @@ class calendar_ui } // Category Selection - $onchange = "var value = ''; - if(selectBox = document.getElementById('cat_id')) { - for(i=0; i < selectBox.length; ++i) { - if (selectBox.options[i].selected) { - value += (value ? ',' : '') + selectBox.options[i].value; - } - } - }"; - if ($baseurl) // we append the value to the baseurl - { - $cat_baseurl = $baseurl ? $baseurl.'&cat_id=' : ''; - if (substr($cat_baseurl,-1) != '=') $cat_baseurl .= strpos($cat_baseurl,'?') === False ? '?' : '&'; - $onchange.="egw_appWindow('calendar').location='$cat_baseurl'+value;"; - } - else // we add $name=value to the actual location - { - $onchange.="var win=egw_appWindow('calendar'); win.location=win.location+(win.location.search.length ? '&' : '?')+'cat_id='+value;"; - } - $cat_id = explode(',',$this->cat_id); $options = ''. $this->categories->formatted_list('select','all',$cat_id,'True'); - $icon_onclick = "if(selectBox = document.getElementById('cat_id')) { - if (!selectBox.multiple) {selectBox.size=4; selectBox.multiple=true;}}"; - $select = ' \n" . html::image('phpgwapi','attach','','onclick="'.$icon_onclick.'"'); + $options."\n" . html::image('phpgwapi','attach','','id="calendar_cat_id_multiple"'); $file[++$n] = array( 'text' => $select, @@ -790,32 +759,17 @@ class calendar_ui } // we no longer exclude non-accounts from the account-selection: it shows all types of participants $accounts = explode(',',$this->owner); + $current_view_url = egw::link('/index.php',array( + 'menuaction' => $this->view_menuaction, + 'date' => $this->date, + ),false); $file[] = array( 'text' => " - -". +\n". + $this->accountsel->selection('owner','uical_select_owner',$accounts,'calendar+',count($accounts) > 1 ? 4 : 1,False, ' style="width: '.(count($accounts) > 1 && in_array($this->common_prefs['account_selection'],array('selectbox','groupmembers')) ? '100%' : '87%').';"'. - ' title="'.lang('select a %1',lang('user')).'" onchange="load_cal(\''. - egw::link('/index.php',array( - 'menuaction' => $this->view_menuaction, - 'date' => $this->date, - ),false).'\',\'uical_select_owner\');"','',$grants,false,array($this->bo,'participant_name')), + ' title="'.lang('select a %1',lang('user')).'"','',$grants,false,array($this->bo,'participant_name')), 'no_lang' => True, 'link' => False ); @@ -839,10 +793,8 @@ function load_cal(url,id,no_reset) { } if($options != '') { $options = '\n" . $options; - $name = 'merge'; - $onchange="var win=egw_appWindow('calendar'); win.location=win.location+(win.location.search.length ? '&' : '?')+'".$name."='+this.value;this.value='';"; - $select = ' '. $options."\n"; $file[] = array( diff --git a/calendar/inc/class.calendar_uiviews.inc.php b/calendar/inc/class.calendar_uiviews.inc.php index 79070972a7..74525d4611 100644 --- a/calendar/inc/class.calendar_uiviews.inc.php +++ b/calendar/inc/class.calendar_uiviews.inc.php @@ -146,6 +146,9 @@ class calendar_uiviews extends calendar_ui */ function __construct($set_states=null) { + // tell framework calendar needs eval and inline javascript :( + egw_framework::csp_script_src_attrs(array('unsafe-eval', 'unsafe-inline')); + parent::__construct(false,$set_states); // call the parent's constructor $this->extraRowsOriginal = $this->extraRows; //save original extraRows value diff --git a/calendar/js/navigation.js b/calendar/js/navigation.js new file mode 100644 index 0000000000..d17d40ee91 --- /dev/null +++ b/calendar/js/navigation.js @@ -0,0 +1,76 @@ +/** + * Calendar - sidebox navigation + * + * @link http://www.egroupware.org + * @author Ralf Becker + * @package calendar + * @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License + * @version $Id$ + */ + +/** + * Sidebox navigation for calendar + * + * @todo add code from jscalendar->flat(), or better replace it altogether ... + */ +(function() +{ + var script_tag = document.getElementById('calendar-navigation-script'); + var current_view_url; + if (script_tag) + { + current_view_url = script_tag.getAttribute('data-current-view-url'); + } + function load_cal(url,id,no_reset) { + var owner=''; + var i = 0; + selectBox = document.getElementById(id); + for(i=0; i < selectBox.length; ++i) { + if (selectBox.options[i].selected) { + owner += (owner ? ',' : '') + selectBox.options[i].value; + } + } + if (owner) { + if (typeof no_reset == 'unknown') no_reset = false; + egw_appWindow('calendar').location=url+'&owner='+(no_reset?'':'0,')+owner; + } + } + + /** + * Initialisation after DOM *and* jQuery is loaded + */ + egw_LAB.wait(function() { + $j(function(){ + var calendar_window = egw_appWindow('calendar'); + // change handlers setting a certain url, eg. view + $j('#calendar_view').change(function(){ + calendar_window.location = egw_webserverUrl+'/index.php?'+this.value; + }); + // calendar owner selection change + $j('#uical_select_owner,#uical_select_resource').change(function(e){ + if (this.value != 'popup') + { + load_cal(current_view_url, this.id, this.id != 'uical_select_owner'); + e.preventDefault(); + } + }); + // diverse change handlers appending a name=value to url + $j('#calendar_merge,#calendar_filter,#calendar_cat_id').change(function(){ + var val = $j(this).val(); + if ($j.isArray(val)) val = val.join(','); + calendar_window.location = current_view_url+ + (current_view_url.search.length ? '&' : '?')+this.name+'='+val; + if (this.name == 'merge') this.value=''; + }); + // click handler to switch selectbox to multiple + $j('#calendar_cat_id_multiple').click(function(){ + var selectBox = document.getElementById(this.id.replace('_multiple', '')); + if (selectBox && !selectBox.multiple) + { + selectBox.size=4; + selectBox.multiple=true; + } + }); + }); + }); +})(); \ No newline at end of file diff --git a/etemplate/inc/class.etemplate_old.inc.php b/etemplate/inc/class.etemplate_old.inc.php index 6438d9ce93..b1c1ff01b4 100644 --- a/etemplate/inc/class.etemplate_old.inc.php +++ b/etemplate/inc/class.etemplate_old.inc.php @@ -136,6 +136,9 @@ class etemplate_old extends boetemplate */ function __construct($name='',$load_via='') { + // tell framework old eTemplate apps needs eval and inline javascript :( + egw_framework::csp_script_src_attrs(array('unsafe-eval', 'unsafe-inline')); + parent::__construct($name,$load_via); $this->sitemgr = isset($GLOBALS['Common_BO']) && is_object($GLOBALS['Common_BO']); diff --git a/etemplate/inc/class.etemplate_widget_htmlarea.inc.php b/etemplate/inc/class.etemplate_widget_htmlarea.inc.php index d4821a7e8f..5075f6a988 100644 --- a/etemplate/inc/class.etemplate_widget_htmlarea.inc.php +++ b/etemplate/inc/class.etemplate_widget_htmlarea.inc.php @@ -31,8 +31,10 @@ class etemplate_widget_htmlarea extends etemplate_widget public function beforeSendToClient($cname) { $form_name = self::form_name($cname, $this->id); - - + + // tell framework CK Editor needs eval and inline javascript :( + egw_framework::csp_script_src_attrs(array('unsafe-eval', 'unsafe-inline')); + $config = egw_ckeditor_config::get_ckeditor_config_array($this->attrs['mode'], $this->attrs['height'], $this->attrs['expand_toolbar'],$this->attrs['base_href'] ); diff --git a/phpgwapi/inc/class.egw_framework.inc.php b/phpgwapi/inc/class.egw_framework.inc.php index e236d25634..4366832891 100644 --- a/phpgwapi/inc/class.egw_framework.inc.php +++ b/phpgwapi/inc/class.egw_framework.inc.php @@ -83,6 +83,59 @@ abstract class egw_framework $this->template_dir = '/phpgwapi/templates/'.$template; } + /** + * Additional attributes for CSP script-src 'self' + * + * @var array + */ + private static $csp_script_src_attrs = array('unsafe-eval'); + + /** + * Set/get Content-Security-Policy attributes for script-src: 'unsafe-eval' and/or 'unsafe-inline' + * + * Using CK-Editor currently requires both to be set :( + * + * Old pre-et2 apps might need to call egw_framework::csp_script_src_attrs(array('unsafe-eval','unsafe-inline')) + * + * EGroupware itself currently still requires 'unsafe-eval'! + * + * @param string|array $set=array() 'unsafe-eval' and/or 'unsafe-inline' (without quotes!) + * @return string with attributes eg. "'unsafe-eval' 'unsafe-inline'" + */ + public static function csp_script_src_attrs($set=null) + { + foreach((array)$set as $attr) + { + if (!in_array($attr, self::$csp_script_src_attrs)) + { + self::$csp_script_src_attrs[] = $attr; + //error_log(__METHOD__."() swiching CSP OFF for script-src '$attr' ".function_backtrace()); + } + } + return self::$csp_script_src_attrs ? "'".implode("' '", self::$csp_script_src_attrs)."'" : ''; + } + + /** + * Send HTTP headers: Content-Type and Content-Security-Policy + */ + protected function _send_headers() + { + // add a content-type header to overwrite an existing default charset in apache (AddDefaultCharset directiv) + header('Content-type: text/html; charset='.translation::charset()); + + // content-security-policy header: + // - "script-src 'self' 'unsafe-eval'" allows only self and eval (eg. ckeditor), but forbids inline scripts, onchange, etc + // - "connect-src 'self'" allows ajax requests only to self + // - "style-src 'self' 'unsave-inline'" allows only self and inline style, which we need + // - "frame-src 'self' manual.egroupware.org" allows frame and iframe content only for self or manual.egroupware.org + $csp = "script-src 'self' ".($script_attrs=self::csp_script_src_attrs())."; connect-src 'self'; style-src 'self' 'unsafe-inline'; frame-src 'self' manual.egroupware.org"; + //error_log(__METHOD__."() script_attrs=$script_attrs"); + //$csp = "default-src * 'unsafe-eval' 'unsafe-inline'"; // allow everything + header("Content-Security-Policy: $csp"); + header("X-Webkit-CSP: $csp"); // Chrome: <= 24, Safari incl. iOS + header("X-Content-Security-Policy: $csp"); // FF <= 22 + } + /** * Constructor for static variables */ diff --git a/phpgwapi/inc/class.html.inc.php b/phpgwapi/inc/class.html.inc.php index 3f6cfceae0..b4b43f55ea 100644 --- a/phpgwapi/inc/class.html.inc.php +++ b/phpgwapi/inc/class.html.inc.php @@ -531,6 +531,9 @@ class html return self::textarea($_name,$_content,'style="width: '.$_width.'; height: '.$_height.';" id="'.htmlspecialchars($_name).'"'); } + // tell framework CK Editor needs eval and inline javascript :( + egw_framework::csp_script_src_attrs(array('unsafe-eval', 'unsafe-inline')); + //include the ckeditor js file egw_framework::validate_file('ckeditor','ckeditor','phpgwapi'); diff --git a/phpgwapi/inc/class.uiaccountsel.inc.php b/phpgwapi/inc/class.uiaccountsel.inc.php index 8dcbd27820..709d507043 100644 --- a/phpgwapi/inc/class.uiaccountsel.inc.php +++ b/phpgwapi/inc/class.uiaccountsel.inc.php @@ -52,10 +52,6 @@ class uiaccountsel { $this->account_selection = 'primary_group'; } - - // Include these here, framework may have already sent header by the time the account select is made - egw_framework::validate_file('/phpgwapi/js/jquery/chosen/chosen.jquery.js'); - egw_framework::includeCSS('/phpgwapi/js/jquery/chosen/chosen.css',null,false); } /** @@ -96,6 +92,8 @@ class uiaccountsel $multi_size = abs($lines); $lines = 1; } + $options .= ' class="uiaccountselection '.$this->account_selection.'"'; // to be able to style and select it with jQuery + if ($this->account_selection == 'none') // dont show user-selection at all! { return html::input_hidden($name,$selected); @@ -249,26 +247,16 @@ class uiaccountsel 'element_id' => $element_id, 'multiple' => $lines, // single selection (multiple=0), closes after the first selection ),false); - $popup_options = 'width=600,height=420,toolbar=no,scrollbars=yes,resizable=yes'; $app = $GLOBALS['egw_info']['flags']['currentapp']; if (!$only_groups && ($lines <= 1 && $this->account_selection == 'popup' || !$lines && $this->account_selection == 'primary_group')) { if (!$lines) { - $options .= ' onchange="if (this.value==\'popup\') '."window.open('$link','uiaccountsel','$popup_options');". - ($onchange ? " else { $onchange }" : '' ).'" onclick="if (this.value==\'popup\') '."window.open('$link','uiaccountsel','$popup_options');\""; $select['popup'] = lang('Search').' ...'; } - elseif ($onchange) - { - $options .= ' onchange="if (this.value[0]!=\',\') { '.$onchange.' }"'; - } - $need_js_popup = True; - } - elseif ($onchange) - { - $options .= ' onchange="'.$onchange.'"'; } + if ($onchange) $options .= ' onchange="'.$onchange.'"'; // no working under CSP without 'unsafe-inline' + if ($extra_label) { //in php5 this put's the extra-label at the end: $select = array($extra_label) + $select; @@ -286,77 +274,30 @@ class uiaccountsel ); } //echo "

html::select('$name',".print_r($selected,True).",".print_r($select,True).",True,'$options')

\n"; - $html = html::select($name,$selected,$select,True,$options.' id="'.$element_id.'"',$lines > 1 ? $lines : 0,true); + $html = html::select($name,$selected,$select,True,$options.' id="'.$element_id. + '" data-popup-link="'.htmlspecialchars($link).'"',$lines > 1 ? $lines : 0,false); if (!$only_groups && ($lines > 0 && $this->account_selection == 'popup' || $lines > 1 && $this->account_selection == 'primary_group')) { - $js = "window.open('$link','uiaccountsel','$popup_options'); return false;"; $html .= html::submit_button('search','Search accounts',$js,false, - ' title="'.html::htmlspecialchars(lang('Search accounts')).'"','search','phpgwapi'); + ' title="'.html::htmlspecialchars(lang('Search accounts')). + '" class="uiaccountselection_trigger" id="'.$element_id.'_popup"','search','phpgwapi','button'); $need_js_popup = True; } elseif (!$only_groups && ($lines == 1 || $lines > 0 && $this->account_selection == 'primary_group')) { - $js = "if (selectBox = document.getElementById('$element_id')) if (!selectBox.multiple) { if(\$j(selectBox).unchosen) \$j(selectBox).unchosen(); selectBox.size=$multi_size; selectBox.multiple=true; if (selectBox.options[0].value=='') selectBox.options[0] = null;"; - if(count($select) > html::SELECT_ENHANCED_ROW_COUNT || true) - { - $js .= "\$j(selectBox).css('width','100%'); "; - } - if (!in_array($this->account_selection,array('groupmembers','selectbox'))) // no popup! - { - $js .= " this.src='".common::image('phpgwapi','search')."'; this.title='". - html::htmlspecialchars(lang('Search accounts'))."';} else {window.open('$link','uiaccountsel','$popup_options');"; - $need_js_popup = True; - } - else - { - $js .= "this.style.display='none'; selectBox.style.width='100%';"; - } - $js .= "} return false;"; - $html .= html::submit_button('search','Select multiple accounts',$js,false, - ' title="'.html::htmlspecialchars(lang('Select multiple accounts')).'"','users','phpgwapi'); - } - // jDots needs a little re-do, since it plays with the layout - $html .= ""; - if($need_js_popup && !$GLOBALS['egw_info']['flags']['uiaccountsel']['addOption_installed']) - { - $html .= ''; - $GLOBALS['egw_info']['flags']['uiaccountsel']['addOption_installed'] = True; + $html .= html::submit_button('search','Select multiple accounts','',false, + ' title="'.html::htmlspecialchars(lang('Select multiple accounts')). + '" class="uiaccountselection_trigger" id="'.$element_id.'_multiple"','users','phpgwapi','button'); } return $html; } function popup() { + // switch CSP to script-src 'unsafe-inline', until code get fixed here to work without + egw_framework::csp_script_src_attrs('unsafe-inline'); + global $query; // nextmatch requires that !!! $app = get_var('app',array('POST','GET')); @@ -477,7 +418,7 @@ function addOption(id,label,value,do_onchange) { $link_data['group_id'] = $group['account_id']; - $GLOBALS['egw']->template->set_var('onclick',"addOption('$element_id','". + $GLOBALS['egw']->template->set_var('onclick',"ownAddOption('$element_id','". addslashes(common::grab_owner_name($group['account_id']))."','$group[account_id]',".(int)($multiple==1).")". (!$multiple ? '; window.close()' : '')); @@ -491,7 +432,7 @@ function addOption(id,label,value,do_onchange) if($use == 'both') // allow selection of groups { $GLOBALS['egw']->template->fp('cal','group_cal',True); - $GLOBALS['egw']->template->set_var('js_addAllGroups',"addOption('$element_id','". + $GLOBALS['egw']->template->set_var('js_addAllGroups',"ownAddOption('$element_id','". addslashes(common::grab_owner_name($group['account_id']))."','$group[account_id]',".(int)($multiple==1).")". (!$multiple ? '; window.close();' : ';')); $GLOBALS['egw']->template->fp('selectAllGroups','group_selectAll',True); @@ -555,12 +496,12 @@ function addOption(id,label,value,do_onchange) 'lid' => $user['account_lid'], 'firstname' => $user['account_firstname'] ? $user['account_firstname'] : ' ', 'lastname' => $user['account_lastname'] ? $user['account_lastname'] : ' ', - 'onclick' => "addOption('$element_id','". + 'onclick' => "ownAddOption('$element_id','". addslashes(common::grab_owner_name($user['account_id']))."','$user[account_id]',".(int)($multiple==1).")". (!$multiple ? '; window.close()' : ''), )); $GLOBALS['egw']->template->fp('list','accounts_list',True); - $GLOBALS['egw']->template->set_var('js_addAllAccounts',"addOption('$element_id','". + $GLOBALS['egw']->template->set_var('js_addAllAccounts',"ownAddOption('$element_id','". addslashes(common::grab_owner_name($user['account_id']))."','$user[account_id]',".(int)($multiple==1).")". (!$multiple ? '; window.close()' : ';')); $GLOBALS['egw']->template->fp('selectAllAccounts','accounts_selectAll',True); diff --git a/phpgwapi/js/jsapi/jsapi.js b/phpgwapi/js/jsapi/jsapi.js index 3b873394d0..69ef07c780 100644 --- a/phpgwapi/js/jsapi/jsapi.js +++ b/phpgwapi/js/jsapi/jsapi.js @@ -794,7 +794,14 @@ function dropdown_menu_hack(el) */ function egw_link_handler(_link, _app) { - window.location.href = _link; + if (window.framework) + { + window.framework.linkHandler(_link, _app); + } + else + { + window.location.href = _link; + } } /** @@ -843,3 +850,72 @@ function egw_preferences(name, apps) egw_link_handler(egw_webserverUrl+url, current_app); } } + +/** + * Support functions for uiaccountselection class + * + * @ToDo: should be removed if uiaccountsel class is no longer in use + */ +function addOption(id,label,value,do_onchange) +{ + selectBox = document.getElementById(id); + for (var i=0; i < selectBox.length; i++) { + // check existing entries if they're already there and only select them in that case + if (selectBox.options[i].value == value) { + selectBox.options[i].selected = true; + break; + } + } + if (i >= selectBox.length) { + if (!do_onchange) { + if (selectBox.length && selectBox.options[0].value=='') selectBox.options[0] = null; + selectBox.multiple=true; + selectBox.size=4; + } + selectBox.options[selectBox.length] = new Option(label,value,false,true); + } + if (selectBox.onchange && do_onchange) selectBox.onchange(); +} +/** + * Install click handlers for popup and multiple triggers of uiaccountselection + */ +$j(function(){ + $j(document).on('click', 'input.uiaccountselection_trigger',function(){ + var selectBox = document.getElementById(this.id.replace(/(_multiple|_popup)$/, '')); + if (selectBox) + { + var link = selectBox.getAttribute('data-popup-link'); + + if (selectBox.multiple || this.id.match(/_popup$/)) + { + window.open(link, 'uiaccountsel', 'width=600,height=420,toolbar=no,scrollbars=yes,resizable=yes'); + } + else + { + selectBox.size = 4; + selectBox.multiple = true; + if (selectBox.options[0].value=='') selectBox.options[0] = null; + + if (!$j(selectBox).hasClass('groupmembers') && !$j(selectBox).hasClass('selectbox')) // no popup! + { + this.src = egw.image('search'); + this.title = egw.lang('Search accounts'); + } + else + { + this.style.display = 'none'; + selectBox.style.width = '100%'; + } + } + } + }); + $j(document).on('change', 'select.uiaccountselection',function(e){ + if (this.value == 'popup') + { + var link = this.getAttribute('data-popup-link'); + window.open(link, 'uiaccountsel', 'width=600,height=420,toolbar=no,scrollbars=yes,resizable=yes'); + e.preventDefault(); + } + }); +}); + diff --git a/phpgwapi/templates/default/uiaccountsel.tpl b/phpgwapi/templates/default/uiaccountsel.tpl index c75147b404..b33bb81889 100644 --- a/phpgwapi/templates/default/uiaccountsel.tpl +++ b/phpgwapi/templates/default/uiaccountsel.tpl @@ -3,7 +3,7 @@ ", + 'text' => $selectbox, 'no_lang' => True, 'link' => False )