* @author Romain Quetiez * @author Denis Flaven * @license http://www.opensource.org/licenses/gpl-3.0.html LGPL */ 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; public function __construct($sTitle) { parent::__construct($sTitle); $this->m_sCurrentTabContainer = ''; $this->m_sCurrentTab = ''; $this->m_aTabs = array(); $this->m_sMenu = ""; $oAppContext = new ApplicationContext(); $sExtraParams = $oAppContext->GetForLink(); // $this->m_currentOrganization = $currentOrganization; $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/date.picker.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.dimensions.js"); $this->add_linked_script("../js/jquery.tablehover.js"); $this->add_linked_script("../js/jquery.treeview.js"); $this->add_linked_script("../js/jquery.autocomplete.js"); $this->add_linked_script("../js/jquery.bgiframe.js"); $this->add_linked_script("../js/jquery.positionBy.js"); $this->add_linked_script("../js/jquery.popupmenu.js"); $this->add_linked_script("../js/date.js"); // $this->add_linked_script("../js/jquery.date.picker.js"); $this->add_linked_script("../js/jquery.tablesorter.min.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_ready_script( <<'); if (GetUserPreference('menu_pane', 'open') == 'closed') { myLayout.close('west'); } // Accordion Menu $("#accordion").accordion({ header: "h3", navigation: true, autoHeight: false, collapsible: false, icons: false }); // collapsible will be enabled once the item will be selected }); //add new widget called TruncatedList to properly display truncated lists when they are sorted $.tablesorter.addWidget({ // give the widget a id id: "truncatedList", // format is called when the on init and when a sorting has finished format: function(table) { // Check if there is a "truncated" line this.truncatedList = false; if ($("tr td.truncated",table).length > 0) { this.truncatedList = true; } if (this.truncatedList) { $("tr td",table).removeClass('truncated'); $("tr:last td",table).addClass('truncated'); } } }); $.tablesorter.addWidget({ // give the widget a id id: "myZebra", // format is called when the on init and when a sorting has finished format: function(table) { // Replace the 'red even' lines by 'red_even' since most browser do not support 2 classes selector in CSS, etc.. $("tbody tr:even",table).addClass('even'); $("tbody tr.red:even",table).removeClass('red').removeClass('even').addClass('red_even'); $("tbody tr.orange:even",table).removeClass('orange').removeClass('even').addClass('orange_even'); $("tbody tr.green:even",table).removeClass('green').removeClass('even').addClass('green_even'); // In case we sort again the table, we need to remove the added 'even' classes on odd rows $("tbody tr:odd",table).removeClass('even'); $("tbody tr.red_even:odd",table).removeClass('even').removeClass('red_even').addClass('red'); $("tbody tr.orange_even:odd",table).removeClass('even').removeClass('orange_even').addClass('orange'); $("tbody tr.green_even:odd",table).removeClass('even').removeClass('green_even').addClass('green'); } }); $('.resizable').resizable(); // Make resizable everything that claims to be resizable ! // 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'; // 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' }); // 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'); } }); }); // End of Tabs handling $("table.listResults").tableHover(); // hover tables // Check each 'listResults' table for a checkbox in the first column and make the first column sortable only if it does not contain a checkbox in the header $(".listResults").each( function() { var table = $(this); var id = $(this).parent(); var checkbox = (table.find('th:first :checkbox').length > 0); if (checkbox) { // There is a checkbox in the first column, don't make it sortable table.tablesorter( { headers: { 0: {sorter: false}}, widgets: ['myZebra', 'truncatedList']} ); // sortable and zebra tables } else { // There is NO checkbox in the first column, all columns are considered sortable table.tablesorter( { widgets: ['myZebra', 'truncatedList']} ); // sortable and zebra tables } }); $(".date-pick").datepicker({ showOn: 'button', buttonImage: '../images/calendar.png', buttonImageOnly: true, dateFormat: 'yy-mm-dd', constrainInput: false, changeMonth: true, changeYear: true }); // Restore the persisted sortable order, for all sortable lists... if any $('.sortable').each(function() { var sTemp = GetUserPreference(this.id+'_order', undefined); if (sTemp != undefined) { var aSerialized = sTemp.split(','); var sortable = $(this); $.each(aSerialized, function(i,v) { var item = $('#menu_'+v); if (item.length > 0) // Check that the menu exists { sortable.append(item); } }); } }); // 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(); // $.history.init(history_callback); // $("a[rel='history']").click(function() // { // $.history.load(this.href.replace(/^.*#/, '')); // return false; // }); } catch(err) { // Do something with the error ! alert(err); } //$('.display_block').draggable(); // make the blocks draggable EOF ); $sUserPrefs = appUserPreferences::GetAsJSON(); $this->add_script( << 0) { window.location.href = './UI.php?operation=details&class='+sClass+'&id='+id; } else { window.location.href = sDefaultUrl; } } function BackToList(sClass) { window.location.href = './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; EOF ); // Build menus from module handlers // foreach(get_declared_classes() as $sPHPClass) { if (is_subclass_of($sPHPClass, 'ModuleHandlerAPI')) { $aCallSpec = array($sPHPClass, 'OnMenuCreation'); call_user_func($aCallSpec); } } } public function AddToMenu($sHtml) { $this->m_sMenu .= $sHtml; } public function GetSiloSelectionForm() { // List of visible Organizations $iCount = 0; if (MetaModel::IsValidClass('Organization')) { $oSearchFilter = new DBObjectSearch('Organization'); $oSet = new CMDBObjectSet($oSearchFilter); $iCount = $oSet->Count(); } 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: $oAppContext = new ApplicationContext(); $iCurrentOrganization = $oAppContext->GetCurrentValue('org_id'); $sHtml = '
'; $sHtml .= '
'; // Add other dimensions/context information to this form // $oAppContext = new ApplicationContext(); $oAppContext->Reset('org_id'); // org_id is handled above and we want to be able to change it here ! $sHtml .= $oAppContext->GetForForm(); $sHtml .= '
'; $sHtml .= '
'; } return $sHtml; } public function DisplayMenu() { // Display the menu $oAppContext = new ApplicationContext(); $iAccordionIndex = 0; ApplicationMenu::DisplayMenu($this, $oAppContext->GetAsHash()); } /** * Outputs (via some echo) the complete HTML page by assembling all its elements */ public function output() { $this->DisplayMenu(); // Compute the menu // Put here the 'ready scripts' that must be executed after all others $this->add_ready_script( <<a_headers as $s_header) { header($s_header); } $s_captured_output = ob_get_contents(); ob_end_clean(); echo "\n"; echo "\n"; echo "\n"; // Make sure that Internet Explorer renders the page using its latest/highest/greatest standards ! echo "\n"; echo "\n"; echo "{$this->s_title}\n"; echo $this->get_base_tag(); // Stylesheets MUST be loaded before any scripts otherwise // jQuery scripts may face some spurious problems (like failing on a 'reload') foreach($this->a_linked_stylesheets as $a_stylesheet) { if ($a_stylesheet['condition'] != "") { echo "\n"; } } foreach($this->a_linked_scripts as $s_script) { echo "\n"; } if (count($this->m_aReadyScripts)>0) { $this->add_script("\$(document).ready(function() {\n".implode("\n", $this->m_aReadyScripts)."\n});"); } if (count($this->a_scripts)>0) { echo "\n"; } if (count($this->a_styles)>0) { echo "\n"; } echo "\n"; echo "\n"; echo "\n"; // Render the revision number if (ITOP_REVISION == '$WCREV$') { // This is NOT a version built using the buil system, just display the main version $sVersionString = Dict::Format('UI:iTopVersion:Short', ITOP_VERSION); } else { // This is a build made from SVN, let display the full information $sVersionString = Dict::Format('UI:iTopVersion:Long', ITOP_VERSION, ITOP_REVISION, ITOP_BUILD_DATE); } // Render the text of the global search form $sText = Utils::ReadParam('text', ''); $sOnClick = ""; if (empty($sText)) { // if no search text is supplied then // 1) the search text is filled with "your search" // 2) clicking on it will erase it $sText = Dict::S("UI:YourSearch"); $sOnClick = " onclick=\"this.value='';this.onclick=null;\""; } $sForm = $this->GetSiloSelectionForm(); // Render the tabs in the page (if any) foreach($this->m_aTabs as $sTabContainerName => $m_aTabs) { $sTabs = ''; $container_index = 0; if (count($m_aTabs) > 0) { $sTabs = "\n
\n"; $sTabs .= "\n"; // Now add the content of the tabs themselves $i = 0; foreach($m_aTabs as $sTabName => $sTabContent) { $sTabs .= "
".$sTabContent."
\n"; $i++; } $sTabs .= "
\n\n"; } $this->s_content = str_replace("\$Tabs:$sTabContainerName\$", $sTabs, $this->s_content); $container_index++; } $sUserName = UserRights::GetUser(); $sIsAdmin = UserRights::IsAdministrator() ? '(Administrator)' : ''; if (UserRights::IsAdministrator()) { $sLogonMessage = Dict::Format('UI:LoggedAsMessage+Admin', $sUserName); } else { $sLogonMessage = Dict::Format('UI:LoggedAsMessage', $sUserName); } $sLogOffMenu = "\n"; $sRestrictions = ''; if (!MetaModel::DBHasAccess(ACCESS_ADMIN_WRITE)) { if (!MetaModel::DBHasAccess(ACCESS_ADMIN_WRITE)) { $sRestrictions = Dict::S('UI:AccessRO-All'); } } elseif (!MetaModel::DBHasAccess(ACCESS_USER_WRITE)) { $sRestrictions = Dict::S('UI:AccessRO-Users'); } if (strlen($sRestrictions) > 0) { $sAdminMessage = trim(MetaModel::GetConfig()->Get('access_message')); $sApplicationBanner = '
'; $sApplicationBanner .= ''; $sApplicationBanner .= ' '.$sRestrictions.''; if (strlen($sAdminMessage) > 0) { $sApplicationBanner .= ' '.$sAdminMessage.''; } $sApplicationBanner .= '
'; } else { $sApplicationBanner = ''; } $sOnlineHelpUrl = MetaModel::GetConfig()->Get('online_help'); //$sLogOffMenu = ""; echo '
'; echo ''; echo ' '; echo '
'; echo '
pin
'; echo '
'.$sForm.'
'; echo '
'; echo ' '; echo ' '; echo ''; echo '
'; echo '
'; echo '
'; echo $sApplicationBanner; echo ' '; //echo '        
'; echo '
'; echo '
'; echo ' '; echo $this->s_content; echo ' '; echo '
'; echo ''; /* echo "
iTop
\n"; //echo "
\n"; $sText = Utils::ReadParam('text', ''); $sOnClick = ""; if (empty($sText)) { // if no search text is supplied then // 1) the search text is filled with "your search" // 2) clicking on it will erase it $sText = Dict::S("UI:YourSearch"); $sOnClick = " onclick=\"this.value='';this.onclick=null;\""; } $sUserName = UserRights::GetUser(); $sIsAdmin = UserRights::IsAdministrator() ? '(Administrator)' : ''; if (UserRights::IsAdministrator()) { $sLogonMessage = Dict::Format('UI:LoggedAsMessage+Admin', $sUserName); } else { $sLogonMessage = Dict::Format('UI:LoggedAsMessage', $sUserName); } $sLogOffBtn = Dict::S('UI:Button:Logoff'); $sSearchBtn = Dict::S('UI:Button:GlobalSearch'); echo "
{$sLogonMessage}  "; echo "
\n"; echo "\n"; echo "\n"; echo "
\n"; echo "
\n"; echo "
\n"; echo "\n"; // Display the menu echo "
\n"; echo "
\n"; echo $this->m_sMenu; echo "
\n"; echo "
\n"; // Display the page's content echo $this->s_content; */ // Add the captured output if (trim($s_captured_output) != "") { echo "
$s_captured_output
\n"; } echo $this->s_deferred_content; echo "
Please wait...
\n"; // jqModal Window echo "
"; echo "
"; echo "\n"; echo "\n"; } 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; } /** * 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); } } /* public function AddSearchForm($sClassName, $bOpen = false) { $iSearchSectionId = 0; $sStyle = $bOpen ? 'SearchDrawer' : 'SearchDrawer DrawerClosed'; $this->add("
\n"); $this->add("

Search form for ".Metamodel::GetName($sClassName)."

\n"); $this->add_ready_script("\$(\"#LnkSearch_$iSearchSectionId\").click(function() {\$(\"#Search_$iSearchSectionId\").slideToggle('normal'); $(\"#LnkSearch_$iSearchSectionId\").toggleClass('open');});"); $oFilter = new DBObjectSearch($sClassName); $sFilter = $oFilter->serialize(); $oSet = new CMDBObjectSet($oFilter); cmdbAbstractObject::DisplaySearchForm($this, $oSet, array('operation' => 'search', 'filter' => $sFilter, 'search_form' => true)); $this->add("
\n"); $this->add("
\n"); $this->add("
Search
\n"); $iSearchSectionId++; } */ } ?>