瀏覽代碼

#485 Improved the end-user experience with Excel and the web queries (added a phrasebook) + link to test the OQL attributes (query phrasebook or email actions, etc.) including the handlink of query arguments) + fixed wrong prototypes for a few implementations of GetBareProperties()

git-svn-id: http://svn.code.sf.net/p/itop/code/trunk@1716 a333f486-631f-4898-b8df-5754b55c2be0
romainq 13 年之前
父節點
當前提交
dc0380550d

+ 1 - 0
application/application.inc.php

@@ -29,6 +29,7 @@ require_once(APPROOT.'/application/displayblock.class.inc.php');
 require_once(APPROOT.'/application/sqlblock.class.inc.php');
 require_once(APPROOT.'/application/audit.category.class.inc.php');
 require_once(APPROOT.'/application/audit.rule.class.inc.php');
+require_once(APPROOT.'/application/query.class.inc.php');
 //require_once(APPROOT.'/application/menunode.class.inc.php');
 require_once(APPROOT.'/application/utils.inc.php');
 

+ 16 - 1
application/cmdbabstract.class.inc.php

@@ -1653,6 +1653,7 @@ EOF
 					$sHTMLValue = "<input title=\"$sHelpText\" type=\"password\" size=\"30\" name=\"attr_{$sFieldPrefix}{$sAttCode}{$sNameSuffix}\" value=\"".htmlentities($value, ENT_QUOTES, 'UTF-8')."\" id=\"$iId\"/>&nbsp;{$sValidationField}";
 				break;
 				
+				case 'OQLExpression':
 				case 'Text':
 					$aEventsList[] ='validate';
 					$aEventsList[] ='keyup';
@@ -1674,7 +1675,21 @@ EOF
 					{
 						$sStyle = 'style="'.implode('; ', $aStyles).'"';
 					}
-					$sHTMLValue = "<table><tr><td><textarea class=\"resizable\" title=\"$sHelpText\" name=\"attr_{$sFieldPrefix}{$sAttCode}{$sNameSuffix}\" rows=\"8\" cols=\"40\" id=\"$iId\" $sStyle>".htmlentities($sEditValue, ENT_QUOTES, 'UTF-8')."</textarea></td><td>{$sValidationField}</td></tr></table>";
+					if ($oAttDef->GetEditClass() == 'OQLExpression')
+					{
+						$sTestResId = 'query_res_'.$sFieldPrefix.$sAttCode.$sNameSuffix; //$oPage->GetUniqueId();
+						$sBaseUrl = utils::GetAbsoluteUrlAppRoot().'pages/run_query.php?expression=';
+						$sInitialUrl = $sBaseUrl.urlencode($sEditValue);
+						$sAdditionalStuff = "<a id=\"$sTestResId\" target=\"_blank\" href=\"$sInitialUrl\">".Dict::S('UI:Edit:TestQuery')."</a>";
+						$oPage->add_ready_script("$('#$iId').bind('change keyup', function(evt, sFormId) { $('#$sTestResId').attr('href', '$sBaseUrl'+encodeURIComponent($(this).val())); } );");
+					}
+					else
+					{
+						$sAdditionalStuff = "";
+					}
+					// Ok, the text area is drawn here
+					$sHTMLValue = "<table><tr><td><textarea class=\"resizable\" title=\"$sHelpText\" name=\"attr_{$sFieldPrefix}{$sAttCode}{$sNameSuffix}\" rows=\"8\" cols=\"40\" id=\"$iId\" $sStyle>".htmlentities($sEditValue, ENT_QUOTES, 'UTF-8')."</textarea>$sAdditionalStuff</td><td>{$sValidationField}</td></tr></table>";
+
 				break;
 
 				case 'CaseLog':

+ 1 - 0
core/attributedef.class.inc.php

