*/ class BsFileUploadFieldRenderer extends BsFieldRenderer { /** @var DBObjectSet */ private $oAttachmentsSet; public function __construct(Field $oField) { parent::__construct($oField); $oSearch = DBObjectSearch::FromOQL('SELECT Attachment WHERE item_class = :class AND item_id = :item_id'); // Note : AllowAllData set to true here instead of checking scope's flag because we are displaying a value that has been set and validated $oSearch->AllowAllData(); $sObjectClass = get_class($this->oField->GetObject()); $this->oAttachmentsSet = new DBObjectSet($oSearch, array(), array('class' => $sObjectClass, 'item_id' => $this->oField->GetObject()->GetKey())); } /** * @inheritDoc * @throws \CoreException */ public function Render() { $oOutput = parent::Render(); $sObjectClass = get_class($this->oField->GetObject()); $bIsDeleteAllowed = ($this->oField->GetAllowDelete() && !$this->oField->GetReadOnly()); $sTempId = utils::GetUploadTempId($this->oField->GetTransactionId()); $sUploadDropZoneLabel = Dict::S('Portal:Attachments:DropZone:Message'); // Starting field container $oOutput->AddHtml('
'); $sCollapseTogglerIconVisibleClass = 'glyphicon-menu-down'; $sCollapseTogglerIconHiddenClass = 'glyphicon-menu-down collapsed'; $sCollapseTogglerClass = 'form_linkedset_toggler'; $sCollapseTogglerId = $sCollapseTogglerClass . '_' . $this->oField->GetGlobalId(); $sFieldWrapperId = 'form_upload_wrapper_' . $this->oField->GetGlobalId(); $sFieldDescriptionForHTMLTag = ($this->oField->HasDescription()) ? 'data-tooltip-content="'.utils::HtmlEntities($this->oField->GetDescription()).'"' : ''; // If collapsed $sCollapseTogglerClass .= ' collapsed'; $sCollapseTogglerExpanded = 'false'; $sCollapseTogglerIconClass = $sCollapseTogglerIconHiddenClass; $sCollapseJSInitState = 'false'; // Label $oOutput->AddHtml('
'); if ($this->oField->GetLabel() !== '') { $iAttachmentsCount = $this->oAttachmentsSet->Count(); $oOutput ->AddHtml(''); } $oOutput->AddHtml('
'); // Value $oOutput->AddHtml('
'); // - Field feedback $oOutput->AddHtml('
'); // Starting files container $oOutput->AddHtml('
'); // Files list $oOutput->AddHtml('
'); $this->PrepareExistingFiles($oOutput, $bIsDeleteAllowed); $oOutput->Addhtml('
'); $sAttachmentTableId = $this->GetAttachmentsTableId(); $sNoAttachmentLabel = json_encode(Dict::S('Attachments:NoAttachment')); $sDeleteColumnDef = $bIsDeleteAllowed ? '{ targets: [4], orderable: false},' : ''; $oOutput->AddJs( <<oField->GetGlobalId()} === undefined) { buildTable_{$this->oField->GetGlobalId()}(); } }) .on('show.bs.collapse', function(){ $('#{$sCollapseTogglerId} > span.glyphicon').removeClass('{$sCollapseTogglerIconHiddenClass}').addClass('{$sCollapseTogglerIconVisibleClass}'); }) .on('hide.bs.collapse', function(){ $('#{$sCollapseTogglerId} > span.glyphicon').removeClass('{$sCollapseTogglerIconVisibleClass}').addClass('{$sCollapseTogglerIconHiddenClass}'); }); var oTable_{$this->oField->GetGlobalId()}; // Build datatable var buildTable_{$this->oField->GetGlobalId()} = function() { oTable_{$this->oField->GetGlobalId()} = $("table#$sAttachmentTableId").DataTable( { "dom": "tp", "order": [[3, "asc"]], "columnDefs": [ $sDeleteColumnDef { targets: '_all', orderable: true }, ], "language": { "infoEmpty": $sNoAttachmentLabel, "zeroRecords": $sNoAttachmentLabel } } ); } JS ); // Removing upload input if in read only // TODO : Add max upload size when itop attachment has been refactored if (!$this->oField->GetReadOnly()) { $oOutput->AddHtml('
'.Dict::S('Attachments:AddAttachment').''.InlineImage::GetMaxUpload().'
'); } // Ending files container $oOutput->AddHtml('
'); $oOutput->AddHtml('
'); // Ending field container $oOutput->AddHtml('
'); // JS for file upload $iMaxUploadInBytes = AttachmentPlugIn::GetMaxUploadSize(); $sMaxUploadLabel = AttachmentPlugIn::GetMaxUpload(); $sFileTooBigLabel = Dict::Format('Attachments:Error:FileTooLarge', $sMaxUploadLabel); $sFileTooBigLabelForJS = addslashes($sFileTooBigLabel); // Note : This is based on itop-attachement/main.itop-attachments.php $sAttachmentTableRowTemplate = json_encode(self::GetAttachmentTableRow( '{{iAttId}}', '{{sLineStyle}}', '{{sDocDownloadUrl}}', '{{sIconClass}}', '{{sAttachmentThumbUrl}}', '{{sFileName}}', '{{sAttachmentMeta}}', '{{sFileSize}}', '{{iFileSizeRaw}}', '{{sAttachmentDate}}', '{{iAttachmentDateRaw}}', $bIsDeleteAllowed )); $sAttachmentTableId = $this->GetAttachmentsTableId(); $oOutput->AddJs( <<span.attachments-count"), iCountCurrentValue = parseInt(countContainer.text()); countContainer.text(iCountCurrentValue+iIncrement); } $('#{$this->oField->GetGlobalId()}').fileupload({ url: '{$this->oField->GetUploadEndpoint()}', formData: { operation: 'add', temp_id: '{$sTempId}', object_class: '{$sObjectClass}', 'field_name': '{$this->oField->GetId()}' }, dataType: 'json', pasteZone: null, // Don't accept files via Chrome's copy/paste done: function (e, data) { if((data.result.error !== undefined) && window.console) { console.log(data.result.error); } else { var \$oAttachmentTBody = $(this).closest('.fileupload_field_content').find('.attachments_container table#$sAttachmentTableId>tbody'), iAttId = data.result.att_id, sDownloadLink = '{$this->oField->GetDownloadEndpoint()}'.replace(/-sAttachmentId-/, iAttId), sIconClass = (data.result.preview == 'true') ? 'trigger-preview' : '', sAttachmentMeta = ''; // hide "no attachment" line if present \$oAttachmentFirstRow = \$oAttachmentTBody.find("tr:first-child"); \$oAttachmentFirstRow.find("td[colspan]").closest("tr").hide(); // update attachments count IncreaseAttachementsCount(); var replaces = [ {search: "{{iAttId}}", replace:iAttId }, {search: "{{lineStyle}}", replace:'' }, {search: "{{sDocDownloadUrl}}", replace:sDownloadLink }, {search: "{{sIconClass}}", replace:sIconClass }, {search: "{{sAttachmentThumbUrl}}", replace:data.result.icon }, {search: "{{sFileName}}", replace: data.result.msg }, {search: "{{sAttachmentMeta}}", replace:sAttachmentMeta }, {search: "{{sFileSize}}", replace:data.result.file_size }, {search: "{{sAttachmentDate}}", replace:data.result.creation_date }, ]; var sAttachmentRow = attachmentRowTemplate; $.each(replaces, function(indexInArray, value ) { var re = new RegExp(value.search, 'gi'); sAttachmentRow = sAttachmentRow.replace(re, value.replace); }); \$oAttachmentTBody.append(sAttachmentRow); // Preview tooltip if(data.result.preview){ $('#display_attachment_'+data.result.att_id +' a.trigger-preview').tooltip({ container: 'body', html: true, title: function(){ return '
'; } }); } // Remove button handler $('#display_attachment_'+data.result.att_id+' :button').on('click', function(oEvent){ oEvent.preventDefault(); RemoveAttachment(data.result.att_id); }); } }, send: function(e, data){ // Don't send attachment if size is greater than PHP post_max_size, otherwise it will break the request and all its parameters (\$_REQUEST, \$_POST, ...) // Note: We loop on the files as the data structures is an array but in this case, we only upload 1 file at a time. var iTotalSizeInBytes = 0; for(var i = 0; i < data.files.length; i++) { iTotalSizeInBytes += data.files[i].size; } if(iTotalSizeInBytes > $iMaxUploadInBytes) { alert('$sFileTooBigLabelForJS'); return false; } }, start: function() { // Scrolling to dropzone so the user can see that attachments are uploaded $(this)[0].scrollIntoView(); // Showing loader $(this).closest('.upload_container').find('.loader').css('visibility', 'visible'); }, stop: function() { // Hiding the loader $(this).closest('.upload_container').find('.loader').css('visibility', 'hidden'); // Adding this field to the touched fields of the field set so the cancel event is called if necessary $(this).closest(".field_set").trigger("field_change", { id: '{$this->oField->GetGlobalId()}', name: '{$this->oField->GetId()}' }); } }); // Preview tooltip $('table#$sAttachmentTableId>tbody>tr>td a.trigger-preview').each(function(iIndex, oElem){ $(oElem).parent().tooltip({ container: 'body', html: true, title: function(){ return '
'; } }); }); // Remove button handler $('.attachments_container table#$sAttachmentTableId>tbody>tr>td :button').on('click', function(oEvent){ oEvent.preventDefault(); RemoveAttachment($(this).closest('.attachment').find(':input[name="attachments[]"]').val()); }); // Handles a drag / drop overlay if($('#drag_overlay').length === 0) { $('body').append( $('
{$sUploadDropZoneLabel}
') ); } // Handles highlighting of the drop zone // Note : This is inspired by itop-attachments/main.attachments.php $(document).on('dragover', function(oEvent){ var bFiles = false; if (oEvent.dataTransfer && oEvent.dataTransfer.types) { for (var i = 0; i < oEvent.dataTransfer.types.length; i++) { if (oEvent.dataTransfer.types[i] == "application/x-moz-nativeimage") { bFiles = false; // mozilla contains "Files" in the types list when dragging images inside the page, but it also contains "application/x-moz-nativeimage" before break; } if (oEvent.dataTransfer.types[i] == "Files") { bFiles = true; break; } } } if (!bFiles) return; // Not dragging files var oDropZone = $('#drag_overlay'); var oTimeout = window.dropZoneTimeout; // This is to detect when there is no drag over because there is no "drag out" event if (!oTimeout) { oDropZone.removeClass('drag_out').addClass('drag_in'); } else { clearTimeout(oTimeout); } window.dropZoneTimeout = setTimeout(function () { window.dropZoneTimeout = null; oDropZone.removeClass('drag_in').addClass('drag_out'); }, 200); }); JS ); return $oOutput; } /** * * @param \Combodo\iTop\Renderer\RenderingOutput $oOutput * @param boolean $bIsDeleteAllowed * * @throws \Exception * @throws \CoreException */ protected function PrepareExistingFiles(RenderingOutput $oOutput, $bIsDeleteAllowed) { $sAttachmentTableId = $this->GetAttachmentsTableId(); $sDeleteBtn = Dict::S('Portal:Button:Delete'); // If in read only and no attachments, we display a short message if ($this->oField->GetReadOnly() && ($this->oAttachmentsSet->Count() === 0)) { $oOutput->AddHtml(Dict::S('Attachments:NoAttachment')); } else { $sTableHead = self::GetAttachmentTableHeader($bIsDeleteAllowed); $oOutput->Addhtml(<< $sTableHead HTML ); /** @var \Attachment $oAttachment */ while ($oAttachment = $this->oAttachmentsSet->Fetch()) { $iAttId = $oAttachment->GetKey(); $sLineStyle = ''; $sAttachmentMeta = ''; /** @var \ormDocument $oDoc */ $oDoc = $oAttachment->Get('contents'); $sFileName = htmlentities($oDoc->GetFileName(), ENT_QUOTES, 'UTF-8'); $sDocDownloadUrl = str_replace('-sAttachmentId-', $iAttId, $this->oField->GetDownloadEndpoint()); $sAttachmentThumbUrl = utils::GetAbsoluteUrlAppRoot().AttachmentPlugIn::GetFileIcon($sFileName); $sIconClass = ''; if ($oDoc->IsPreviewAvailable()) { $sIconClass = 'trigger-preview'; $iMaxSizeForPreview = MetaModel::GetModuleSetting('itop-attachments', 'icon_preview_max_size', AbstractAttachmentsRenderer::DEFAULT_MAX_SIZE_FOR_PREVIEW); if ($oDoc->GetSize() <= $iMaxSizeForPreview) { $sAttachmentThumbUrl = $sDocDownloadUrl; } } $iFileSizeRaw = $oDoc->GetSize(); $sFileSize = $oDoc->GetFormattedSize(); $bIsTempAttachment = ($oAttachment->Get('item_id') === 0); $sAttachmentDate = ''; $iAttachmentDateRaw = ''; if (!$bIsTempAttachment) { $sAttachmentDate = $oAttachment->Get('creation_date'); $iAttachmentDateRaw = AttributeDateTime::GetAsUnixSeconds($sAttachmentDate); } $oOutput->Addhtml(self::GetAttachmentTableRow( $iAttId, $sLineStyle, $sDocDownloadUrl, $sIconClass, $sAttachmentThumbUrl, $sFileName, $sAttachmentMeta, $sFileSize, $iFileSizeRaw, $sAttachmentDate, $iAttachmentDateRaw, $bIsDeleteAllowed )); } $oOutput->Addhtml(<< HTML ); } } /** * @param bool $bIsDeleteAllowed * * @return string * @since 2.7.0 */ protected static function GetAttachmentTableHeader($bIsDeleteAllowed) { $sTitleThumbnail = Dict::S('Attachments:File:Thumbnail'); $sTitleFileName = Dict::S('Attachments:File:Name'); $sTitleFileSize = Dict::S('Attachments:File:Size'); $sTitleFileDate = Dict::S('Attachments:File:Date'); // Optional column $sDeleteHeaderAsHtml = ($bIsDeleteAllowed) ? '' : ''; return << $sTitleThumbnail $sTitleFileName $sTitleFileSize $sTitleFileDate $sDeleteHeaderAsHtml HTML; } /** * @param int $iAttId * @param string $sLineStyle * @param string $sDocDownloadUrl * @param string $sIconClass * @param string $sAttachmentThumbUrl * @param string $sFileName * @param string $sAttachmentMeta * @param string $sFileSize * @param integer $iFileSizeRaw * @param string $sAttachmentDate * @param integer $iAttachmentDateRaw * @param boolean $bIsDeleteAllowed * * @return string * @since 2.7.0 */ protected static function GetAttachmentTableRow( $iAttId, $sLineStyle, $sDocDownloadUrl, $sIconClass, $sAttachmentThumbUrl, $sFileName, $sAttachmentMeta, $sFileSize, $iFileSizeRaw, $sAttachmentDate, $iAttachmentDateRaw, $bIsDeleteAllowed ) { $sDeleteCell = ''; if ($bIsDeleteAllowed) { $sDeleteBtnLabel = Dict::S('Portal:Button:Delete'); $sDeleteCell = ''; } return << $sFileName$sAttachmentMeta $sFileSize $sAttachmentDate $sDeleteCell HTML; } /** * @return string */ protected function GetAttachmentsTableId() { $sFormFieldId = $this->oField->GetGlobalId(); $sAttachmentTableId = 'attachments-'.$sFormFieldId; return $sAttachmentTableId; } }