main.attachments.php 25 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740
  1. <?php
  2. // Copyright (C) 2010-2016 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. define('ATTACHMENT_DOWNLOAD_URL', 'pages/ajax.render.php?operation=download_document&class=Attachment&field=contents&id=');
  19. class AttachmentPlugIn implements iApplicationUIExtension, iApplicationObjectExtension
  20. {
  21. protected static $m_bIsModified = false;
  22. public function OnDisplayProperties($oObject, WebPage $oPage, $bEditMode = false)
  23. {
  24. if ($this->GetAttachmentsPosition() == 'properties')
  25. {
  26. $this->DisplayAttachments($oObject, $oPage, $bEditMode);
  27. }
  28. }
  29. public function OnDisplayRelations($oObject, WebPage $oPage, $bEditMode = false)
  30. {
  31. if ($this->GetAttachmentsPosition() == 'relations')
  32. {
  33. $this->DisplayAttachments($oObject, $oPage, $bEditMode);
  34. }
  35. }
  36. public function OnFormSubmit($oObject, $sFormPrefix = '')
  37. {
  38. if ($this->IsTargetObject($oObject))
  39. {
  40. // For new objects attachments are processed in OnDBInsert
  41. if (!$oObject->IsNew())
  42. {
  43. self::UpdateAttachments($oObject);
  44. }
  45. }
  46. }
  47. protected function GetMaxUpload()
  48. {
  49. $iMaxUpload = ini_get('upload_max_filesize');
  50. if (!$iMaxUpload)
  51. {
  52. $sRet = Dict::S('Attachments:UploadNotAllowedOnThisSystem');
  53. }
  54. else
  55. {
  56. $iMaxUpload = utils::ConvertToBytes($iMaxUpload);
  57. if ($iMaxUpload > 1024*1024*1024)
  58. {
  59. $sRet = Dict::Format('Attachment:Max_Go', sprintf('%0.2f', $iMaxUpload/(1024*1024*1024)));
  60. }
  61. else if ($iMaxUpload > 1024*1024)
  62. {
  63. $sRet = Dict::Format('Attachment:Max_Mo', sprintf('%0.2f', $iMaxUpload/(1024*1024)));
  64. }
  65. else
  66. {
  67. $sRet = Dict::Format('Attachment:Max_Ko', sprintf('%0.2f', $iMaxUpload/(1024)));
  68. }
  69. }
  70. return $sRet;
  71. }
  72. public function OnFormCancel($sTempId)
  73. {
  74. // Delete all "pending" attachments for this form
  75. $sOQL = 'SELECT Attachment WHERE temp_id = :temp_id';
  76. $oSearch = DBObjectSearch::FromOQL($sOQL);
  77. $oSet = new DBObjectSet($oSearch, array(), array('temp_id' => $sTempId));
  78. while($oAttachment = $oSet->Fetch())
  79. {
  80. $oAttachment->DBDelete();
  81. // Pending attachment, don't mention it in the history
  82. }
  83. }
  84. public function EnumUsedAttributes($oObject)
  85. {
  86. return array();
  87. }
  88. public function GetIcon($oObject)
  89. {
  90. return '';
  91. }
  92. public function GetHilightClass($oObject)
  93. {
  94. // Possible return values are:
  95. // HILIGHT_CLASS_CRITICAL, HILIGHT_CLASS_WARNING, HILIGHT_CLASS_OK, HILIGHT_CLASS_NONE
  96. return HILIGHT_CLASS_NONE;
  97. }
  98. public function EnumAllowedActions(DBObjectSet $oSet)
  99. {
  100. // No action
  101. return array();
  102. }
  103. public function OnIsModified($oObject)
  104. {
  105. return self::$m_bIsModified;
  106. }
  107. public function OnCheckToWrite($oObject)
  108. {
  109. return array();
  110. }
  111. public function OnCheckToDelete($oObject)
  112. {
  113. return array();
  114. }
  115. public function OnDBUpdate($oObject, $oChange = null)
  116. {
  117. if ($this->IsTargetObject($oObject))
  118. {
  119. // Get all current attachments
  120. $oSearch = DBObjectSearch::FromOQL("SELECT Attachment WHERE item_class = :class AND item_id = :item_id");
  121. $oSet = new DBObjectSet($oSearch, array(), array('class' => get_class($oObject), 'item_id' => $oObject->GetKey()));
  122. while ($oAttachment = $oSet->Fetch())
  123. {
  124. $oAttachment->SetItem($oObject, true /*updateonchange*/);
  125. }
  126. }
  127. }
  128. public function OnDBInsert($oObject, $oChange = null)
  129. {
  130. if ($this->IsTargetObject($oObject))
  131. {
  132. self::UpdateAttachments($oObject, $oChange);
  133. }
  134. }
  135. public function OnDBDelete($oObject, $oChange = null)
  136. {
  137. if ($this->IsTargetObject($oObject))
  138. {
  139. $oSearch = DBObjectSearch::FromOQL("SELECT Attachment WHERE item_class = :class AND item_id = :item_id");
  140. $oSet = new DBObjectSet($oSearch, array(), array('class' => get_class($oObject), 'item_id' => $oObject->GetKey()));
  141. while ($oAttachment = $oSet->Fetch())
  142. {
  143. $oAttachment->DBDelete();
  144. }
  145. }
  146. }
  147. ///////////////////////////////////////////////////////////////////////////////////////////////////////
  148. //
  149. // Plug-ins specific functions
  150. //
  151. ///////////////////////////////////////////////////////////////////////////////////////////////////////
  152. protected function IsTargetObject($oObject)
  153. {
  154. $aAllowedClasses = MetaModel::GetModuleSetting('itop-attachments', 'allowed_classes', array('Ticket'));
  155. foreach($aAllowedClasses as $sAllowedClass)
  156. {
  157. if ($oObject instanceof $sAllowedClass)
  158. {
  159. return true;
  160. }
  161. }
  162. return false;
  163. }
  164. protected function GetAttachmentsPosition()
  165. {
  166. return MetaModel::GetModuleSetting('itop-attachments', 'position', 'relations');
  167. }
  168. var $m_bDeleteEnabled = true;
  169. public function EnableDelete($bEnabled)
  170. {
  171. $this->m_bDeleteEnabled = $bEnabled;
  172. }
  173. public function DisplayAttachments($oObject, WebPage $oPage, $bEditMode = false)
  174. {
  175. // Exit here if the class is not allowed
  176. if (!$this->IsTargetObject($oObject)) return;
  177. $oSearch = DBObjectSearch::FromOQL("SELECT Attachment WHERE item_class = :class AND item_id = :item_id");
  178. $oSet = new DBObjectSet($oSearch, array(), array('class' => get_class($oObject), 'item_id' => $oObject->GetKey()));
  179. if ($this->GetAttachmentsPosition() == 'relations')
  180. {
  181. $sTitle = ($oSet->Count() > 0)? Dict::Format('Attachments:TabTitle_Count', $oSet->Count()) : Dict::S('Attachments:EmptyTabTitle');
  182. $oPage->SetCurrentTab($sTitle);
  183. }
  184. $sMaxWidth = MetaModel::GetModuleSetting('itop-attachment', 'inline_image_max_width', '450px');
  185. $oPage->add_style(
  186. <<<EOF
  187. .attachment {
  188. display: inline-block;
  189. text-align:center;
  190. float:left;
  191. padding:5px;
  192. }
  193. .attachment:hover {
  194. background-color: #e0e0e0;
  195. }
  196. .attachment img {
  197. border: 0;
  198. }
  199. .attachment a {
  200. text-decoration: none;
  201. color: #1C94C4;
  202. }
  203. .btn_hidden {
  204. display: none;
  205. }
  206. .drag_in {
  207. -webkit-box-shadow:inset 0 0 10px 2px #1C94C4;
  208. box-shadow:inset 0 0 10px 2px #1C94C4;
  209. }
  210. #history .attachment-history-added {
  211. padding: 0;
  212. float: none;
  213. }
  214. .inline-image {
  215. cursor: zoom-in;
  216. }
  217. EOF
  218. );
  219. $oPage->add('<fieldset>');
  220. $oPage->add('<legend>'.Dict::S('Attachments:FieldsetTitle').'</legend>');
  221. if ($bEditMode)
  222. {
  223. $sIsDeleteEnabled = $this->m_bDeleteEnabled ? 'true' : 'false';
  224. $iTransactionId = $oPage->GetTransactionId();
  225. $sClass = get_class($oObject);
  226. $iObjectId = $oObject->Getkey();
  227. $sTempId = session_id().'_'.$iTransactionId;
  228. $sDeleteBtn = Dict::S('Attachments:DeleteBtn');
  229. $oPage->add_script(
  230. <<<EOF
  231. function RemoveAttachment(att_id)
  232. {
  233. var bDelete = true;
  234. if ($('#display_attachment_'+att_id).hasClass('image-in-use'))
  235. {
  236. bDelete = window.confirm('This image is used in a description. Delete it anyway?');
  237. }
  238. if (bDelete)
  239. {
  240. $('#attachment_'+att_id).attr('name', 'removed_attachments[]');
  241. $('#display_attachment_'+att_id).hide();
  242. $('#attachment_plugin').trigger('remove_attachment', [att_id]);
  243. }
  244. return false; // Do not submit the form !
  245. }
  246. EOF
  247. );
  248. $oPage->add('<span id="attachments">');
  249. while ($oAttachment = $oSet->Fetch())
  250. {
  251. $iAttId = $oAttachment->GetKey();
  252. $oDoc = $oAttachment->Get('contents');
  253. $sFileName = $oDoc->GetFileName();
  254. $sIcon = utils::GetAbsoluteUrlAppRoot().AttachmentPlugIn::GetFileIcon($sFileName);
  255. $sPreview = $oDoc->IsPreviewAvailable() ? 'true' : 'false';
  256. $sDownloadLink = utils::GetAbsoluteUrlAppRoot().ATTACHMENT_DOWNLOAD_URL.$iAttId;
  257. $oPage->add('<div class="attachment" id="display_attachment_'.$iAttId.'"><a data-preview="'.$sPreview.'" href="'.$sDownloadLink.'"><img src="'.$sIcon.'"><br/>'.$sFileName.'<input id="attachment_'.$iAttId.'" type="hidden" name="attachments[]" value="'.$iAttId.'"/></a><br/>&nbsp;<input id="btn_remove_'.$iAttId.'" type="button" class="btn_hidden" value="Delete" onClick="RemoveAttachment('.$iAttId.');"/>&nbsp;</div>');
  258. }
  259. // Suggested attachments are listed here but treated as temporary
  260. $aDefault = utils::ReadParam('default', array(), false, 'raw_data');
  261. if (array_key_exists('suggested_attachments', $aDefault))
  262. {
  263. $sSuggestedAttachements = $aDefault['suggested_attachments'];
  264. if (is_array($sSuggestedAttachements))
  265. {
  266. $sSuggestedAttachements = implode(',', $sSuggestedAttachements);
  267. }
  268. $oSearch = DBObjectSearch::FromOQL("SELECT Attachment WHERE id IN($sSuggestedAttachements)");
  269. $oSet = new DBObjectSet($oSearch, array());
  270. if ($oSet->Count() > 0)
  271. {
  272. while ($oAttachment = $oSet->Fetch())
  273. {
  274. // Mark the attachments as temporary attachments for the current object/form
  275. $oAttachment->Set('temp_id', $sTempId);
  276. $oAttachment->DBUpdate();
  277. // Display them
  278. $iAttId = $oAttachment->GetKey();
  279. $oDoc = $oAttachment->Get('contents');
  280. $sFileName = $oDoc->GetFileName();
  281. $sIcon = utils::GetAbsoluteUrlAppRoot().AttachmentPlugIn::GetFileIcon($sFileName);
  282. $sDownloadLink = utils::GetAbsoluteUrlAppRoot().ATTACHMENT_DOWNLOAD_URL.$iAttId;
  283. $sPreview = $oDoc->IsPreviewAvailable() ? 'true' : 'false';
  284. $oPage->add('<div class="attachment" id="display_attachment_'.$iAttId.'"><a data-preview="'.$sPreview.'" href="'.$sDownloadLink.'"><img src="'.$sIcon.'"><br/>'.$sFileName.'<input id="attachment_'.$iAttId.'" type="hidden" name="attachments[]" value="'.$iAttId.'"/></a><br/>&nbsp;<input id="btn_remove_'.$iAttId.'" type="button" class="btn_hidden" value="Delete" onClick="RemoveAttachment('.$iAttId.');"/>&nbsp;</div>');
  285. $oPage->add_ready_script("$('#attachment_plugin').trigger('add_attachment', [$iAttId, '".addslashes($sFileName)."', false /* not an line image */]);");
  286. }
  287. }
  288. }
  289. $oPage->add('</span>');
  290. $oPage->add('<div style="clear:both"></div>');
  291. $sMaxUpload = $this->GetMaxUpload();
  292. $oPage->p(Dict::S('Attachments:AddAttachment').'<input type="file" name="file" id="file"><span style="display:none;" id="attachment_loading">&nbsp;<img src="../images/indicator.gif"></span> '.$sMaxUpload);
  293. $oPage->add_linked_script(utils::GetAbsoluteUrlAppRoot().'js/jquery.iframe-transport.js');
  294. $oPage->add_linked_script(utils::GetAbsoluteUrlAppRoot().'js/jquery.fileupload.js');
  295. $sDownloadLink = utils::GetAbsoluteUrlAppRoot().ATTACHMENT_DOWNLOAD_URL;
  296. $oPage->add_ready_script(
  297. <<< EOF
  298. $('#file').fileupload({
  299. url: GetAbsoluteUrlModulesRoot()+'itop-attachments/ajax.attachment.php',
  300. formData: { operation: 'add', temp_id: '$sTempId', obj_class: '$sClass' },
  301. dataType: 'json',
  302. pasteZone: null, // Don't accept files via Chrome's copy/paste
  303. done: function (e, data) {
  304. if(typeof(data.result.error) != 'undefined')
  305. {
  306. if(data.result.error != '')
  307. {
  308. alert(data.result.error);
  309. }
  310. else
  311. {
  312. var sDownloadLink = '$sDownloadLink'+data.result.att_id;
  313. $('#attachments').append('<div class="attachment" id="display_attachment_'+data.result.att_id+'"><a data-preview="'+data.result.preview+'" href="'+sDownloadLink+'"><img src="'+data.result.icon+'"><br/>'+data.result.msg+'<input id="attachment_'+data.result.att_id+'" type="hidden" name="attachments[]" value="'+data.result.att_id+'"/></a><br/><input type="button" class="btn_hidden" value="{$sDeleteBtn}" onClick="RemoveAttachment('+data.result.att_id+');"/></div>');
  314. if($sIsDeleteEnabled)
  315. {
  316. $('#display_attachment_'+data.result.att_id).hover( function() { $(this).children(':button').toggleClass('btn_hidden'); } );
  317. }
  318. $('#attachment_plugin').trigger('add_attachment', [data.result.att_id, data.result.msg, false /* inline image */]);
  319. }
  320. }
  321. },
  322. start: function() {
  323. $('#attachment_loading').show();
  324. },
  325. stop: function() {
  326. $('#attachment_loading').hide();
  327. }
  328. });
  329. $(document).bind('dragover', function (e) {
  330. var bFiles = false;
  331. if (e.dataTransfer && e.dataTransfer.types)
  332. {
  333. for (var i = 0; i < e.dataTransfer.types.length; i++)
  334. {
  335. if (e.dataTransfer.types[i] == "application/x-moz-nativeimage")
  336. {
  337. bFiles = false; // mozilla contains "Files" in the types list when dragging images inside the page, but it also contains "application/x-moz-nativeimage" before
  338. break;
  339. }
  340. if (e.dataTransfer.types[i] == "Files")
  341. {
  342. bFiles = true;
  343. break;
  344. }
  345. }
  346. }
  347. if (!bFiles) return; // Not dragging files
  348. var dropZone = $('#file').closest('fieldset');
  349. if (!dropZone.is(':visible'))
  350. {
  351. // Hidden, but inside an inactive tab? Higlight the tab
  352. var sTabId = dropZone.closest('.ui-tabs-panel').attr('aria-labelledby');
  353. dropZone = $('#'+sTabId).closest('li');
  354. }
  355. timeout = window.dropZoneTimeout;
  356. if (!timeout) {
  357. dropZone.addClass('drag_in');
  358. } else {
  359. clearTimeout(timeout);
  360. }
  361. window.dropZoneTimeout = setTimeout(function () {
  362. window.dropZoneTimeout = null;
  363. dropZone.removeClass('drag_in');
  364. }, 300);
  365. });
  366. // check if the attachments are used by inline images
  367. window.setTimeout( function() {
  368. $('.attachment a').each(function() {
  369. var sUrl = $(this).attr('href');
  370. if($('img[src="'+sUrl+'"]').length > 0)
  371. {
  372. $(this).addClass('image-in-use').find('img').wrap('<div class="image-in-use-wrapper" style="position:relative;display:inline-block;"></div>');
  373. }
  374. });
  375. $('.htmlEditor').each(function() {
  376. var oEditor = $(this).ckeditorGet();
  377. var sHtml = oEditor.getData();
  378. var jElement = $('<div/>').html(sHtml).contents();
  379. jElement.find('img').each(function() {
  380. var sSrc = $(this).attr('src');
  381. $('.attachment a[href="'+sSrc+'"]').parent().addClass('image-in-use').find('img').wrap('<div class="image-in-use-wrapper" style="position:relative;display:inline-block;"></div>');
  382. });
  383. });
  384. $('.image-in-use-wrapper').append('<div style="position:absolute;top:0;left:0;"><img src="../images/transp-lock.png"></div>');
  385. }, 200 );
  386. EOF
  387. );
  388. $oPage->p('<span style="display:none;" id="attachment_loading">Loading, please wait...</span>');
  389. $oPage->p('<input type="hidden" id="attachment_plugin" name="attachment_plugin"/>');
  390. if ($this->m_bDeleteEnabled)
  391. {
  392. $oPage->add_ready_script('$(".attachment").hover( function() {$(this).children(":button").toggleClass("btn_hidden"); } );');
  393. }
  394. }
  395. else
  396. {
  397. $oPage->add('<span id="attachments">');
  398. if ($oSet->Count() == 0)
  399. {
  400. $oPage->add(Dict::S('Attachments:NoAttachment'));
  401. }
  402. else
  403. {
  404. while ($oAttachment = $oSet->Fetch())
  405. {
  406. $iAttId = $oAttachment->GetKey();
  407. $oDoc = $oAttachment->Get('contents');
  408. $sFileName = $oDoc->GetFileName();
  409. $sIcon = utils::GetAbsoluteUrlAppRoot().AttachmentPlugIn::GetFileIcon($sFileName);
  410. $sPreview = $oDoc->IsPreviewAvailable() ? 'true' : 'false';
  411. $sDownloadLink = utils::GetAbsoluteUrlAppRoot().ATTACHMENT_DOWNLOAD_URL.$iAttId;
  412. $oPage->add('<div class="attachment" id="attachment_'.$iAttId.'"><a data-preview="'.$sPreview.'" href="'.$sDownloadLink.'"><img src="'.$sIcon.'"><br/>'.$sFileName.'</a><input type="hidden" name="attachments[]" value="'.$iAttId.'"/><br/>&nbsp;&nbsp;</div>');
  413. }
  414. }
  415. $oPage->add('</span>');
  416. }
  417. $oPage->add('</fieldset>');
  418. $sPreviewNotAvailable = addslashes(Dict::S('Attachments:PreviewNotAvailable'));
  419. $iMaxWidth = MetaModel::GetModuleSetting('itop-attachments', 'preview_max_width', 290);
  420. $oPage->add_ready_script(
  421. <<<EOF
  422. $(document).tooltip({
  423. items: '.attachment a',
  424. position: { my: 'left top', at: 'right top', using: function( position, feedback ) { $( this ).css( position ); }},
  425. content: function() { if ($(this).attr('data-preview') == 'true') { return('<img style=\"max-width:{$iMaxWidth}px\" src=\"'+$(this).attr('href')+'\"></img>');} else { return '$sPreviewNotAvailable'; }}
  426. });
  427. EOF
  428. );
  429. }
  430. protected static function UpdateAttachments($oObject, $oChange = null)
  431. {
  432. self::$m_bIsModified = false;
  433. if (utils::ReadParam('attachment_plugin', 'not-in-form') == 'not-in-form')
  434. {
  435. // Workaround to an issue in iTop < 2.0
  436. // Leave silently if there is no trace of the attachment form
  437. return;
  438. }
  439. $iTransactionId = utils::ReadParam('transaction_id', null);
  440. if (!is_null($iTransactionId))
  441. {
  442. $aActions = array();
  443. $aAttachmentIds = utils::ReadParam('attachments', array());
  444. $aRemovedAttachmentIds = utils::ReadParam('removed_attachments', array());
  445. // Get all current attachments
  446. $oSearch = DBObjectSearch::FromOQL("SELECT Attachment WHERE item_class = :class AND item_id = :item_id");
  447. $oSet = new DBObjectSet($oSearch, array(), array('class' => get_class($oObject), 'item_id' => $oObject->GetKey()));
  448. while ($oAttachment = $oSet->Fetch())
  449. {
  450. // Remove attachments that are no longer attached to the current object
  451. if (in_array($oAttachment->GetKey(), $aRemovedAttachmentIds))
  452. {
  453. $oAttachment->DBDelete();
  454. $aActions[] = self::GetActionChangeOp($oAttachment, false /* false => deletion */);
  455. }
  456. }
  457. // Attach new (temporary) attachements
  458. $sTempId = session_id().'_'.$iTransactionId;
  459. // The object is being created from a form, check if there are pending attachments
  460. // for this object, but deleting the "new" ones that were already removed from the form
  461. $sOQL = 'SELECT Attachment WHERE temp_id = :temp_id';
  462. $oSearch = DBObjectSearch::FromOQL($sOQL);
  463. foreach($aAttachmentIds as $iAttachmentId)
  464. {
  465. $oSet = new DBObjectSet($oSearch, array(), array('temp_id' => $sTempId));
  466. while($oAttachment = $oSet->Fetch())
  467. {
  468. if (in_array($oAttachment->GetKey(),$aRemovedAttachmentIds))
  469. {
  470. $oAttachment->DBDelete();
  471. // temporary attachment removed, don't even mention it in the history
  472. }
  473. else
  474. {
  475. $oAttachment->SetItem($oObject);
  476. $oAttachment->Set('temp_id', '');
  477. $oAttachment->DBUpdate();
  478. // temporary attachment confirmed, list it in the history
  479. $aActions[] = self::GetActionChangeOp($oAttachment, true /* true => creation */);
  480. }
  481. }
  482. }
  483. if (count($aActions) > 0)
  484. {
  485. foreach($aActions as $oChangeOp)
  486. {
  487. self::RecordHistory($oChange, $oObject, $oChangeOp);
  488. }
  489. self::$m_bIsModified = true;
  490. }
  491. }
  492. }
  493. /////////////////////////////////////////////////////////////////////////////////////////
  494. public static function GetFileIcon($sFileName)
  495. {
  496. $aPathParts = pathinfo($sFileName);
  497. switch($aPathParts['extension'])
  498. {
  499. case 'doc':
  500. case 'docx':
  501. $sIcon = 'doc.png';
  502. break;
  503. case 'xls':
  504. case 'xlsx':
  505. $sIcon = 'xls.png';
  506. break;
  507. case 'ppt':
  508. case 'pptx':
  509. $sIcon = 'ppt.png';
  510. break;
  511. case 'pdf':
  512. $sIcon = 'pdf.png';
  513. break;
  514. case 'txt':
  515. case 'text':
  516. $sIcon = 'txt.png';
  517. break;
  518. case 'rtf':
  519. $sIcon = 'rtf.png';
  520. break;
  521. case 'odt':
  522. $sIcon = 'odt.png';
  523. break;
  524. case 'ods':
  525. $sIcon = 'ods.png';
  526. break;
  527. case 'odp':
  528. $sIcon = 'odp.png';
  529. break;
  530. case 'html':
  531. case 'htm':
  532. $sIcon = 'html.png';
  533. break;
  534. case 'png':
  535. case 'gif':
  536. case 'jpg':
  537. case 'jpeg':
  538. case 'tiff':
  539. case 'tif':
  540. case 'bmp':
  541. $sIcon = 'image.png';
  542. break;
  543. case 'zip':
  544. case 'gz':
  545. case 'tgz':
  546. case 'rar':
  547. $sIcon = 'zip.png';
  548. break;
  549. default:
  550. $sIcon = 'document.png';
  551. break;
  552. }
  553. return 'env-'.utils::GetCurrentEnvironment()."/itop-attachments/icons/$sIcon";
  554. }
  555. /////////////////////////////////////////////////////////////////////////
  556. private static function RecordHistory($oChange, $oTargetObject, $oMyChangeOp)
  557. {
  558. if (!is_null($oChange))
  559. {
  560. $oMyChangeOp->Set("change", $oChange->GetKey());
  561. }
  562. $oMyChangeOp->Set("objclass", get_class($oTargetObject));
  563. $oMyChangeOp->Set("objkey", $oTargetObject->GetKey());
  564. $iId = $oMyChangeOp->DBInsertNoReload();
  565. }
  566. /////////////////////////////////////////////////////////////////////////
  567. private static function GetActionChangeOp($oAttachment, $bCreate = true)
  568. {
  569. $oBlob = $oAttachment->Get('contents');
  570. $sFileName = $oBlob->GetFileName();
  571. if ($bCreate)
  572. {
  573. $oChangeOp = new CMDBChangeOpAttachmentAdded();
  574. $oChangeOp->Set('attachment_id', $oAttachment->GetKey());
  575. $oChangeOp->Set('filename', $sFileName);
  576. }
  577. else
  578. {
  579. $oChangeOp = new CMDBChangeOpAttachmentRemoved();
  580. $oChangeOp->Set('filename', $sFileName);
  581. }
  582. return $oChangeOp;
  583. }
  584. }
  585. /**
  586. * Record the modification of a caselog (text)
  587. * since the caselog itself stores the history
  588. * of its entries, there is no need to duplicate
  589. * the text here
  590. *
  591. * @package iTopORM
  592. */
  593. class CMDBChangeOpAttachmentAdded extends CMDBChangeOp
  594. {
  595. public static function Init()
  596. {
  597. $aParams = array
  598. (
  599. "category" => "core/cmdb",
  600. "key_type" => "",
  601. "name_attcode" => "change",
  602. "state_attcode" => "",
  603. "reconc_keys" => array(),
  604. "db_table" => "priv_changeop_attachment_added",
  605. "db_key_field" => "id",
  606. "db_finalclass_field" => "",
  607. );
  608. MetaModel::Init_Params($aParams);
  609. MetaModel::Init_InheritAttributes();
  610. MetaModel::Init_AddAttribute(new AttributeExternalKey("attachment_id", array("targetclass"=>"Attachment", "allowed_values"=>null, "sql"=>"attachment_id", "is_null_allowed"=>true, "on_target_delete"=>DEL_SILENT, "depends_on"=>array())));
  611. MetaModel::Init_AddAttribute(new AttributeString("filename", array("allowed_values"=>null, "sql"=>"filename", "default_value"=>"", "is_null_allowed"=>false, "depends_on"=>array())));
  612. // Display lists
  613. MetaModel::Init_SetZListItems('details', array('attachment_id')); // Attributes to be displayed for the complete details
  614. MetaModel::Init_SetZListItems('list', array('attachment_id')); // Attributes to be displayed for a list
  615. }
  616. /**
  617. * Describe (as a text string) the modifications corresponding to this change
  618. */
  619. public function GetDescription()
  620. {
  621. // Temporary, until we change the options of GetDescription() -needs a more global revision
  622. $bIsHtml = true;
  623. $sResult = '';
  624. $sTargetObjectClass = 'Attachment';
  625. $iTargetObjectKey = $this->Get('attachment_id');
  626. $sFilename = htmlentities($this->Get('filename'), ENT_QUOTES, 'UTF-8');
  627. $oTargetSearch = new DBObjectSearch($sTargetObjectClass);
  628. $oTargetSearch->AddCondition('id', $iTargetObjectKey, '=');
  629. $oMonoObjectSet = new DBObjectSet($oTargetSearch);
  630. if ($oMonoObjectSet->Count() > 0)
  631. {
  632. $oAttachment = $oMonoObjectSet->Fetch();
  633. $oDoc = $oAttachment->Get('contents');
  634. $sPreview = $oDoc->IsPreviewAvailable() ? 'data-preview="true"' : '';
  635. $sResult = Dict::Format('Attachments:History_File_Added', '<span class="attachment-history-added attachment"><a '.$sPreview.' target="_blank" href="'.$oDoc->GetDownloadURL($sTargetObjectClass, $iTargetObjectKey, 'contents').'">'.$sFilename.'</a></span>');
  636. }
  637. else
  638. {
  639. $sResult = Dict::Format('Attachments:History_File_Added', '<span class="attachment-history-deleted">'.$sFilename.'</span>');
  640. }
  641. return $sResult;
  642. }
  643. }
  644. class CMDBChangeOpAttachmentRemoved extends CMDBChangeOp
  645. {
  646. public static function Init()
  647. {
  648. $aParams = array
  649. (
  650. "category" => "core/cmdb",
  651. "key_type" => "",
  652. "name_attcode" => "change",
  653. "state_attcode" => "",
  654. "reconc_keys" => array(),
  655. "db_table" => "priv_changeop_attachment_removed",
  656. "db_key_field" => "id",
  657. "db_finalclass_field" => "",
  658. );
  659. MetaModel::Init_Params($aParams);
  660. MetaModel::Init_InheritAttributes();
  661. MetaModel::Init_AddAttribute(new AttributeString("filename", array("allowed_values"=>null, "sql"=>"filename", "default_value"=>"", "is_null_allowed"=>false, "depends_on"=>array())));
  662. // Display lists
  663. MetaModel::Init_SetZListItems('details', array('filename')); // Attributes to be displayed for the complete details
  664. MetaModel::Init_SetZListItems('list', array('filename')); // Attributes to be displayed for a list
  665. }
  666. /**
  667. * Describe (as a text string) the modifications corresponding to this change
  668. */
  669. public function GetDescription()
  670. {
  671. // Temporary, until we change the options of GetDescription() -needs a more global revision
  672. $bIsHtml = true;
  673. $sResult = Dict::Format('Attachments:History_File_Removed', '<span class="attachment-history-deleted">'.htmlentities($this->Get('filename'), ENT_QUOTES, 'UTF-8').'</span>');
  674. return $sResult;
  675. }
  676. }