@@ -1908,6 +1908,7 @@ class AttributeIPAddress extends AttributeString
  */
 class AttributeOQL extends AttributeText
 {
+	public function GetEditClass() {return "OQLExpression";}
 }
 
 /**

+ 13 - 0
core/dbobjectsearch.class.php

@@ -737,11 +737,24 @@ class DBObjectSearch
 	{
 		return $this->m_aRelatedTo;
 	}
+
+	public function SetInternalParams($aParams)
+	{
+		return $this->m_aParams = $aParams;
+	}
+
 	public function GetInternalParams()
 	{
 		return $this->m_aParams;
 	}
 
+	public function GetQueryParams()
+	{
+		$aParams = array();
+		$this->m_oSearchCondition->Render($aParams, true);
+		return $aParams;
+	}
+
 	public function ListConstantFields()
 	{
 		return $this->m_oSearchCondition->ListConstantFields();

+ 1 - 1
core/event.class.inc.php

@@ -103,7 +103,7 @@ class Event extends DBObject implements iDisplay
 		$this->DisplayBareProperties($oPage, $bEditMode);
 	}
 	
-	function DisplayBareProperties(WebPage $oPage, $bEditMode = false, $aExtraParams = array())
+	function DisplayBareProperties(WebPage $oPage, $bEditMode = false, $sPrefix = '', $aExtraParams = array())
 	{
 		if ($bEditMode) return; // Not editable
 		

+ 2 - 11
core/expression.class.inc.php

@@ -282,16 +282,7 @@ class UnaryExpression extends Expression
 	// recursive rendering
 	public function Render(&$aArgs = null, $bRetrofitParams = false)
 	{
-		if ($bRetrofitParams)
-		{
-			$iParamIndex = count($aArgs) + 1; // 1-based indexation
-			$aArgs['param'.$iParamIndex] = $this->m_value;
-			return ':param'.$iParamIndex;
-		}
-		else
-		{
-			return CMDBSource::Quote($this->m_value);
-		}
+		return CMDBSource::Quote($this->m_value);
 	}
 
 	public function GetUnresolvedFields($sAlias, &$aUnresolved)
@@ -483,7 +474,7 @@ class VariableExpression extends UnaryExpression
 		}
 		elseif ($bRetrofitParams)
 		{
-			//$aArgs[$this->m_sName] = null;
+			$aArgs[$this->m_sName] = null;
 			return ':'.$this->m_sName;
 		}
 		else

+ 23 - 3
dictionaries/dictionary.itop.ui.php

@@ -76,6 +76,24 @@ Dict::Add('EN US', 'English', 'English', array(
 	'Class:AuditRule/Attribute:category_name+' => 'Name of the category for this rule',
 ));
 
+//
+// Class: QueryOQL
+//
+
+Dict::Add('EN US', 'English', 'English', array(
+	'Class:Query' => 'Query',
+	'Class:Query+' => 'A query is a data set defined in a dynamic way',
+	'Class:Query/Attribute:name' => 'Name',
+	'Class:Query/Attribute:name+' => 'Identifies the query',
+	'Class:Query/Attribute:description' => 'Description',
+	'Class:Query/Attribute:description+' => 'Long description for the query (purpose, usage, etc.)',
+
+	'Class:QueryOQL' => 'OQL Query',
+	'Class:QueryOQL+' => 'A query based on the Object Query Language',
+	'Class:QueryOQL/Attribute:oql' => 'Expression',
+	'Class:QueryOQL/Attribute:oql+' => 'OQL Expression',
+));
+
 //////////////////////////////////////////////////////////////////////
 // Classes in 'addon/userrights'
 //////////////////////////////////////////////////////////////////////
@@ -548,7 +566,7 @@ Dict::Add('EN US', 'English', 'English', array(
 	'UI:RunQuery:DevelopedQuery' => 'Redevelopped query expression: ',
 	'UI:RunQuery:SerializedFilter' => 'Serialized filter: ',
 	'UI:RunQuery:Error' => 'An error occured while running the query: %1$s',
-	
+	'UI:Query:UrlForExcel' => 'URL to use for MS-Excel web queries',
 	'UI:Schema:Title' => 'iTop objects schema',
 	'UI:Schema:CategoryMenuItem' => 'Category <b>%1$s</b>',
 	'UI:Schema:Relationships' => 'Relationships',
@@ -608,8 +626,8 @@ Dict::Add('EN US', 'English', 'English', array(
 	'UI:Schema:LifeCycleAttributeMustChange' => 'Must change',
 	'UI:Schema:LifeCycleAttributeMustPrompt' => 'User will be prompted to change the value',
 	'UI:Schema:LifeCycleEmptyList' => 'empty list',
-	
 	'UI:LinksWidget:Autocomplete+' => 'Type the first 3 characters...',
+	'UI:Edit:TestQuery' => 'Test query',
 	'UI:Combo:SelectValue' => '--- select a value ---',
 	'UI:Label:SelectedObjects' => 'Selected objects: ',
 	'UI:Label:AvailableObjects' => 'Available objects: ',
@@ -622,7 +640,6 @@ Dict::Add('EN US', 'English', 'English', array(
 	'UI:RemoveLinkedObjectsOf_Class' => 'Remove selected objects',
 	'UI:Message:EmptyList:UseAdd' => 'The list is empty, use the "Add..." button to add elements.',
 	'UI:Message:EmptyList:UseSearchForm' => 'Use the search form above to search for objects to be added.',
-	
 	'UI:Wizard:FinalStepTitle' => 'Final step: confirmation',
 	'UI:Title:DeletionOf_Object' => 'Deletion of %1$s',
 	'UI:Title:BulkDeletionOf_Count_ObjectsOf_Class' => 'Bulk deletion of %1$d objects of class %2$s',
@@ -825,6 +842,9 @@ When associated with a trigger, each action is given an "order" number, specifyi
 	'Menu:RunQueriesMenu' => 'Run Queries',
 	'Menu:RunQueriesMenu+' => 'Run any query',
 	
+	'Menu:QueryMenu' => 'Query phrasebook',
+	'Menu:QueryMenu+' => 'Query phrasebook',
+	
 	'Menu:DataAdministration' => 'Data administration',
 	'Menu:DataAdministration+' => 'Data administration',
 	

+ 14 - 0
dictionaries/fr.dictionary.itop.ui.php

@@ -50,6 +50,16 @@ Dict::Add('FR FR', 'French', 'Français', array(
 	'Class:AuditCategory/Attribute:definition_set+' => 'Expression OQL qui défini le périmètre d\'application de l\'audit',
 	'Class:AuditCategory/Attribute:rules_list' => 'Règles d\'audit',
 	'Class:AuditCategory/Attribute:rules_list+' => 'Règles d\'audit pour cette catégorie',
+	'Class:Query' => 'Requête',
+	'Class:Query+' => 'Une requête définit un ensemble d\'information de manière dynamique',
+	'Class:Query/Attribute:name' => 'Nom',
+	'Class:Query/Attribute:name+' => 'Identification de la requête',
+	'Class:Query/Attribute:description' => 'Description',
+	'Class:Query/Attribute:description+' => 'Description complète (finalité, utilisations, public)',
+	'Class:QueryOQL' => 'Requête OQL',
+	'Class:QueryOQL+' => 'Une requête écrite dans le langage "Object Query Language"',
+	'Class:QueryOQL/Attribute:oql' => 'Expression',
+	'Class:QueryOQL/Attribute:oql+' => 'Expression OQL',
 	'Class:URP_Profiles' => 'Profil',
 	'Class:URP_Profiles+' => 'Profil utilisateur',
 	'Class:URP_Profiles/Attribute:name' => 'Nom',
@@ -431,6 +441,7 @@ Dict::Add('FR FR', 'French', 'Français', array(
 	'UI:RunQuery:DevelopedQuery' => 'Requête OQL décompilée : ',
 	'UI:RunQuery:SerializedFilter' => 'Version sérialisée : ',
 	'UI:RunQuery:Error' => 'Une erreur s\'est produite durant l\'exécution de la requête : %1$s',
+	'UI:Query:UrlForExcel' => 'Lien à copier-coller dans Excel, pour déclarer une source de données à partir du web',
 	'UI:Schema:Title' => 'Modèle de données iTop',
 	'UI:Schema:CategoryMenuItem' => 'Catégorie <b>%1$s</b>',
 	'UI:Schema:Relationships' => 'Relations',
@@ -490,6 +501,7 @@ Dict::Add('FR FR', 'French', 'Français', array(
 	'UI:Schema:LifeCycleAttributeMustPrompt' => 'L\'utilisateur se verra proposer de changer la valeur',
 	'UI:Schema:LifeCycleEmptyList' => 'liste vide',
 	'UI:LinksWidget:Autocomplete+' => 'Tapez les 3 premiers caractères...',
+	'UI:Edit:TestQuery' => 'Tester le requête',
 	'UI:Combo:SelectValue' => '--- choisissez une valeur ---',
 	'UI:Label:SelectedObjects' => 'Objets sélectionnés: ',
 	'UI:Label:AvailableObjects' => 'Objets disponibles: ',
@@ -684,6 +696,8 @@ Lors de l\'association à un déclencheur, on attribue à chaque action un numé
 	'Menu:Notifications:Title' => 'Catégories d\'audit',
 	'Menu:RunQueriesMenu' => 'Requêtes OQL',
 	'Menu:RunQueriesMenu+' => 'Executer une requête OQL',
+	'Menu:QueryMenu' => 'Livre des requêtes',
+	'Menu:QueryMenu+' => 'Livre des requêtes',
 	'Menu:DataAdministration' => 'Administration des données',
 	'Menu:DataAdministration+' => 'Administration des données',
 	'Menu:UniversalSearchMenu' => 'Recherche Universelle',

+ 1 - 1
modules/itop-config-mgmt-1.0.0/model.itop-config-mgmt.php

@@ -365,7 +365,7 @@ class FileDoc extends Document
 			$oPage->add($this->DisplayDocumentInline($oPage, 'contents'));
 			$oPage->SetCurrentTab(Dict::S('UI:PropertiesTab'));
 		}
-		parent::DisplayBareProperties($oPage, $bEditMode, $sPrefix);
+		parent::DisplayBareProperties($oPage, $bEditMode, $sPrefix, $aExtraParams);
 		
 	}
 }

+ 1 - 0
modules/itop-welcome-itil/model.itop-welcome-itil.php

@@ -56,6 +56,7 @@ class ItopWelcome extends ModuleHandlerAPI
 			new TemplateMenuNode('NotificationsMenu', APPROOT.'application/templates/notifications_menu.html', $oAdminMenu->GetIndex(), 3 /* fRank */);
 			new OQLMenuNode('AuditCategories', 'SELECT AuditCategory', $oAdminMenu->GetIndex(), 4 /* fRank */);
 			new WebPageMenuNode('RunQueriesMenu', utils::GetAbsoluteUrlAppRoot().'pages/run_query.php', $oAdminMenu->GetIndex(), 8 /* fRank */);
