main.attachments.php 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568
  1. <?php
  2. // Copyright (C) 2010-2012 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. class AttachmentPlugIn implements iApplicationUIExtension, iApplicationObjectExtension
  19. {
  20. public function OnDisplayProperties($oObject, WebPage $oPage, $bEditMode = false)
  21. {
  22. if ($this->GetAttachmentsPosition() == 'properties')
  23. {
  24. $this->DisplayAttachments($oObject, $oPage, $bEditMode);
  25. }
  26. }
  27. public function OnDisplayRelations($oObject, WebPage $oPage, $bEditMode = false)
  28. {
  29. if ($this->GetAttachmentsPosition() == 'relations')
  30. {
  31. $this->DisplayAttachments($oObject, $oPage, $bEditMode);
  32. }
  33. }
  34. public function OnFormSubmit($oObject, $sFormPrefix = '')
  35. {
  36. if ($this->IsTargetObject($oObject))
  37. {
  38. // For new objects attachments are processed in OnDBInsert
  39. if (!$oObject->IsNew())
  40. {
  41. self::UpdateAttachments($oObject);
  42. }
  43. }
  44. }
  45. protected function GetMaxUpload()
  46. {
  47. $iMaxUpload = ini_get('upload_max_filesize');
  48. if (!$iMaxUpload)
  49. {
  50. $sRet = Dict::S('Attachments:UploadNotAllowedOnThisSystem');
  51. }
  52. else
  53. {
  54. $iMaxUpload = utils::ConvertToBytes($iMaxUpload);
  55. if ($iMaxUpload > 1024*1024*1024)
  56. {
  57. $sRet = Dict::Format('Attachment:Max_Go', sprintf('%0.2f', $iMaxUpload/(1024*1024*1024)));
  58. }
  59. else if ($iMaxUpload > 1024*1024)
  60. {
  61. $sRet = Dict::Format('Attachment:Max_Mo', sprintf('%0.2f', $iMaxUpload/(1024*1024)));
  62. }
  63. else
  64. {
  65. $sRet = Dict::Format('Attachment:Max_Ko', sprintf('%0.2f', $iMaxUpload/(1024)));
  66. }
  67. }
  68. return $sRet;
  69. }
  70. public function OnFormCancel($sTempId)
  71. {
  72. // Delete all "pending" attachments for this form
  73. $sOQL = 'SELECT Attachment WHERE temp_id = :temp_id';
  74. $oSearch = DBObjectSearch::FromOQL($sOQL);
  75. $oSet = new DBObjectSet($oSearch, array(), array('temp_id' => $sTempId));
  76. while($oAttachment = $oSet->Fetch())
  77. {
  78. $oAttachment->DBDelete();
  79. // Pending attachment, don't mention it in the history
  80. }
  81. }
  82. public function EnumUsedAttributes($oObject)
  83. {
  84. return array();
  85. }
  86. public function GetIcon($oObject)
  87. {
  88. return '';
  89. }
  90. public function GetHilightClass($oObject)
  91. {
  92. // Possible return values are:
  93. // HILIGHT_CLASS_CRITICAL, HILIGHT_CLASS_WARNING, HILIGHT_CLASS_OK, HILIGHT_CLASS_NONE
  94. return HILIGHT_CLASS_NONE;
  95. }
  96. public function EnumAllowedActions(DBObjectSet $oSet)
  97. {
  98. // No action
  99. return array();
  100. }
  101. public function OnIsModified($oObject)
  102. {
  103. if ($this->IsTargetObject($oObject))
  104. {
  105. $aAttachmentIds = utils::ReadParam('attachments', array());
  106. $aRemovedAttachmentIds = utils::ReadParam('removed_attachments', array());
  107. if ( (count($aAttachmentIds) > 0) || (count($aRemovedAttachmentIds) > 0) )
  108. {
  109. return true;
  110. }
  111. }
  112. return false;
  113. }
  114. public function OnCheckToWrite($oObject)
  115. {
  116. return array();
  117. }
  118. public function OnCheckToDelete($oObject)
  119. {
  120. return array();
  121. }
  122. public function OnDBUpdate($oObject, $oChange = null)
  123. {
  124. if ($this->IsTargetObject($oObject))
  125. {
  126. // Get all current attachments
  127. $oSearch = DBObjectSearch::FromOQL("SELECT Attachment WHERE item_class = :class AND item_id = :item_id");
  128. $oSet = new DBObjectSet($oSearch, array(), array('class' => get_class($oObject), 'item_id' => $oObject->GetKey()));
  129. while ($oAttachment = $oSet->Fetch())
  130. {
  131. $oAttachment->SetItem($oObject, true /*updateonchange*/);
  132. }
  133. }
  134. }
  135. public function OnDBInsert($oObject, $oChange = null)
  136. {
  137. if ($this->IsTargetObject($oObject))
  138. {
  139. self::UpdateAttachments($oObject, $oChange);
  140. }
  141. }
  142. public function OnDBDelete($oObject, $oChange = null)
  143. {
  144. if ($this->IsTargetObject($oObject))
  145. {
  146. $oSearch = DBObjectSearch::FromOQL("SELECT Attachment WHERE item_class = :class AND item_id = :item_id");
  147. $oSet = new DBObjectSet($oSearch, array(), array('class' => get_class($oObject), 'item_id' => $oObject->GetKey()));
  148. while ($oAttachment = $oSet->Fetch())
  149. {
  150. $oAttachment->DBDelete();
  151. }
  152. }
  153. }
  154. ///////////////////////////////////////////////////////////////////////////////////////////////////////
  155. //
  156. // Plug-ins specific functions
  157. //
  158. ///////////////////////////////////////////////////////////////////////////////////////////////////////
  159. protected function IsTargetObject($oObject)
  160. {
  161. $aAllowedClasses = MetaModel::GetModuleSetting('itop-attachments', 'allowed_classes', array('Ticket'));
  162. foreach($aAllowedClasses as $sAllowedClass)
  163. {
  164. if ($oObject instanceof $sAllowedClass)
  165. {
  166. return true;
  167. }
  168. }
  169. return false;
  170. }
  171. protected function GetAttachmentsPosition()
  172. {
  173. return MetaModel::GetModuleSetting('itop-attachments', 'position', 'relations');
  174. }
  175. var $m_bDeleteEnabled = true;
  176. public function EnableDelete($bEnabled)
  177. {
  178. $this->m_bDeleteEnabled = $bEnabled;
  179. }
  180. public function DisplayAttachments($oObject, WebPage $oPage, $bEditMode = false)
  181. {
  182. // Exit here if the class is not allowed
  183. if (!$this->IsTargetObject($oObject)) return;
  184. $oSearch = DBObjectSearch::FromOQL("SELECT Attachment WHERE item_class = :class AND item_id = :item_id");
  185. $oSet = new DBObjectSet($oSearch, array(), array('class' => get_class($oObject), 'item_id' => $oObject->GetKey()));
  186. if ($this->GetAttachmentsPosition() == 'relations')
  187. {
  188. $sTitle = ($oSet->Count() > 0)? Dict::Format('Attachments:TabTitle_Count', $oSet->Count()) : Dict::S('Attachments:EmptyTabTitle');
  189. $oPage->SetCurrentTab($sTitle);
  190. }
  191. $oPage->add_style(
  192. <<<EOF
  193. .attachment {
  194. display: inline-block;
  195. text-align:center;
  196. float:left;
  197. padding:5px;
  198. }
  199. .attachment:hover {
  200. background-color: #e0e0e0;
  201. }
  202. .attachment img {
  203. border: 0;
  204. }
  205. .attachment a {
  206. text-decoration: none;
  207. color: #1C94C4;
  208. }
  209. .btn_hidden {
  210. display: none;
  211. }
  212. EOF
  213. );
  214. $oPage->add('<fieldset>');
  215. $oPage->add('<legend>'.Dict::S('Attachments:FieldsetTitle').'</legend>');
  216. if ($bEditMode)
  217. {
  218. $sIsDeleteEnabled = $this->m_bDeleteEnabled ? 'true' : 'false';
  219. $iTransactionId = $oPage->GetTransactionId();
  220. $sClass = get_class($oObject);
  221. $sTempId = session_id().'_'.$iTransactionId;
  222. $sDeleteBtn = Dict::S('Attachments:DeleteBtn');
  223. $oPage->add_script(
  224. <<<EOF
  225. function RemoveNewAttachment(att_id)
  226. {
  227. $('#attachment_'+att_id).attr('name', 'removed_attachments[]');
  228. $('#display_attachment_'+att_id).hide();
  229. $('#attachment_plugin').trigger('remove_attachment', [att_id]);
  230. return false; // Do not submit the form !
  231. }
  232. function ajaxFileUpload()
  233. {
  234. //starting setting some animation when the ajax starts and completes
  235. $("#attachment_loading").ajaxStart(function(){
  236. $(this).show();
  237. }).ajaxComplete(function(){
  238. $(this).hide();
  239. });
  240. /*
  241. prepareing ajax file upload
  242. url: the url of script file handling the uploaded files
  243. fileElementId: the file type of input element id and it will be the index of \$_FILES Array()
  244. dataType: it support json, xml
  245. secureuri:use secure protocol
  246. success: call back function when the ajax complete
  247. error: callback function when the ajax failed
  248. */
  249. $.ajaxFileUpload
  250. (
  251. {
  252. url: GetAbsoluteUrlModulesRoot()+'itop-attachments/ajax.attachment.php?obj_class={$sClass}&temp_id={$sTempId}&operation=add',
  253. secureuri:false,
  254. fileElementId:'file',
  255. dataType: 'json',
  256. success: function (data, status)
  257. {
  258. if(typeof(data.error) != 'undefined')
  259. {
  260. if(data.error != '')
  261. {
  262. alert(data.error);
  263. }
  264. else
  265. {
  266. var sDownloadLink = GetAbsoluteUrlAppRoot()+'pages/ajax.render.php/?operation=download_document&class=Attachment&id='+data.att_id+'&field=contents';
  267. $('#attachments').append('<div class="attachment" id="display_attachment_'+data.att_id+'"><a href="'+sDownloadLink+'"><img src="'+data.icon+'"><br/>'+data.msg+'<input id="attachment_'+data.att_id+'" type="hidden" name="attachments[]" value="'+data.att_id+'"/></a><br/><input type="button" class="btn_hidden" value="{$sDeleteBtn}" onClick="RemoveNewAttachment('+data.att_id+');"/></div>');
  268. if($sIsDeleteEnabled)
  269. {
  270. $('#display_attachment_'+data.att_id).hover( function() { $(this).children(':button').toggleClass('btn_hidden'); } );
  271. }
  272. $('#attachment_plugin').trigger('add_attachment', [data.att_id, data.msg]);
  273. //alert(data.msg);
  274. }
  275. }
  276. },
  277. error: function (data, status, e)
  278. {
  279. alert(e);
  280. }
  281. }
  282. )
  283. return false;
  284. }
  285. EOF
  286. );
  287. $oPage->add('<span id="attachments">');
  288. while ($oAttachment = $oSet->Fetch())
  289. {
  290. $iAttId = $oAttachment->GetKey();
  291. $oDoc = $oAttachment->Get('contents');
  292. $sFileName = $oDoc->GetFileName();
  293. $sIcon = utils::GetAbsoluteUrlAppRoot().AttachmentPlugIn::GetFileIcon($sFileName);
  294. $sDownloadLink = utils::GetAbsoluteUrlAppRoot().'pages/ajax.render.php/?operation=download_document&class=Attachment&id='.$iAttId.'&field=contents';
  295. $oPage->add('<div class="attachment" id="attachment_'.$iAttId.'"><a href="'.$sDownloadLink.'"><img src="'.$sIcon.'"><br/>'.$sFileName.'<input type="hidden" name="attachments[]" value="'.$iAttId.'"/></a><br/>&nbsp;<input id="btn_remove_'.$iAttId.'" type="button" class="btn_hidden" value="Delete" onClick="$(\'#attachment_'.$iAttId.'\').remove();"/>&nbsp;</div>');
  296. }
  297. // Suggested attachments are listed here but treated as temporary
  298. $aDefault = utils::ReadParam('default', array(), false, 'raw_data');
  299. if (array_key_exists('suggested_attachments', $aDefault))
  300. {
  301. $sSuggestedAttachements = $aDefault['suggested_attachments'];
  302. if (is_array($sSuggestedAttachements))
  303. {
  304. $sSuggestedAttachements = implode(',', $sSuggestedAttachements);
  305. }
  306. $oSearch = DBObjectSearch::FromOQL("SELECT Attachment WHERE id IN($sSuggestedAttachements)");
  307. $oSet = new DBObjectSet($oSearch, array());
  308. if ($oSet->Count() > 0)
  309. {
  310. while ($oAttachment = $oSet->Fetch())
  311. {
  312. // Mark the attachments as temporary attachments for the current object/form
  313. $oAttachment->Set('temp_id', $sTempId);
  314. $oAttachment->DBUpdate();
  315. // Display them
  316. $iAttId = $oAttachment->GetKey();
  317. $oDoc = $oAttachment->Get('contents');
  318. $sFileName = $oDoc->GetFileName();
  319. $sIcon = utils::GetAbsoluteUrlAppRoot().AttachmentPlugIn::GetFileIcon($sFileName);
  320. $sDownloadLink = utils::GetAbsoluteUrlAppRoot().'pages/ajax.render.php/?operation=download_document&class=Attachment&id='.$iAttId.'&field=contents';
  321. $oPage->add('<div class="attachment" id="display_attachment_'.$iAttId.'"><a href="'.$sDownloadLink.'"><img src="'.$sIcon.'"><br/>'.$sFileName.'<input type="hidden" name="attachments[]" value="'.$iAttId.'"/></a><br/>&nbsp;<input id="btn_remove_'.$iAttId.'" type="button" class="btn_hidden" value="Delete" onClick="RemoveNewAttachment('.$iAttId.');"/>&nbsp;</div>');
  322. $oPage->add_ready_script("$('#attachment_plugin').trigger('add_attachment', [$iAttId, '".addslashes($sFileName)."']);");
  323. }
  324. }
  325. }
  326. $oPage->add('</span>');
  327. $oPage->add('<div style="clear:both"></div>');
  328. $sMaxUpload = $this->GetMaxUpload();
  329. $oPage->p(Dict::S('Attachments:AddAttachment').'<input type="file" name="file" id="file" onChange="ajaxFileUpload();"><span style="display:none;" id="attachment_loading">&nbsp;<img src="../images/indicator.gif"></span> '.$sMaxUpload);
  330. $oPage->p('<span style="display:none;" id="attachment_loading">Loading, please wait...</span>');
  331. $oPage->p('<input type="hidden" id="attachment_plugin" name="attachment_plugin"/>');
  332. $oPage->add('</fieldset>');
  333. if ($this->m_bDeleteEnabled)
  334. {
  335. $oPage->add_ready_script('$(".attachment").hover( function() {$(this).children(":button").toggleClass("btn_hidden"); } );');
  336. }
  337. }
  338. else
  339. {
  340. $oPage->add('<span id="attachments">');
  341. if ($oSet->Count() == 0)
  342. {
  343. $oPage->add(Dict::S('Attachments:NoAttachment'));
  344. }
  345. else
  346. {
  347. while ($oAttachment = $oSet->Fetch())
  348. {
  349. $iAttId = $oAttachment->GetKey();
  350. $oDoc = $oAttachment->Get('contents');
  351. $sFileName = $oDoc->GetFileName();
  352. $sIcon = utils::GetAbsoluteUrlAppRoot().AttachmentPlugIn::GetFileIcon($sFileName);
  353. $sDownloadLink = utils::GetAbsoluteUrlAppRoot().'pages/ajax.render.php/?operation=download_document&class=Attachment&id='.$iAttId.'&field=contents';
  354. $oPage->add('<div class="attachment" id="attachment_'.$iAttId.'"><a href="'.$sDownloadLink.'"><img src="'.$sIcon.'"><br/>'.$sFileName.'</a><input type="hidden" name="attachments[]" value="'.$iAttId.'"/><br/>&nbsp;&nbsp;</div>');
  355. }
  356. }
  357. }
  358. }
  359. protected static function UpdateAttachments($oObject, $oChange = null)
  360. {
  361. if (utils::ReadParam('attachment_plugin', 'not-in-form') == 'not-in-form')
  362. {
  363. // Workaround to an issue in iTop < 2.0
  364. // Leave silently if there is no trace of the attachment form
  365. return;
  366. }
  367. $iTransactionId = utils::ReadParam('transaction_id', null);
  368. if (!is_null($iTransactionId))
  369. {
  370. $aActions = array();
  371. $aAttachmentIds = utils::ReadParam('attachments', array());
  372. // Get all current attachments
  373. $oSearch = DBObjectSearch::FromOQL("SELECT Attachment WHERE item_class = :class AND item_id = :item_id");
  374. $oSet = new DBObjectSet($oSearch, array(), array('class' => get_class($oObject), 'item_id' => $oObject->GetKey()));
  375. while ($oAttachment = $oSet->Fetch())
  376. {
  377. // Remove attachments that are no longer attached to the current object
  378. if (!in_array($oAttachment->GetKey(), $aAttachmentIds))
  379. {
  380. $oAttachment->DBDelete();
  381. $aActions[] = self::GetActionDescription($oAttachment, false /* false => deletion */);
  382. }
  383. }
  384. // Attach new (temporary) attachements
  385. $sTempId = session_id().'_'.$iTransactionId;
  386. // The object is being created from a form, check if there are pending attachments
  387. // for this object, but deleting the "new" ones that were already removed from the form
  388. $aRemovedAttachmentIds = utils::ReadParam('removed_attachments', array());
  389. $sOQL = 'SELECT Attachment WHERE temp_id = :temp_id';
  390. $oSearch = DBObjectSearch::FromOQL($sOQL);
  391. foreach($aAttachmentIds as $iAttachmentId)
  392. {
  393. $oSet = new DBObjectSet($oSearch, array(), array('temp_id' => $sTempId));
  394. while($oAttachment = $oSet->Fetch())
  395. {
  396. if (in_array($oAttachment->GetKey(),$aRemovedAttachmentIds))
  397. {
  398. $oAttachment->DBDelete();
  399. // temporary attachment removed, don't even mention it in the history
  400. }
  401. else
  402. {
  403. $oAttachment->SetItem($oObject);
  404. $oAttachment->Set('temp_id', '');
  405. $oAttachment->DBUpdate();
  406. // temporary attachment confirmed, list it in the history
  407. $aActions[] = self::GetActionDescription($oAttachment, true /* true => creation */);
  408. }
  409. }
  410. }
  411. if (count($aActions) > 0)
  412. {
  413. if ($oChange == null)
  414. {
  415. // Let's create a change if non is supplied
  416. $oChange = MetaModel::NewObject("CMDBChange");
  417. $oChange->Set("date", time());
  418. $sUserString = CMDBChange::GetCurrentUserName();
  419. $oChange->Set("userinfo", $sUserString);
  420. $iChangeId = $oChange->DBInsert();
  421. }
  422. foreach($aActions as $sActionDescription)
  423. {
  424. self::RecordHistory($oChange, $oObject, $sActionDescription);
  425. }
  426. }
  427. }
  428. }
  429. /////////////////////////////////////////////////////////////////////////////////////////
  430. public static function GetFileIcon($sFileName)
  431. {
  432. $aPathParts = pathinfo($sFileName);
  433. switch($aPathParts['extension'])
  434. {
  435. case 'doc':
  436. case 'docx':
  437. $sIcon = 'doc.png';
  438. break;
  439. case 'xls':
  440. case 'xlsx':
  441. $sIcon = 'xls.png';
  442. break;
  443. case 'ppt':
  444. case 'pptx':
  445. $sIcon = 'ppt.png';
  446. break;
  447. case 'pdf':
  448. $sIcon = 'pdf.png';
  449. break;
  450. case 'txt':
  451. case 'text':
  452. $sIcon = 'txt.png';
  453. break;
  454. case 'rtf':
  455. $sIcon = 'rtf.png';
  456. break;
  457. case 'odt':
  458. $sIcon = 'odt.png';
  459. break;
  460. case 'ods':
  461. $sIcon = 'ods.png';
  462. break;
  463. case 'odp':
  464. $sIcon = 'odp.png';
  465. break;
  466. case 'html':
  467. case 'htm':
  468. $sIcon = 'html.png';
  469. break;
  470. case 'png':
  471. case 'gif':
  472. case 'jpg':
  473. case 'jpeg':
  474. case 'tiff':
  475. case 'tif':
  476. case 'bmp':
  477. $sIcon = 'image.png';
  478. break;
  479. case 'zip':
  480. case 'gz':
  481. case 'tgz':
  482. case 'rar':
  483. $sIcon = 'zip.png';
  484. break;
  485. default:
  486. $sIcon = 'document.png';
  487. break;
  488. }
  489. return 'env-'.utils::GetCurrentEnvironment()."/itop-attachments/icons/$sIcon";
  490. }
  491. /////////////////////////////////////////////////////////////////////////
  492. private static function RecordHistory(CMDBChange $oChange, $oTargetObject, $sDescription)
  493. {
  494. $oMyChangeOp = MetaModel::NewObject("CMDBChangeOpPlugin");
  495. $oMyChangeOp->Set("change", $oChange->GetKey());
  496. $oMyChangeOp->Set("objclass", get_class($oTargetObject));
  497. $oMyChangeOp->Set("objkey", $oTargetObject->GetKey());
  498. $oMyChangeOp->Set("description", $sDescription);
  499. $iId = $oMyChangeOp->DBInsertNoReload();
  500. }
  501. /////////////////////////////////////////////////////////////////////////
  502. private static function GetActionDescription($oAttachment, $bCreate = true)
  503. {
  504. $oBlob = $oAttachment->Get('contents');
  505. $sFileName = $oBlob->GetFileName();
  506. if ($bCreate)
  507. {
  508. $sDescription = Dict::Format('Attachments:History_File_Added', $sFileName);
  509. }
  510. else
  511. {
  512. $sDescription = Dict::Format('Attachments:History_File_Removed', $sFileName);
  513. }
  514. return $sDescription;
  515. }
  516. }
  517. ?>