/** * Class iTopWebPage * * @copyright Copyright (C) 2010-2012 Combodo SARL * @license http://opensource.org/licenses/AGPL-3.0 */ require_once(APPROOT."/application/nicewebpage.class.inc.php"); require_once(APPROOT."/application/applicationcontext.class.inc.php"); require_once(APPROOT."/application/user.preferences.class.inc.php"); /** * Web page with some associated CSS and scripts (jquery) for a fancier display */ class iTopWebPage extends NiceWebPage { private $m_sMenu; // private $m_currentOrganization; private $m_aTabs; private $m_sCurrentTabContainer; private $m_sCurrentTab; private $m_sMessage; private $m_sInitScript; public function __construct($sTitle) { parent::__construct($sTitle); ApplicationContext::SetUrlMakerClass('iTopStandardURLMaker'); $this->m_sCurrentTabContainer = ''; $this->m_sCurrentTab = ''; $this->m_aTabs = array(); $this->m_sMenu = ""; $this->m_sMessage = ''; $this->SetRootUrl(utils::GetAbsoluteUrlAppRoot()); $this->add_header("Content-type: text/html; charset=utf-8"); $this->add_header("Cache-control: no-cache"); $this->add_linked_stylesheet("../css/jquery.treeview.css"); $this->add_linked_stylesheet("../css/jquery.autocomplete.css"); $this->add_linked_stylesheet("../css/fg.menu.css"); $this->add_linked_stylesheet("../css/jquery.multiselect.css"); $this->add_linked_script('../js/jquery.layout.min.js'); $this->add_linked_script('../js/jquery.ba-bbq.min.js'); $this->add_linked_script("../js/jquery.treeview.js"); $this->add_linked_script("../js/jquery.autocomplete.js"); $this->add_linked_script("../js/date.js"); $this->add_linked_script("../js/jquery.blockUI.js"); $this->add_linked_script("../js/utils.js"); $this->add_linked_script("../js/swfobject.js"); $this->add_linked_script("../js/ckeditor/ckeditor.js"); $this->add_linked_script("../js/ckeditor/adapters/jquery.js"); $this->add_linked_script("../js/jquery.qtip-1.0.min.js"); $this->add_linked_script('../js/property_field.js'); $this->add_linked_script('../js/fg.menu.js'); $this->add_linked_script('../js/icon_select.js'); $this->add_linked_script('../js/raphael-min.js'); $this->add_linked_script('../js/g.raphael.js'); $this->add_linked_script('../js/g.pie.js'); $this->add_linked_script('../js/g.dot.js'); $this->add_linked_script('../js/charts.js'); $this->add_linked_script('../js/jquery.multiselect.min.js'); $this->add_linked_script('../js/ajaxfileupload.js'); $sSearchAny = addslashes(Dict::S('UI:SearchValue:Any')); $sSearchNbSelected = addslashes(Dict::S('UI:SearchValue:NbSelected')); $this->m_sInitScript = <<< EOF try { var myLayout; // a var is required because this page utilizes: myLayout.allowOverflow() method // Layout paneSize = GetUserPreference('menu_size', 300) myLayout = $('body').layout({ west : { minSize: 200, size: paneSize, spacing_open: 16, spacing_close: 16, slideTrigger_open: "mouseover", hideTogglerOnSlide: true, enableCursorHotkey: false, onclose_end: function(name, elt, state, options, layout) { if (state.isSliding == false) { SetUserPreference('menu_pane', 'closed', true); } }, onresize_end: function(name, elt, state, options, layout) { if (state.isSliding == false) { SetUserPreference('menu_size', state.size, true); } }, onopen_end: function(name, elt, state, options, layout) { if (state.isSliding == false) { SetUserPreference('menu_pane', 'open', true); } } }, center: { onresize_end: function(name, elt, state, options, layout) { $('.v-resizable').each( function() { var fixedWidth = $(this).parent().innerWidth() - 6; $(this).width(fixedWidth); // Make sure it cannot be resized horizontally $(this).resizable('options', { minWidth: fixedWidth, maxWidth: fixedWidth }); // Now adjust all the child 'items' var innerWidth = $(this).innerWidth() - 10; $(this).find('.item').width(innerWidth); }); } } }); myLayout.addPinBtn( "#tPinMenu", "west" ); //myLayout.open( "west" ); $('.ui-layout-resizer-west .ui-layout-toggler').css({background: 'transparent'}); if (GetUserPreference('menu_pane', 'open') == 'closed') { myLayout.close('west'); } $('#left-pane').layout({ resizable: false, spacing_open: 0, south: { size: 94 }, enableCursorHotkey: false }); // Accordion Menu $("#accordion").accordion({ header: "h3", navigation: true, autoHeight: false, collapsible: false, icons: false }); // collapsible will be enabled once the item will be selected // Tabs, using JQuery BBQ to store the history // The "tab widgets" to handle. var tabs = $('div[id^=tabbedContent]'); // This selector will be reused when selecting actual tab widget A elements. var tab_a_selector = 'ul.ui-tabs-nav a'; // Enable tabs on all tab widgets. The `event` property must be overridden so // that the tabs aren't changed on click, and any custom event name can be // specified. Note that if you define a callback for the 'select' event, it // will be executed for the selected tab whenever the hash changes. tabs.tabs({ event: 'change', 'show': function(event, ui) { $('.resizable', ui.panel).resizable(); // Make resizable everything that claims to be resizable ! } }); $('.multiselect').multiselect({header: false, noneSelectedText: '$sSearchAny', selectedList: 1, selectedText:'$sSearchNbSelected'}); $('.resizable').filter(':visible').resizable(); } catch(err) { // Do something with the error ! alert(err); } EOF ; $this->add_ready_script( <<< EOF // Adjust initial size $('.v-resizable').each( function() { var parent_id = $(this).parent().id; // Restore the saved height var iHeight = GetUserPreference(parent_id+'_'+this.id+'_height', undefined); if (iHeight != undefined) { $(this).height(parseInt(iHeight, 10)); // Parse in base 10 !); } // Adjust the child 'item''s height and width to fit var container = $(this); var fixedWidth = container.parent().innerWidth() - 6; // Set the width to fit the parent $(this).width(fixedWidth); var headerHeight = $(this).find('.drag_handle').height(); // Now adjust the width and height of the child 'item' container.find('.item').height(container.innerHeight() - headerHeight - 12).width(fixedWidth - 10); } ); // Make resizable, vertically only everything that claims to be v-resizable ! $('.v-resizable').resizable( { handles: 's', minHeight: $(this).find('.drag_handle').height(), minWidth: $(this).parent().innerWidth() - 6, maxWidth: $(this).parent().innerWidth() - 6, stop: function() { // Adjust the content var container = $(this); var headerHeight = $(this).find('.drag_handle').height(); container.find('.item').height(container.innerHeight() - headerHeight - 12);//.width(container.innerWidth()); var parent_id = $(this).parent().id; SetUserPreference(parent_id+'_'+this.id+'_height', $(this).height(), true); // true => persistent } } ); // Tabs, using JQuery BBQ to store the history // The "tab widgets" to handle. var tabs = $('div[id^=tabbedContent]'); // This selector will be reused when selecting actual tab widget A elements. var tab_a_selector = 'ul.ui-tabs-nav a'; // Define our own click handler for the tabs, overriding the default. tabs.find( tab_a_selector ).click(function() { var state = {}; // Get the id of this tab widget. var id = $(this).closest( 'div[id^=tabbedContent]' ).attr( 'id' ); // Get the index of this tab. var idx = $(this).parent().prevAll().length; // Set the state! state[ id ] = idx; $.bbq.pushState( state ); }); // Bind an event to window.onhashchange that, when the history state changes, // iterates over all tab widgets, changing the current tab as necessary. $(window).bind( 'hashchange', function(e) { // Iterate over all tab widgets. tabs.each(function() { // Get the index for this tab widget from the hash, based on the // appropriate id property. In jQuery 1.4, you should use e.getState() // instead of $.bbq.getState(). The second, 'true' argument coerces the // string value to a number. var idx = $.bbq.getState( this.id, true ) || 0; // Select the appropriate tab for this tab widget by triggering the custom // event specified in the .tabs() init above (you could keep track of what // tab each widget is on using .data, and only select a tab if it has // changed). $(this).find( tab_a_selector ).eq( idx ).triggerHandler( 'change' ); }); // Iterate over all truncated lists to find whether they are expanded or not $('a.truncated').each(function() { var state = $.bbq.getState( this.id, true ) || 'close'; if (state == 'open') { $(this).trigger('open'); } else { $(this).trigger('close'); } }); }); // Shortcut menu actions $('.actions_button a').click( function() { aMatches = /#(.*)$/.exec(window.location.href); if (aMatches != null) { currentHash = aMatches[1]; if ( /#(.*)$/.test(this.href)) { this.href = this.href.replace(/#(.*)$/, '#'+currentHash); } } }); // End of Tabs handling $(".date-pick").datepicker({ showOn: 'button', buttonImage: '../images/calendar.png', buttonImageOnly: true, dateFormat: 'yy-mm-dd', constrainInput: false, changeMonth: true, changeYear: true }); $(".datetime-pick").datepicker({ showOn: 'button', buttonImage: '../images/calendar.png', buttonImageOnly: true, dateFormat: 'yy-mm-dd 00:00:00', constrainInput: false, changeMonth: true, changeYear: true }); // Make sortable, everything that claims to be sortable $('.sortable').sortable( {axis: 'y', cursor: 'move', handle: '.drag_handle', stop: function() { if ($(this).hasClass('persistent')) { // remember the sort order for next time the page is loaded... sSerialized = $(this).sortable('serialize', {key: 'menu'}); var sTemp = sSerialized.replace(/menu=/g, ''); SetUserPreference(this.id+'_order', sTemp.replace(/&/g, ','), true); // true => persistent ! } } }); docWidth = $(document).width(); $('#ModalDlg').dialog({ autoOpen: false, modal: true, width: 0.8*docWidth }); // JQuery UI dialogs ShowDebug(); $('#logOffBtn>ul').popupmenu(); $('.caselog_header').click( function () { $(this).toggleClass('open').next('.caselog_entry').toggle(); }); EOF ); $sUserPrefs = appUserPreferences::GetAsJSON(); $this->add_script( << 0) { window.location.href = AddAppContext(GetAbsoluteUrlAppRoot()+'pages/UI.php?operation=details&class='+sClass+'&id='+id); } else { window.location.href = sDefaultUrl; // Already contains the context... } } function BackToList(sClass) { window.location.href = AddAppContext(GetAbsoluteUrlAppRoot()+'pages/UI.php?operation=search_oql&oql_class='+sClass+'&oql_clause=WHERE id=0'); } function ShowDebug() { if ($('#rawOutput > div').html() != '') { $('#rawOutput').dialog( {autoOpen: true, modal:false}); } } var oUserPreferences = $sUserPrefs; // For disabling the CKEditor at init time when the corresponding textarea is disabled ! CKEDITOR.plugins.add( 'disabler', { init : function( editor ) { editor.on( 'instanceReady', function(e) { e.removeListener(); $('#'+ editor.name).trigger('update'); }); } }); EOF ); } public function AddToMenu($sHtml) { $this->m_sMenu .= $sHtml; } public function GetSiloSelectionForm() { // List of visible Organizations $iCount = 0; $oSet = null; if (MetaModel::IsValidClass('Organization')) { // Display the list of *favorite* organizations... but keeping in mind what is the real number of organizations $aFavoriteOrgs = appUserPreferences::GetPref('favorite_orgs', null); $oSearchFilter = new DBObjectSearch('Organization'); $oSearchFilter->SetModifierProperty('UserRightsGetSelectFilter', 'bSearchMode', true); $oSet = new CMDBObjectSet($oSearchFilter); $iCount = $oSet->Count(); // total number of existing Orgs // Now get the list of Orgs to be displayed in the menu $oSearchFilter = DBObjectSearch::FromOQL(ApplicationMenu::GetFavoriteSiloQuery()); $oSearchFilter->SetModifierProperty('UserRightsGetSelectFilter', 'bSearchMode', true); if (!empty($aFavoriteOrgs)) { $oSearchFilter->AddCondition('id', $aFavoriteOrgs, 'IN'); } $oSet = new CMDBObjectSet($oSearchFilter); // List of favorite orgs } switch($iCount) { case 0: // No such dimension/silo => nothing to select $sHtml = '
'; break; case 1: // Only one possible choice... no selection, but display the value $oOrg = $oSet->Fetch(); $sHtml = '
'.$oOrg->GetName().'
'; $sHtml .= ''; break; default: $sHtml = ''; $oAppContext = new ApplicationContext(); $iCurrentOrganization = $oAppContext->GetCurrentValue('org_id'); $sHtml = '
'; $sHtml .= '
'; //'; $sHtml .= ''; $sHtml .= ''; $sHtml .= ''.self::FilterXSS($sLogOffMenu).'
'; //echo '        '; $sHtml .= ' '; $sHtml .= '
'; $sHtml .= ' '; $sHtml .= self::FilterXSS($this->s_content); $sHtml .= ' '; $sHtml .= '
'; $sHtml .= ''; // Add the captured output if (trim($s_captured_output) != "") { $sHtml .= "
".self::FilterXSS($s_captured_output)."
\n"; } $sHtml .= "
".self::FilterXSS($this->s_deferred_content)."
"; $sHtml .= "
Please wait...
\n"; // jqModal Window $sHtml .= "
"; $sHtml .= "
"; } else { $sHtml .= self::FilterXSS($this->s_content); } $sHtml .= "\n"; $sHtml .= "\n"; if ($this->GetOutputFormat() == 'html') { echo $sHtml; } else if ($this->GetOutputFormat() == 'pdf' && $this->IsOutputFormatAvailable('pdf') ) { require_once(APPROOT.'lib/MPDF/mpdf.php'); $oMPDF = new mPDF('c'); $oMPDF->mirroMargins = false; if ($this->a_base['href'] != '') { $oMPDF->setBasePath($this->a_base['href']); // Seems that the tag is not recognized by mPDF... } $oMPDF->showWatermarkText = true; if ($this->GetOutputOption('pdf', 'template_path')) { $oMPDF->setImportUse(); // Allow templates $oMPDF->SetDocTemplate ($this->GetOutputOption('pdf', 'template_path'), 1); } $oMPDF->WriteHTML($sHtml); $sOutputName = $this->s_title.'.pdf'; if ($this->GetOutputOption('pdf', 'output_name')) { $sOutputName = $this->GetOutputOption('pdf', 'output_name'); } $oMPDF->Output($sOutputName, 'I'); } } public function AddTabContainer($sTabContainer) { $this->m_aTabs[$sTabContainer] = array(); $this->add("\$Tabs:$sTabContainer\$"); } public function AddToTab($sTabContainer, $sTabLabel, $sHtml) { if (!isset($this->m_aTabs[$sTabContainer][$sTabLabel])) { // Set the content of the tab $this->m_aTabs[$sTabContainer][$sTabLabel] = $sHtml; } else { // Append to the content of the tab $this->m_aTabs[$sTabContainer][$sTabLabel] .= $sHtml; } } public function SetCurrentTabContainer($sTabContainer = '') { $sPreviousTabContainer = $this->m_sCurrentTabContainer; $this->m_sCurrentTabContainer = $sTabContainer; return $sPreviousTabContainer; } public function SetCurrentTab($sTabLabel = '') { $sPreviousTab = $this->m_sCurrentTab; $this->m_sCurrentTab = $sTabLabel; return $sPreviousTab; } public function GetCurrentTab() { return $this->m_sCurrentTab; } public function RemoveTab($sTabLabel, $sTabContainer = null) { if ($sTabContainer == null) { $sTabContainer = $this->m_sCurrentTabContainer; } if (isset($this->m_aTabs[$sTabContainer][$sTabLabel])) { // Delete the content of the tab unset($this->m_aTabs[$sTabContainer][$sTabLabel]); // If we just removed the active tab, let's reset the active tab if (($this->m_sCurrentTabContainer == $sTabContainer) && ($this->m_sCurrentTab == $sTabLabel)) { $this->m_sCurrentTab = ''; } } } /** * Finds the tab whose title matches a given pattern * @return mixed The name of the tab as a string or false if not found */ public function FindTab($sPattern, $sTabContainer = null) { $return = false; if ($sTabContainer == null) { $sTabContainer = $this->m_sCurrentTabContainer; } foreach($this->m_aTabs[$sTabContainer] as $sTabLabel => $void) { if (preg_match($sPattern, $sTabLabel)) { $result = $sTabLabel; break; } } return $result; } /** * Make the given tab the active one, as if it were clicked * DOES NOT WORK: apparently in the *old* version of jquery * that we are using this is not supported... TO DO upgrade * the whole jquery bundle... */ public function SelectTab($sTabContainer, $sTabLabel) { $container_index = 0; $tab_index = 0; foreach($this->m_aTabs as $sCurrentTabContainerName => $aTabs) { if ($sTabContainer == $sCurrentTabContainerName) { foreach($aTabs as $sCurrentTabLabel => $void) { if ($sCurrentTabLabel == $sTabLabel) { break; } $tab_index++; } break; } $container_index++; } $sSelector = '#tabbedContent_'.$container_index.' > ul'; $this->add_ready_script("$('$sSelector').tabs('select', $tab_index);"); } public function StartCollapsibleSection($sSectionLabel, $bOpen = false) { $this->add($this->GetStartCollapsibleSection($sSectionLabel, $bOpen)); } public function GetStartCollapsibleSection($sSectionLabel, $bOpen = false) { $sHtml = ''; static $iSectionId = 0; $sImgStyle = $bOpen ? ' open' : ''; $sHtml .= "
$sSectionLabel
\n"; $sStyle = $bOpen ? '' : 'style="display:none" '; $sHtml .= "
"; $this->add_ready_script("\$(\"#LnkCollapse_$iSectionId\").click(function() {\$(\"#Collapse_$iSectionId\").slideToggle('normal'); $(\"#LnkCollapse_$iSectionId\").toggleClass('open');});"); //$this->add_ready_script("$('#LnkCollapse_$iSectionId').hide();"); $iSectionId++; return $sHtml; } public function EndCollapsibleSection() { $this->add($this->GetEndCollapsibleSection()); } public function GetEndCollapsibleSection() { return "
"; } public function add($sHtml) { if (!empty($this->m_sCurrentTabContainer) && !empty($this->m_sCurrentTab)) { $this->AddToTab($this->m_sCurrentTabContainer, $this->m_sCurrentTab, $sHtml); } else { parent::add($sHtml); } } /** * Records the current state of the 'html' part of the page output * @return mixed The current state of the 'html' output */ public function start_capture() { if (!empty($this->m_sCurrentTabContainer) && !empty($this->m_sCurrentTab)) { $iOffset = isset($this->m_aTabs[$this->m_sCurrentTabContainer][$this->m_sCurrentTab]) ? strlen($this->m_aTabs[$this->m_sCurrentTabContainer][$this->m_sCurrentTab]): 0; return array('tc' => $this->m_sCurrentTabContainer, 'tab' => $this->m_sCurrentTab, 'offset' => $iOffset); } else { return parent::start_capture(); } } /** * Returns the part of the html output that occurred since the call to start_capture * and removes this part from the current html output * @param $offset mixed The value returned by start_capture * @return string The part of the html output that was added since the call to start_capture */ public function end_capture($offset) { if (is_array($offset)) { if (isset($this->m_aTabs[$offset['tc']][$offset['tab']])) { $sCaptured = substr($this->m_aTabs[$offset['tc']][$offset['tab']], $offset['offset']); $this->m_aTabs[$offset['tc']][$offset['tab']] = substr($this->m_aTabs[$offset['tc']][$offset['tab']], 0, $offset['offset']); } else { $sCaptured = ''; } } else { $sCaptured = parent::end_capture($offset); } return $sCaptured; } /** * Set the message to be displayed in the 'admin-banner' section at the top of the page */ public function SetMessage($sMessage) { $this->m_sMessage = $sMessage; } } ?>