+			new OQLMenuNode('QueryMenu', 'SELECT Query', $oAdminMenu->GetIndex(), 8.5 /* fRank */);
 			new WebPageMenuNode('ExportMenu', utils::GetAbsoluteUrlAppRoot().'webservices/export.php', $oAdminMenu->GetIndex(), 9 /* fRank */);
 			new WebPageMenuNode('DataModelMenu', utils::GetAbsoluteUrlAppRoot().'pages/schema.php', $oAdminMenu->GetIndex(), 10 /* fRank */);
 			new WebPageMenuNode('UniversalSearchMenu', utils::GetAbsoluteUrlAppRoot().'pages/UniversalSearch.php', $oAdminMenu->GetIndex(), 11 /* fRank */);

+ 48 - 15
pages/run_query.php

@@ -120,29 +120,62 @@ try
 		// leave $sExpression as is
 	}
 
+	$oFilter = null;
+	$aArgs = array();
+
+	if (!empty($sExpression))
+	{
+		$oFilter = DBObjectSearch::FromOQL($sExpression);
+		if ($oFilter)
+		{
+			$aArgs = array();
+			foreach($oFilter->GetQueryParams() as $sParam => $foo)
+			{
+				$value = utils::ReadParam('arg_'.$sParam, null, true, 'raw_data');
+				if (!is_null($value))
+				{
+					$aArgs[$sParam] = $value;
+				}
+				else
+				{
+					$aArgs[$sParam] = '';
+				}
+			}
+			$oFilter->SetInternalParams($aArgs);
+		}
+	}
+
 	$oP->add("<form method=\"get\">\n");
 	$oP->add(Dict::S('UI:RunQuery:ExpressionToEvaluate')."<br/>\n");
 	$oP->add("<textarea cols=\"120\" rows=\"8\" name=\"expression\">$sExpression</textarea>\n");
