ajax.csvimport.php 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380
  1. <?php
  2. // Copyright (C) 2010 Combodo SARL
  3. //
  4. // This program is free software; you can redistribute it and/or modify
  5. // it under the terms of the GNU General Public License as published by
  6. // the Free Software Foundation; version 3 of the License.
  7. //
  8. // This program is distributed in the hope that it will be useful,
  9. // but WITHOUT ANY WARRANTY; without even the implied warranty of
  10. // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  11. // GNU General Public License for more details.
  12. //
  13. // You should have received a copy of the GNU General Public License
  14. // along with this program; if not, write to the Free Software
  15. // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
  16. /**
  17. * Specific to the interactive csv import
  18. *
  19. * @author Erwan Taloc <erwan.taloc@combodo.com>
  20. * @author Romain Quetiez <romain.quetiez@combodo.com>
  21. * @author Denis Flaven <denis.flaven@combodo.com>
  22. * @license http://www.opensource.org/licenses/gpl-3.0.html LGPL
  23. */
  24. require_once('../application/application.inc.php');
  25. require_once('../application/webpage.class.inc.php');
  26. require_once('../application/ajaxwebpage.class.inc.php');
  27. require_once('../application/wizardhelper.class.inc.php');
  28. require_once('../application/ui.linkswidget.class.inc.php');
  29. require_once('../application/csvpage.class.inc.php');
  30. /**
  31. * Determines if the name of the field to be mapped correspond
  32. * to the name of an external key or an Id of the given class
  33. * @param string $sClassName The name of the class
  34. * @param string $sFieldCode The attribute code of the field , or empty if no match
  35. * @return bool true if the field corresponds to an id/External key, false otherwise
  36. */
  37. function IsIdField($sClassName, $sFieldCode)
  38. {
  39. $bResult = false;
  40. if (!empty($sFieldCode))
  41. {
  42. if ($sFieldCode == 'id')
  43. {
  44. $bResult = true;
  45. }
  46. else if (strpos($sFieldCode, '->') === false)
  47. {
  48. $oAttDef = MetaModel::GetAttributeDef($sClassName, $sFieldCode);
  49. $bResult = $oAttDef->IsExternalKey();
  50. }
  51. }
  52. return $bResult;
  53. }
  54. /**
  55. * Get all the fields xxx->yyy based on the field xxx which is an external key
  56. * @param string $sExtKeyAttCode Attribute code of the external key
  57. * @param AttributeDefinition $oExtKeyAttDef Attribute definition of the external key
  58. * @param bool $bAdvanced True if advanced mode
  59. * @return Ash List of codes=>display name: xxx->yyy where yyy are the reconciliation keys for the object xxx
  60. */
  61. function GetMappingsForExtKey($sAttCode, AttributeDefinition $oExtKeyAttDef, $bAdvanced)
  62. {
  63. $aResult = array();
  64. $sTargetClass = $oExtKeyAttDef->GetTargetClass();
  65. foreach(MetaModel::ListAttributeDefs($sTargetClass) as $sTargetAttCode => $oTargetAttDef)
  66. {
  67. if (MetaModel::IsReconcKey($sTargetClass, $sTargetAttCode))
  68. {
  69. $bExtKey = $oTargetAttDef->IsExternalKey();
  70. $sSuffix = '';
  71. if ($bExtKey)
  72. {
  73. $sSuffix = '->id';
  74. }
  75. if ($bAdvanced || !$bExtKey)
  76. {
  77. // When not in advanced mode do not allow to use reconciliation keys (on external keys) if they are themselves external keys !
  78. $aResult[$sAttCode.'->'.$sTargetAttCode] = $oExtKeyAttDef->GetLabel().'->'.$oTargetAttDef->GetLabel().$sSuffix;
  79. }
  80. }
  81. }
  82. return $aResult;
  83. }
  84. /**
  85. * Helper function to build the mapping drop-down list for a field
  86. * Spec: Possible choices are "writable" fields in this class plus external fields that are listed as reconciliation keys
  87. * for any class pointed to by an external key in the current class.
  88. * If not in advanced mode, all "id" fields (id and external keys) must be mapped to ":none:" (i.e -- ignore this field --)
  89. * External fields that do not correspond to a reconciliation key must be mapped to ":none:"
  90. * Otherwise, if a field equals either the 'code' or the 'label' (translated) of a field, then it's mapped automatically
  91. * @param string $sClassName Name of the class used for the mapping
  92. * @param string $sFieldName Name of the field, as it comes from the data file (header line)
  93. * @param integer $iFieldIndex Number of the field in the sequence
  94. * @param bool $bAdvancedMode Whether or not advanced mode was chosen
  95. * @return string The HTML code corresponding to the drop-down list for this field
  96. */
  97. function GetMappingForField($sClassName, $sFieldName, $iFieldIndex, $bAdvancedMode = false)
  98. {
  99. $aChoices = array('' => Dict::S('UI:CSVImport:MappingSelectOne'));
  100. $aChoices[':none:'] = Dict::S('UI:CSVImport:MappingNotApplicable');
  101. $sFieldCode = ''; // Code of the attribute, if there is a match
  102. if ($sFieldName == 'id')
  103. {
  104. $sFieldCode = 'id';
  105. }
  106. if ($bAdvancedMode)
  107. {
  108. $aChoices['id'] = Dict::S('UI:CSVImport:idField');
  109. }
  110. foreach(MetaModel::ListAttributeDefs($sClassName) as $sAttCode => $oAttDef)
  111. {
  112. if ($oAttDef->IsExternalKey())
  113. {
  114. if ( ($sFieldName == $oAttDef->GetLabel()) || ($sFieldName == $sAttCode))
  115. {
  116. $sFieldCode = $sAttCode;
  117. }
  118. if ($bAdvancedMode)
  119. {
  120. $aChoices[$sAttCode] = $oAttDef->GetLabel();
  121. }
  122. // Get fields of the external class that are considered as reconciliation keys
  123. $sTargetClass = $oAttDef->GetTargetClass();
  124. foreach(MetaModel::ListAttributeDefs($sTargetClass) as $sTargetAttCode => $oTargetAttDef)
  125. {
  126. if (MetaModel::IsReconcKey($sTargetClass, $sTargetAttCode))
  127. {
  128. $bExtKey = $oTargetAttDef->IsExternalKey();
  129. $sSuffix = '';
  130. if ($bExtKey)
  131. {
  132. $sSuffix = '->id';
  133. }
  134. if ($bAdvancedMode || !$bExtKey)
  135. {
  136. // When not in advanced mode do not allow to use reconciliation keys (on external keys) if they are themselves external keys !
  137. $aChoices[$sAttCode.'->'.$sTargetAttCode] = $oAttDef->GetLabel().'->'.$oTargetAttDef->GetLabel().$sSuffix;
  138. if ((strcasecmp($sFieldName, $aChoices[$sAttCode.'->'.$sTargetAttCode]) == 0) || (strcasecmp($sFieldName, ($sAttCode.'->'.$sTargetAttCode.$sSuffix)) == 0) )
  139. {
  140. $sFieldCode = $sAttCode.'->'.$sTargetAttCode;
  141. }
  142. }
  143. }
  144. }
  145. }
  146. else if ( ($oAttDef->IsWritable()) && (!$oAttDef->IsLinkSet()) )
  147. {
  148. $aChoices[$sAttCode] = $oAttDef->GetLabel();
  149. if ( ($sFieldName == $oAttDef->GetLabel()) || ($sFieldName == $sAttCode))
  150. {
  151. $sFieldCode = $sAttCode;
  152. }
  153. }
  154. }
  155. asort($aChoices);
  156. $sHtml = "<select id=\"mapping_{$iFieldIndex}\" name=\"field[$iFieldIndex]\">\n";
  157. $bIsIdField = IsIdField($sClassName, $sFieldCode);
  158. foreach($aChoices as $sAttCode => $sLabel)
  159. {
  160. $sSelected = '';
  161. if ($bIsIdField && (!$bAdvancedMode)) // When not in advanced mode, ID are mapped to n/a
  162. {
  163. if ($sAttCode == ':none:')
  164. {
  165. $sSelected = ' selected';
  166. }
  167. }
  168. else if (empty($sFieldCode) && (strpos($sFieldName, '->') !== false))
  169. {
  170. if ($sAttCode == ':none:')
  171. {
  172. $sSelected = ' selected';
  173. }
  174. }
  175. else if ($sFieldCode == $sAttCode) // Otherwise map by default if there is a match
  176. {
  177. $sSelected = ' selected';
  178. }
  179. $sHtml .= "<option value=\"$sAttCode\"$sSelected>$sLabel</option>\n";
  180. }
  181. $sHtml .= "</select>\n";
  182. return $sHtml;
  183. }
  184. require_once('../application/startup.inc.php');
  185. require_once('../application/loginwebpage.class.inc.php');
  186. LoginWebPage::DoLogin(); // Check user rights and prompt if needed
  187. $sOperation = utils::ReadParam('operation', '');
  188. switch($sOperation)
  189. {
  190. case 'parser_preview':
  191. $oPage = new ajax_page("");
  192. $oPage->no_cache();
  193. $sSeparator = utils::ReadParam('separator', ',');
  194. if ($sSeparator == 'tab') $sSeparator = "\t";
  195. $sTextQualifier = utils::ReadParam('qualifier', '"');
  196. $iLinesToSkip = utils::ReadParam('nb_lines_skipped', 0);
  197. $bFirstLineAsHeader = utils::ReadParam('header_line', true);
  198. $sEncoding = utils::ReadParam('encoding', 'UTF-8');
  199. $sData = stripslashes(utils::ReadParam('csvdata', true));
  200. $oCSVParser = new CSVParser($sData, $sSeparator, $sTextQualifier);
  201. $aData = $oCSVParser->ToArray($iLinesToSkip);
  202. $iTarget = count($aData);
  203. if ($iTarget == 0)
  204. {
  205. $oPage->p(Dict::S('UI:CSVImport:NoData'));
  206. }
  207. else
  208. {
  209. $sMaxLen = (strlen(''.$iTarget) < 3) ? 3 : strlen(''.$iTarget); // Pad line numbers to the appropriate number of chars, but at least 3
  210. $sFormat = '%0'.$sMaxLen.'d';
  211. $oPage->p("<h3>".Dict::S('UI:Title:DataPreview')."</h3>\n");
  212. $oPage->p("<div style=\"overflow-y:auto\" class=\"white\">\n");
  213. $oPage->add("<table cellspacing=\"0\" style=\"overflow-y:auto\">");
  214. $iMaxIndex= 10; // Display maximum 10 lines for the preview
  215. $index = 1;
  216. foreach($aData as $aRow)
  217. {
  218. $sCSSClass = 'csv_row'.($index % 2);
  219. if ( ($bFirstLineAsHeader) && ($index == 1))
  220. {
  221. $oPage->add("<tr class=\"$sCSSClass\"><td style=\"border-left:#999 3px solid;padding-right:10px;padding-left:10px;\">".sprintf($sFormat, $index)."</td><th>");
  222. $oPage->add(implode('</th><th>', $aRow));
  223. $oPage->add("</th></tr>\n");
  224. $iNbCols = count($aRow);
  225. }
  226. else
  227. {
  228. if ($index == 1) $iNbCols = count($aRow);
  229. $oPage->add("<tr class=\"$sCSSClass\"><td style=\"border-left:#999 3px solid;padding-right:10px;padding-left:10px;\">".sprintf($sFormat, $index)."</td><td>");
  230. $oPage->add(implode('</td><td>', $aRow));
  231. $oPage->add("</td></tr>\n");
  232. }
  233. $index++;
  234. if ($index > $iMaxIndex) break;
  235. }
  236. $oPage->add("</table>\n");
  237. $oPage->add("</div>\n");
  238. if($iNbCols == 1)
  239. {
  240. $oPage->p('<img src="../images/error.png">&nbsp;'.Dict::S('UI:CSVImport:ErrorOnlyOneColumn'));
  241. }
  242. else
  243. {
  244. $oPage->p('&nbsp;');
  245. }
  246. }
  247. break;
  248. case 'display_mapping_form':
  249. $oPage = new ajax_page("");
  250. $oPage->no_cache();
  251. $sSeparator = utils::ReadParam('separator', ',');
  252. $sTextQualifier = utils::ReadParam('qualifier', '"');
  253. $iLinesToSkip = utils::ReadParam('nb_lines_skipped', 0);
  254. $bFirstLineAsHeader = utils::ReadParam('header_line', false);
  255. $sData = stripslashes(utils::ReadParam('csvdata', ''));
  256. $sClassName = utils::ReadParam('class_name', '');
  257. $bAdvanced = utils::ReadParam('advanced', false);
  258. $sEncoding = utils::ReadParam('encoding', 'UTF-8');
  259. $oCSVParser = new CSVParser($sData, $sSeparator, $sTextQualifier);
  260. $aData = $oCSVParser->ToArray($iLinesToSkip);
  261. $iTarget = count($aData);
  262. if ($iTarget == 0)
  263. {
  264. $oPage->p(Dict::S('UI:CSVImport:NoData'));
  265. }
  266. else
  267. {
  268. $oPage->add("<table>");
  269. $aFirstLine = $aData[0]; // Use the first row to determine the number of columns
  270. $iStartLine = 0;
  271. $iNbColumns = count($aFirstLine);
  272. if ($bFirstLineAsHeader)
  273. {
  274. $iStartLine = 1;
  275. foreach($aFirstLine as $sField)
  276. {
  277. $aHeader[] = $sField;
  278. }
  279. }
  280. else
  281. {
  282. // Build some conventional name for the fields: field1...fieldn
  283. $index= 1;
  284. foreach($aFirstLine as $sField)
  285. {
  286. $aHeader[] = Dict::Format('UI:CSVImport:FieldName', $index);
  287. $index++;
  288. }
  289. }
  290. $oPage->add("<table>\n");
  291. $oPage->add('<tr>');
  292. $oPage->add('<th>'.Dict::S('UI:CSVImport:HeaderFields').'</th><th>'.Dict::S('UI:CSVImport:HeaderMappings').'</th><th>&nbsp;</th><th>'.Dict::S('UI:CSVImport:HeaderSearch').'</th><th>'.Dict::S('UI:CSVImport:DataLine1').'</th><th>'.Dict::S('UI:CSVImport:DataLine2').'</th>');
  293. $oPage->add('</tr>');
  294. $index = 1;
  295. foreach($aHeader as $sField)
  296. {
  297. $oPage->add('<tr>');
  298. $oPage->add("<th>$sField</th>");
  299. $oPage->add('<td>'.GetMappingForField($sClassName, $sField, $index, $bAdvanced).'</td>');
  300. $oPage->add('<td>&nbsp;</td>');
  301. $oPage->add('<td><input id="search_'.$index.'" type="checkbox" name="search_field['.$index.']" value="1" /></td>');
  302. $oPage->add('<td>'.(isset($aData[$iStartLine][$index-1]) ? htmlentities($aData[$iStartLine][$index-1], ENT_QUOTES, 'UTF-8') : '&nbsp;').'</td>');
  303. $oPage->add('<td>'.(isset($aData[$iStartLine+1][$index-1]) ? htmlentities($aData[$iStartLine+1][$index-1], ENT_QUOTES, 'UTF-8') : '&nbsp;').'</td>');
  304. $oPage->add('</tr>');
  305. $index++;
  306. }
  307. $oPage->add("</table>\n");
  308. $aReconciliationKeys = MetaModel::GetReconcKeys($sClassName);
  309. $aMoreReconciliationKeys = array();
  310. foreach($aReconciliationKeys as $sAttCode)
  311. {
  312. $oAttDef = MetaModel::GetAttributeDef($sClassName, $sAttCode);
  313. if ($oAttDef->IsExternalKey())
  314. {
  315. $aMoreReconciliationKeys = array_keys(GetMappingsForExtKey($sAttCode, $oAttDef, $bAdvanced));
  316. }
  317. }
  318. $sDefaultKeys = '"'.implode('", "',array_merge($aReconciliationKeys,$aMoreReconciliationKeys)).'"';
  319. $oPage->add_ready_script(
  320. <<<EOF
  321. $('select[name^=field]').change( DoCheckMapping );
  322. aDefaultKeys = new Array($sDefaultKeys);
  323. DoCheckMapping();
  324. EOF
  325. );
  326. }
  327. break;
  328. case 'get_csv_template':
  329. $sClassName = utils::ReadParam('class_name');
  330. $oSearch = new DBObjectSearch($sClassName);
  331. $oSearch->AddCondition('id', 0, '='); // Make sure we create an empty set
  332. $oSet = new CMDBObjectSet($oSearch);
  333. $sResult = cmdbAbstractObject::GetSetAsCSV($oSet);
  334. //$aCSV = explode("\n", $sCSV);
  335. // If there are more than one line, let's assume that the first line is a comment and skip it.
  336. //if (count($aCSV) > 1)
  337. //{
  338. // $sResult = $aCSV[0];
  339. //}
  340. //else
  341. //{
  342. // $sResult = $sCSV;
  343. //}
  344. $sClassDisplayName = MetaModel::GetName($sClassName);
  345. $sDisposition = utils::ReadParam('disposition', 'inline');
  346. if ($sDisposition == 'attachment')
  347. {
  348. $oPage = new CSVPage("");
  349. $oPage->add_header("Content-disposition: attachment; filename=\"{$sClassDisplayName}.csv\"");
  350. $oPage->no_cache();
  351. $oPage->add($sResult);
  352. }
  353. else
  354. {
  355. $oPage = new ajax_page("");
  356. $oPage->no_cache();
  357. $oPage->add('<p style="text-align:center"><a style="text-decoration:none" href="../pages/ajax.csvimport.php?operation=get_csv_template&disposition=attachment&class_name='.$sClassName.'"><img border="0" src="../images/csv.png"><br/>'.$sClassDisplayName.'.csv</a></p>');
  358. $oPage->add('<p><textarea rows="5" cols="100">'.$sResult.'</textarea></p>');
  359. }
  360. break;
  361. }
  362. $oPage->output();
  363. ?>