tabularbulkexport.class.inc.php 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368
  1. <?php
  2. // Copyright (C) 2015 Combodo SARL
  3. //
  4. // This file is part of iTop.
  5. //
  6. // iTop is free software; you can redistribute it and/or modify
  7. // it under the terms of the GNU Affero General Public License as published by
  8. // the Free Software Foundation, either version 3 of the License, or
  9. // (at your option) any later version.
  10. //
  11. // iTop is distributed in the hope that it will be useful,
  12. // but WITHOUT ANY WARRANTY; without even the implied warranty of
  13. // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  14. // GNU Affero General Public License for more details.
  15. //
  16. // You should have received a copy of the GNU Affero General Public License
  17. // along with iTop. If not, see <http://www.gnu.org/licenses/>
  18. /**
  19. * Bulk export: Tabular export: abstract base class for all "tabular" exports.
  20. * Provides the user interface for selecting the column to be exported
  21. *
  22. * @copyright Copyright (C) 2015 Combodo SARL
  23. * @license http://opensource.org/licenses/AGPL-3.0
  24. */
  25. abstract class TabularBulkExport extends BulkExport
  26. {
  27. public function EnumFormParts()
  28. {
  29. return array_merge(parent::EnumFormParts(), array('tabular_fields' => array('fields')));
  30. }
  31. public function DisplayFormPart(WebPage $oP, $sPartId)
  32. {
  33. switch($sPartId)
  34. {
  35. case 'tabular_fields':
  36. $sFields = utils::ReadParam('fields', '', true, 'raw_data');
  37. $sSuggestedFields = utils::ReadParam('suggested_fields', null, true, 'raw_data');
  38. if (($sSuggestedFields !== null) && ($sSuggestedFields !== ''))
  39. {
  40. //IssueLog::Info(__function__.' Suggesting fields: '.$sSuggestedFields);
  41. $aSuggestedFields = explode(',', $sSuggestedFields);
  42. $sFields = implode(',', $this->SuggestFields($aSuggestedFields));
  43. }
  44. $oP->add('<input id="tabular_fields" type="hidden" size="50" name="fields" value="'.htmlentities($sFields, ENT_QUOTES, 'UTF-8').'"></input>');
  45. break;
  46. default:
  47. return parent:: DisplayFormPart($oP, $sPartId);
  48. }
  49. }
  50. protected function SuggestFields($aSuggestedFields)
  51. {
  52. // By defaults all fields are Ok, nothing gets translated but
  53. // you can overload this method if some fields are better exported
  54. // (in a given format) by using an alternate field, for example id => friendlyname
  55. $aAliases = $this->oSearch->GetSelectedClasses();
  56. foreach($aSuggestedFields as $idx => $sField)
  57. {
  58. if (preg_match('/^([^\\.]+)\\.(.+)$/', $sField, $aMatches))
  59. {
  60. $sAlias = $aMatches[1];
  61. $sAttCode = $aMatches[2];
  62. $sClass = $aAliases[$sAlias];
  63. }
  64. else
  65. {
  66. $sAlias = '';
  67. $sAttCode = $sField;
  68. $sClass = reset($aAliases);
  69. }
  70. $aSuggestedFields[$idx] = $this->SuggestField($aAliases, $sClass, $sAlias, $sAttCode);
  71. }
  72. return $aSuggestedFields;
  73. }
  74. protected function SuggestField($aAliases, $sClass, $sAlias, $sAttCode)
  75. {
  76. // Remove the aliases (if any) from the field names to make them compatible
  77. // with the 'short' notation used in this case by the widget
  78. if (count($aAliases) == 1)
  79. {
  80. return $sAttCode;
  81. }
  82. return $sAlias.'.'.$sAttCode;
  83. }
  84. protected function IsSubAttribute($sClass, $sAttCode, $oAttDef)
  85. {
  86. return (($oAttDef instanceof AttributeFriendlyName) || ($oAttDef instanceof AttributeExternalField) || ($oAttDef instanceof AttributeSubItem));
  87. }
  88. protected function GetSubAttributes($sClass, $sAttCode, $oAttDef)
  89. {
  90. $aResult = array();
  91. switch(get_class($oAttDef))
  92. {
  93. case 'AttributeExternalKey':
  94. case 'AttributeHierarchicalKey':
  95. $aResult[] = array('code' => $sAttCode, 'unique_label' => $oAttDef->GetLabel(), 'label' => Dict::S('Core:BulkExport:Identifier'), 'attdef' => $oAttDef);
  96. $bAddFriendlyName = true;
  97. $oKeyAttDef = MetaModel::GetAttributeDef($sClass, $sAttCode);
  98. $sRemoteClass = $oKeyAttDef->GetTargetClass();
  99. $sFriendlyNameAttCode = MetaModel::GetFriendlyNameAttributeCode($sRemoteClass);
  100. if (!is_null($sFriendlyNameAttCode))
  101. {
  102. // The friendly name is made of a single attribute, check if that attribute is present as an external field
  103. foreach(MetaModel::ListAttributeDefs($sClass) as $sSubAttCode => $oSubAttDef)
  104. {
  105. if ($oSubAttDef instanceof AttributeExternalField)
  106. {
  107. if (($oSubAttDef->GetKeyAttCode() == $sAttCode) && ($oSubAttDef->GetExtAttCode() == $sFriendlyNameAttCode))
  108. {
  109. $bAddFriendlyName = false;
  110. }
  111. }
  112. }
  113. }
  114. if ($bAddFriendlyName)
  115. {
  116. $aResult[] = array('code' => $sAttCode.'_friendlyname', 'unique_label' => $oAttDef->GetLabel().'->'.Dict::S('Core:FriendlyName-Label'), 'label' => Dict::S('Core:FriendlyName-Label'), 'attdef' => MetaModel::GetAttributeDef($sClass, $sAttCode.'_friendlyname'));
  117. }
  118. // NOT WORKING !!!!!!!!!
  119. // NOT WORKING !!!!!!!!!
  120. // NOT WORKING !!!!!!!!!
  121. // Add the reconciliation keys
  122. foreach(MetaModel::GetReconcKeys($sRemoteClass) as $sRemoteAttCode)
  123. {
  124. $oRemoteAttDef = MetaModel::GetAttributeDef($sRemoteClass, $sRemoteAttCode);
  125. $aResult[] = array('code' => $sAttCode.'->'.$sRemoteAttCode, 'unique_label' => $oAttDef->GetLabel().'->'.$oRemoteAttDef->GetLabel(), 'label' => $oRemoteAttDef->GetLabel(), 'attdef' => $oRemoteAttDef);
  126. }
  127. foreach(MetaModel::ListAttributeDefs($sClass) as $sSubAttCode => $oSubAttDef)
  128. {
  129. if ($oSubAttDef instanceof AttributeExternalField)
  130. {
  131. if ($oSubAttDef->GetKeyAttCode() == $sAttCode)
  132. {
  133. $aResult[] = array('code' => $sSubAttCode, 'unique_label' => $oAttDef->GetLabel().'->'.$oSubAttDef->GetExtAttDef()->GetLabel(), 'label' => $oSubAttDef->GetExtAttDef()->GetLabel(), 'attdef' => $oSubAttDef);
  134. }
  135. }
  136. }
  137. break;
  138. case 'AttributeStopWatch':
  139. foreach(MetaModel::ListAttributeDefs($sClass) as $sSubAttCode => $oSubAttDef)
  140. {
  141. if ($oSubAttDef instanceof AttributeSubItem)
  142. {
  143. if ($oSubAttDef->GetParentAttCode() == $sAttCode)
  144. {
  145. $aResult[] = array('code' => $sSubAttCode, 'unique_label' => $oSubAttDef->GetLabel(), 'label' => $oSubAttDef->GetLabel(), 'attdef' => $oSubAttDef);
  146. }
  147. }
  148. }
  149. break;
  150. }
  151. return $aResult;
  152. }
  153. protected function GetInteractiveFieldsWidget(WebPage $oP, $sWidgetId)
  154. {
  155. $oSet = new DBObjectSet($this->oSearch);
  156. $aSelectedClasses = $this->oSearch->GetSelectedClasses();
  157. $aAuthorizedClasses = array();
  158. foreach($aSelectedClasses as $sAlias => $sClassName)
  159. {
  160. if (UserRights::IsActionAllowed($sClassName, UR_ACTION_BULK_READ, $oSet) && (UR_ALLOWED_YES || UR_ALLOWED_DEPENDS))
  161. {
  162. $aAuthorizedClasses[$sAlias] = $sClassName;
  163. }
  164. }
  165. $aAllFieldsByAlias = array();
  166. foreach($aAuthorizedClasses as $sAlias => $sClass)
  167. {
  168. $aAllFields = array();
  169. if (count($aAuthorizedClasses) > 1 )
  170. {
  171. $sShortAlias = $sAlias.'.';
  172. }
  173. else
  174. {
  175. $sShortAlias = '';
  176. }
  177. if ($this->IsValidField($sClass, 'id'))
  178. {
  179. $sFriendlyNameAttCode = MetaModel::GetFriendlyNameAttributeCode($sClass);
  180. if (is_null($sFriendlyNameAttCode))
  181. {
  182. // The friendly name is made of several attribute
  183. $aSubAttr = array(
  184. array('code' => $sShortAlias.'id', 'unique_label' => $sShortAlias.Dict::S('Core:BulkExport:Identifier'), 'label' => $sShortAlias.'id'),
  185. array('code' => $sShortAlias.'friendlyname', 'unique_label' => $sShortAlias.Dict::S('Core:FriendlyName-Label'), 'label' => $sShortAlias.Dict::S('Core:FriendlyName-Label')),
  186. );
  187. }
  188. else
  189. {
  190. // The friendly name has no added value
  191. $aSubAttr = array();
  192. }
  193. $aAllFields[] = array('code' => $sShortAlias.'id', 'unique_label' => $sShortAlias.Dict::S('Core:BulkExport:Identifier'), 'label' => $sShortAlias.'id', 'subattr' => $aSubAttr);
  194. }
  195. foreach(MetaModel::ListAttributeDefs($sClass) as $sAttCode => $oAttDef)
  196. {
  197. if($this->IsSubAttribute($sClass, $sAttCode, $oAttDef)) continue;
  198. if ($this->IsValidField($sClass, $sAttCode, $oAttDef))
  199. {
  200. $sShortLabel = $oAttDef->GetLabel();
  201. $sLabel = $sShortAlias.$oAttDef->GetLabel();
  202. $aSubAttr = $this->GetSubAttributes($sClass, $sAttCode, $oAttDef);
  203. $aValidSubAttr = array();
  204. foreach($aSubAttr as $aSubAttDef)
  205. {
  206. if ($this->IsValidField($sClass, $aSubAttDef['code'], $aSubAttDef['attdef']))
  207. {
  208. $aValidSubAttr[] = array('code' => $sShortAlias.$aSubAttDef['code'], 'label' => $aSubAttDef['label'], 'unique_label' => $aSubAttDef['unique_label']);
  209. }
  210. }
  211. $aAllFields[] = array('code' => $sShortAlias.$sAttCode, 'label' => $sShortLabel, 'unique_label' => $sLabel, 'subattr' => $aValidSubAttr);
  212. }
  213. }
  214. usort($aAllFields, array(get_class($this), 'SortOnLabel'));
  215. if (count($aAuthorizedClasses) > 1)
  216. {
  217. $sKey = MetaModel::GetName($sClass).' ('.$sAlias.')';
  218. }
  219. else
  220. {
  221. $sKey = MetaModel::GetName($sClass);
  222. }
  223. $aAllFieldsByAlias[$sKey] = $aAllFields;
  224. }
  225. $oP->add('<div id="'.$sWidgetId.'"></div>');
  226. $JSAllFields = json_encode($aAllFieldsByAlias);
  227. //IssueLog::Info('JSAllFields='.print_r($aAllFieldsByAlias, true));
  228. $oSet = new DBObjectSet($this->oSearch);
  229. $iCount = $oSet->Count();
  230. $iPreviewLimit = 3;
  231. $oSet->SetLimit($iPreviewLimit);
  232. $aSampleData = array();
  233. while($aRow = $oSet->FetchAssoc())
  234. {
  235. $aSampleRow = array();
  236. foreach($aAuthorizedClasses as $sAlias => $sClass)
  237. {
  238. if (count($aAuthorizedClasses) > 1 )
  239. {
  240. $sShortAlias = $sAlias.'.';
  241. }
  242. else
  243. {
  244. $sShortAlias = '';
  245. }
  246. if ($this->IsValidField($sClass, 'id'))
  247. {
  248. $aSampleRow[$sShortAlias.'id'] = $this->GetSampleKey($aRow[$sAlias]);
  249. }
  250. foreach(MetaModel::ListAttributeDefs($sClass) as $sAttCode => $oAttDef)
  251. {
  252. if ($this->IsValidField($sClass, $sAttCode, $oAttDef))
  253. {
  254. $aSampleRow[$sShortAlias.$sAttCode] = $this->GetSampleData($aRow[$sAlias], $sAttCode);
  255. }
  256. }
  257. }
  258. $aSampleData[] = $aSampleRow;
  259. }
  260. $sJSSampleData = json_encode($aSampleData);
  261. $aLabels = array(
  262. 'preview_header' => Dict::S('Core:BulkExport:DragAndDropHelp'),
  263. 'empty_preview' => Dict::S('Core:BulkExport:EmptyPreview'),
  264. 'columns_order' => Dict::S('Core:BulkExport:ColumnsOrder'),
  265. 'columns_selection' => Dict::S('Core:BulkExport:AvailableColumnsFrom_Class'),
  266. 'check_all' => Dict::S('Core:BulkExport:CheckAll'),
  267. 'uncheck_all' => Dict::S('Core:BulkExport:UncheckAll'),
  268. 'no_field_selected' => Dict::S('Core:BulkExport:NoFieldSelected'),
  269. );
  270. $sJSLabels = json_encode($aLabels);
  271. $oP->add_ready_script(
  272. <<<EOF
  273. $('#$sWidgetId').tabularfieldsselector({fields: $JSAllFields, value_holder: '#tabular_fields', advanced_holder: '#tabular_advanced', sample_data: $sJSSampleData, total_count: $iCount, preview_limit: $iPreviewLimit, labels: $sJSLabels });
  274. EOF
  275. );
  276. }
  277. static public function SortOnLabel($aItem1, $aItem2)
  278. {
  279. return strcmp($aItem1['label'], $aItem2['label']);
  280. }
  281. /**
  282. * Tells if the specified field can be exported
  283. * @param unknown $sClass
  284. * @param unknown $sAttCode
  285. * @param AttributeDefinition $oAttDef Can be null when $sAttCode == 'id'
  286. * @return boolean
  287. */
  288. protected function IsValidField($sClass, $sAttCode, $oAttDef = null)
  289. {
  290. if ($sAttCode == 'id') return true;
  291. if ($oAttDef instanceof AttributeLinkedSet) return false;
  292. return true; //$oAttDef->IsScalar();
  293. }
  294. protected function GetSampleData(DBObject $oObj, $sAttCode)
  295. {
  296. if ($oObj == null) return '';
  297. return $oObj->GetEditValue($sAttCode);
  298. }
  299. protected function GetSampleKey(DBObject $oObj)
  300. {
  301. if ($oObj == null) return '';
  302. return $oObj->GetKey();
  303. }
  304. public function ReadParameters()
  305. {
  306. parent::ReadParameters();
  307. $sQueryId = utils::ReadParam('query', null, true);
  308. $sFields = utils::ReadParam('fields', null, true, 'raw_data');
  309. if ((($sFields === null) || ($sFields === '')) && ($sQueryId === null))
  310. {
  311. throw new BulkExportMissingParameterException('fields');
  312. }
  313. else if(($sQueryId !== null) && ($sQueryId !== null))
  314. {
  315. $oSearch = DBObjectSearch::FromOQL('SELECT QueryOQL WHERE id = :query_id', array('query_id' => $sQueryId));
  316. $oQueries = new DBObjectSet($oSearch);
  317. if ($oQueries->Count() > 0)
  318. {
  319. $oQuery = $oQueries->Fetch();
  320. if (($sFields === null) || ($sFields === ''))
  321. {
  322. // No 'fields' parameter supplied, take the fields from the query phrasebook definition
  323. $sFields = trim($oQuery->Get('fields'));
  324. if ($sFields === '')
  325. {
  326. throw new BulkExportMissingParameterException('fields');
  327. }
  328. }
  329. }
  330. else
  331. {
  332. throw BulkExportException('Invalid value for the parameter: query. There is no Query Phrasebook with id = '.$sQueryId, Dict::Format('Core:BulkExport:InvalidParameter_Query', $sQueryId));
  333. }
  334. }
  335. $aFields = explode(',', $sFields);
  336. $this->aStatusInfo['fields'] = array();
  337. foreach($aFields as $sField)
  338. {
  339. // Trim the values since it's too temping to write: fields=name, first_name, org_name instead of fields=name,first_name,org_name
  340. $this->aStatusInfo['fields'][] = trim($sField);
  341. }
  342. }
  343. }