+
+	if (count($aArgs) > 0)
+	{
+		$oP->add("<div class=\"wizContainer\">\n");
+		$oP->add("<h3>Query arguments</h3>\n");
+		foreach($aArgs as $sParam => $sValue)
+		{
+			$oP->p("$sParam: <input type=\"string\" name=\"arg_$sParam\" value=\"$sValue\">\n");
+		}
+		$oP->add("</div>\n"); 
+	}
+
 	$oP->add("<input type=\"submit\" value=\"".Dict::S('UI:Button:Evaluate')."\">\n");
 	$oP->add($oAppContext->GetForForm());
 	$oP->add("</form>\n");
 
-	if (!empty($sExpression))
+	if ($oFilter)
 	{
-		$oFilter = DBObjectSearch::FromOQL($sExpression);
-		if ($oFilter)
-		{
-			$oP->add("<h3>Query results</h3>\n");
-			
-			$oResultBlock = new DisplayBlock($oFilter, 'list', false);
-			$oResultBlock->Display($oP, 'runquery');
-
-			$oP->p('');
-			$oP->StartCollapsibleSection(Dict::S('UI:RunQuery:MoreInfo'), false);
-			$oP->p(Dict::S('UI:RunQuery:DevelopedQuery').$oFilter->ToOQL());
-			$oP->p(Dict::S('UI:RunQuery:SerializedFilter').$oFilter->serialize());
-			$oP->EndCollapsibleSection();
-		}
+		$oP->add("<h3>Query results</h3>\n");
+		
+		$oResultBlock = new DisplayBlock($oFilter, 'list', false);
+		$oResultBlock->Display($oP, 'runquery');
+
+		$oP->p('');
+		$oP->StartCollapsibleSection(Dict::S('UI:RunQuery:MoreInfo'), false);
+		$oP->p(Dict::S('UI:RunQuery:DevelopedQuery').$oFilter->ToOQL());
+		$oP->p(Dict::S('UI:RunQuery:SerializedFilter').$oFilter->serialize());
+		$oP->EndCollapsibleSection();
 	}
 }
 catch(CoreException $e)

+ 2 - 2
synchro/synchrodatasource.class.inc.php

@@ -2275,7 +2275,7 @@ class SynchroReplica extends DBObject implements iDisplay
 		$this->DisplayBareProperties($oPage, $bEditMode);
 	}
 	
-	function DisplayBareProperties(WebPage $oPage, $bEditMode = false, $aExtraParams = array())
+	function DisplayBareProperties(WebPage $oPage, $bEditMode = false, $sPrefix = '', $aExtraParams = array())
 	{
 		if ($bEditMode) return; // Not editable
 		
@@ -2297,7 +2297,7 @@ class SynchroReplica extends DBObject implements iDisplay
 		{
 			$oPage->add('<fieldset>');
 			$oPage->add('<legend>'.Dict::Format('Core:SynchroReplica:TargetObject', $oDestObj->GetHyperlink()).'</legend>');
-				$oDestObj->DisplayBareProperties($oPage, false, $aExtraParams);
+				$oDestObj->DisplayBareProperties($oPage, false, $sPrefix, $aExtraParams);
 			$oPage->add('<fieldset>');
 		}
 		$oPage->add('</td><td>');

+ 32 - 2
webservices/export.php

@@ -55,6 +55,22 @@ $currentOrganization = utils::ReadParam('org_id', '');
 
 // Main program
 $sExpression = utils::ReadParam('expression', '', true /* Allow CLI */, 'raw_data');
+if (strlen($sExpression) == 0)
+{
+	$sQueryId = trim(utils::ReadParam('query', '', true /* Allow CLI */, 'raw_data'));
+	if (strlen($sQueryId) > 0)
+	{
+		$oSearch = DBObjectSearch::FromOQL('SELECT QueryOQL WHERE id = :query_id', array('query_id' => $sQueryId));
+		$oQueries = new DBObjectSet($oSearch);
+		if ($oQueries->Count() > 0)
+		{
+			$oQuery = $oQueries->Fetch();
+			$sExpression = $oQuery->Get('oql');
+		}
+	}
+}
+
+
 $sFormat = strtolower(utils::ReadParam('format', 'html'));
 
 $sFields = utils::ReadParam('fields', '', true, 'raw_data'); // CSV field list (allows to specify link set attributes, still not taken into account for XML export)
@@ -76,9 +92,21 @@ if (!empty($sExpression))
 	try
 	{
 		$oFilter = DBObjectSearch::FromOQL($sExpression);
+
+		$aArgs = array();
+		foreach($oFilter->GetQueryParams() as $sParam => $foo)
+		{
+			$value = utils::ReadParam('arg_'.$sParam, null, true, 'raw_data');
+			if (!is_null($value))
+			{
+				$aArgs[$sParam] = $value;
+			}
+		}
+		$oFilter->SetInternalParams($aArgs);
+
 		if ($oFilter)
 		{
-			$oSet = new CMDBObjectSet($oFilter);
+			$oSet = new CMDBObjectSet($oFilter, array(), $aArgs);
 			switch($sFormat)
 			{
 				case 'html':
@@ -164,7 +192,9 @@ if (!$oP)
 	$oP->p("<strong>General purpose export page.</strong>");
 	$oP->p("<strong>Parameters:</strong>");
 	$oP->p("<ul><li>expression: an OQL expression (URL encoded if needed)</li>
-			    <li>format: (optional, default is html) the desired output format. Can be one of 'html', 'csv' or 'xml'</li>
+			    <li>query: (alternative to 'expression') the id of an entry from the query phrasebook</li>
+			    <li>arg_xxx: (needed if the query has parameters) the value of the parameter 'xxx'</li>
+			    <li>format: (optional, default is html) the desired output format. Can be one of 'html', 'spreadsheet', 'csv' or 'xml'</li>
 			    <li>fields: (optional, no effect on XML format) list of fields (attribute codes) separated by a coma</li>
 		    </ul>");
 }