N°2889 - Add counter on file attributes / attachments downloads

This commit is contained in:
Molkobain
2022-12-21 22:58:04 +01:00
parent fc97491708
commit 0aa0229170
7 changed files with 126 additions and 41 deletions

View File

@@ -8095,34 +8095,38 @@ class AttributeBlob extends AttributeDefinition
$aColumns[''] = $sPrefix.'_mimetype';
$aColumns['_data'] = $sPrefix.'_data';
$aColumns['_filename'] = $sPrefix.'_filename';
$aColumns['_downloads_count'] = $sPrefix.'_downloads_count';
return $aColumns;
}
public function FromSQLToValue($aCols, $sPrefix = '')
{
if (!array_key_exists($sPrefix, $aCols))
{
if (!array_key_exists($sPrefix, $aCols)) {
$sAvailable = implode(', ', array_keys($aCols));
throw new MissingColumnException("Missing column '$sPrefix' from {$sAvailable}");
}
$sMimeType = isset($aCols[$sPrefix]) ? $aCols[$sPrefix] : '';
if (!array_key_exists($sPrefix.'_data', $aCols))
{
if (!array_key_exists($sPrefix.'_data', $aCols)) {
$sAvailable = implode(', ', array_keys($aCols));
throw new MissingColumnException("Missing column '".$sPrefix."_data' from {$sAvailable}");
}
$data = isset($aCols[$sPrefix.'_data']) ? $aCols[$sPrefix.'_data'] : null;
if (!array_key_exists($sPrefix.'_filename', $aCols))
{
if (!array_key_exists($sPrefix.'_filename', $aCols)) {
$sAvailable = implode(', ', array_keys($aCols));
throw new MissingColumnException("Missing column '".$sPrefix."_filename' from {$sAvailable}");
}
$sFileName = isset($aCols[$sPrefix.'_filename']) ? $aCols[$sPrefix.'_filename'] : '';
$value = new ormDocument($data, $sMimeType, $sFileName);
if (!array_key_exists($sPrefix.'_downloads_count', $aCols)) {
$sAvailable = implode(', ', array_keys($aCols));
throw new MissingColumnException("Missing column '".$sPrefix."_downloads_count' from {$sAvailable}");
}
$iDownloadsCount = isset($aCols[$sPrefix.'_downloads_count']) ? $aCols[$sPrefix.'_downloads_count'] : ormDocument::DEFAULT_DOWNLOADS_COUNT;
$value = new ormDocument($data, $sMimeType, $sFileName, $iDownloadsCount);
return $value;
}
@@ -8148,6 +8152,7 @@ class AttributeBlob extends AttributeDefinition
}
$aValues[$this->GetCode().'_mimetype'] = $value->GetMimeType();
$aValues[$this->GetCode().'_filename'] = $value->GetFileName();
$aValues[$this->GetCode().'_downloads_count'] = $value->GetDownloadsCount();
}
else
{
@@ -8155,6 +8160,7 @@ class AttributeBlob extends AttributeDefinition
$aValues[$this->GetCode().'_data'] = '';
$aValues[$this->GetCode().'_mimetype'] = '';
$aValues[$this->GetCode().'_filename'] = '';
$aValues[$this->GetCode().'_downloads_count'] = ''; // Note: Should this be set to \ormDocument::DEFAULT_DOWNLOADS_COUNT ?
}
return $aValues;
@@ -8166,6 +8172,7 @@ class AttributeBlob extends AttributeDefinition
$aColumns[$this->GetCode().'_data'] = 'LONGBLOB'; // 2^32 (4 Gb)
$aColumns[$this->GetCode().'_mimetype'] = 'VARCHAR(255)'.CMDBSource::GetSqlStringColumnDefinition();
$aColumns[$this->GetCode().'_filename'] = 'VARCHAR(255)'.CMDBSource::GetSqlStringColumnDefinition();
$aColumns[$this->GetCode().'_downloads_count'] = 'INT(11) UNSIGNED';
return $aColumns;
}
@@ -8235,11 +8242,13 @@ class AttributeBlob extends AttributeDefinition
$sRet = '';
if (is_object($value))
{
/** @var \ormDocument $value */
if (!$value->IsEmpty())
{
$sRet = '<mimetype>'.$value->GetMimeType().'</mimetype>';
$sRet .= '<filename>'.$value->GetFileName().'</filename>';
$sRet .= '<data>'.base64_encode($value->GetData()).'</data>';
$sRet .= '<downloads_count>'.$value->GetDownloadsCount().'</downloads_count>';
}
}
@@ -8258,6 +8267,7 @@ class AttributeBlob extends AttributeDefinition
$aValues['data'] = base64_encode($value->GetData());
$aValues['mimetype'] = $value->GetMimeType();
$aValues['filename'] = $value->GetFileName();
$aValues['downloads_count'] = $value->GetDownloadsCount();
}
else
{
@@ -8276,7 +8286,7 @@ class AttributeBlob extends AttributeDefinition
if (isset($json->data))
{
$data = base64_decode($json->data);
$value = new ormDocument($data, $json->mimetype, $json->filename);
$value = new ormDocument($data, $json->mimetype, $json->filename, $json->downloads_count);
}
else
{

View File

@@ -1,28 +1,20 @@
<?php
// Copyright (C) 2010-2021 Combodo SARL
//
// This file is part of iTop.
//
// iTop is free software; you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// iTop is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with iTop. If not, see <http://www.gnu.org/licenses/>
/**
* ormDocument
* encapsulate the behavior of a binary data set that will be stored an attribute of class AttributeBlob
* Copyright (C) 2013-2022 Combodo SARL
*
* @copyright Copyright (C) 2010-2021 Combodo SARL
* @license http://opensource.org/licenses/AGPL-3.0
* This file is part of iTop.
*
* iTop is free software; you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* iTop is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
*/
use Combodo\iTop\Service\Events\EventData;
@@ -35,21 +27,52 @@ use Combodo\iTop\Service\Events\EventService;
*
* @package itopORM
*/
class ormDocument
{
/**
* @var string For content that should be displayed in the browser
* @link https://developer.mozilla.org/fr/docs/Web/HTTP/Headers/Content-Disposition#syntaxe
* @since 3.1.0
*/
public const ENUM_CONTENT_DISPOSITION_INLINE = 'inline';
/**
* @var string For content that should be downloaded on the device. Mind that "attachment" Content-Disposition has nothing to do with the "Attachment" class from the DataModel.
* @link https://developer.mozilla.org/fr/docs/Web/HTTP/Headers/Content-Disposition#syntaxe
* @since 3.1.0
*/
public const ENUM_CONTENT_DISPOSITION_ATTACHMENT = 'attachment';
/**
* @var int Default downloads count of the document, should always be 0.
* @since 3.1.0
*/
public const DEFAULT_DOWNLOADS_COUNT = 0;
protected $m_data;
protected $m_sMimeType;
protected $m_sFileName;
/**
* @var int $m_iDownloadsCount Number of times the document has been downloaded (through the standard API!). Note that download from the browser's cache won't appear.
* @since 3.1.0
*/
private $m_iDownloadsCount;
/**
* Constructor
*
* @param null $data
* @param string $sMimeType
* @param string $sFileName
* @param int $iDownloadsCount
*
* @since 3.1.0 N°2889 Add $iDownloadsCount parameter
*/
public function __construct($data = null, $sMimeType = 'text/plain', $sFileName = '')
public function __construct($data = null, $sMimeType = 'text/plain', $sFileName = '', $iDownloadsCount = self::DEFAULT_DOWNLOADS_COUNT)
{
$this->m_data = $data;
$this->m_sMimeType = $sMimeType;
$this->m_sFileName = $sFileName;
$this->m_iDownloadsCount = $iDownloadsCount;
}
public function __toString()
@@ -109,6 +132,30 @@ class ormDocument
return $this->m_sFileName;
}
/**
* @see static::DownloadDocument()
* @see static::$m_iDownloadsCount
* @return int Number of times the document has been downloaded (through the standard API!)
* @since 3.1.0
*/
public function GetDownloadsCount(): int
{
// Force cast to get 0 instead of null on fields prior to the features that have never been downloaded.
return (int) $this->m_iDownloadsCount;
}
/**
* Increase the number of downloads of the document by $iNumber
*
* @param int $iNumber Step to increase the counter with, default is 1.
* @return void
* @since 3.1.0
*/
public function IncreaseDownloadsCount($iNumber = 1): void
{
$this->m_iDownloadsCount += $iNumber;
}
public function GetAsHTML()
{
$sResult = '';
@@ -119,7 +166,8 @@ class ormDocument
} else {
$data = $this->GetData();
$sSize = utils::BytesToFriendlyFormat(strlen($data));
$sResult = utils::EscapeHtml($this->GetFileName()).' ('.$sSize.')<br/>';
$iDownloadsCount = $this->GetDownloadsCount();
$sResult = utils::EscapeHtml($this->GetFileName()).' ('.$sSize.' / '.$iDownloadsCount.' <i class="fas fa-cloud-download-alt"></i>)<br/>';
}
return $sResult;
}
@@ -196,6 +244,8 @@ class ormDocument
* @param string $sContentDisposition Either 'inline' or 'attachment'
* @param string $sSecretField The attcode of the field containing a "secret" to be provided in order to retrieve the file
* @param string $sSecretValue The value of the secret to be compared with the value of the attribute $sSecretField
*
* @return void
*/
public static function DownloadDocument(WebPage $oPage, $sClass, $id, $sAttCode, $sContentDisposition = 'attachment', $sSecretField = null, $sSecretValue = null)
{
@@ -211,6 +261,7 @@ class ormDocument
usleep(200);
throw new Exception("Invalid secret for class '$sClass' - the object does not exist or you are not allowed to view it");
}
/** @var \ormDocument $oDocument */
$oDocument = $oObj->Get($sAttCode);
if (is_object($oDocument))
{
@@ -224,6 +275,13 @@ class ormDocument
$oPage->SetContentType($oDocument->GetMimeType());
$oPage->SetContentDisposition($sContentDisposition,$oDocument->GetFileName());
$oPage->add($oDocument->GetData());
// Update downloads count only when content disposition is set to "attachment" as other disposition are to display the document within the page
if($sContentDisposition === static::ENUM_CONTENT_DISPOSITION_ATTACHMENT) {
$oDocument->IncreaseDownloadsCount();
$oObj->Set($sAttCode, $oDocument);
$oObj->DBUpdate();
}
}
}
catch(Exception $e)

View File

@@ -69,6 +69,7 @@ Dict::Add('EN US', 'English', 'English', array(
'Attachments:File:Uploader' => 'Uploaded by',
'Attachments:File:Size' => 'Size',
'Attachments:File:MimeType' => 'Type',
'Attachments:File:DownloadsCount' => 'Downloads',
));
//
// Class: Attachment

View File

@@ -68,6 +68,7 @@ Dict::Add('FR FR', 'French', 'Français', array(
'Attachments:File:Uploader' => 'Chargé par',
'Attachments:File:Size' => 'Taille',
'Attachments:File:MimeType' => 'Type',
'Attachments:File:DownloadsCount' => 'Téléchargements',
));
//
// Class: Attachment

View File

@@ -31,6 +31,7 @@ use Combodo\iTop\Application\UI\Base\Component\Input\FileSelect\FileSelectUIBloc
use Combodo\iTop\Application\UI\Base\Component\Panel\PanelUIBlockFactory;
use Combodo\iTop\Renderer\BlockRenderer;
define('ATTACHMENT_DISPLAY_URL', 'pages/ajax.render.php?operation=display_document&class=Attachment&field=contents&id=');
define('ATTACHMENT_DOWNLOAD_URL', 'pages/ajax.document.php?operation=download_document&class=Attachment&field=contents&id=');
define('ATTACHMENTS_RENDERER', 'TableDetailsAttachmentsRenderer');
@@ -416,6 +417,7 @@ class TableDetailsAttachmentsRenderer extends AbstractAttachmentsRenderer
$sFileDate = Dict::S('Attachments:File:Date');
$sFileUploader = Dict::S('Attachments:File:Uploader');
$sFileType = Dict::S('Attachments:File:MimeType');
$sFileDownloadsCount = Dict::S('Attachments:File:DownloadsCount');
if ($bWithDeleteButton)
{
@@ -443,6 +445,7 @@ class TableDetailsAttachmentsRenderer extends AbstractAttachmentsRenderer
'upload-date' => array('label' => $sFileDate, 'description' => $sFileDate),
'uploader' => array('label' => $sFileUploader, 'description' => $sFileUploader),
'type' => array('label' => $sFileType, 'description' => $sFileType),
'downloads-count' => array('label' => $sFileDownloadsCount, 'description' => $sFileDownloadsCount),
);
if ($bWithDeleteButton) {
@@ -496,6 +499,7 @@ JS
/** @var \ormDocument $oDoc */
$oDoc = $oAttachment->Get('contents');
$sDocDisplayUrl = utils::GetAbsoluteUrlAppRoot().ATTACHMENT_DISPLAY_URL.$iAttachmentId;
$sDocDownloadUrl = utils::GetAbsoluteUrlAppRoot().ATTACHMENT_DOWNLOAD_URL.$iAttachmentId;
$sFileName = utils::HtmlEntities($oDoc->GetFileName());
$sTrId = $this->GetAttachmentContainerId($iAttachmentId);
@@ -521,6 +525,7 @@ JS
$sFileType = $oDoc->GetMimeType();
$sAttachmentThumbUrl = utils::GetAbsoluteUrlAppRoot().AttachmentPlugIn::GetFileIcon($sFileName);
$sAttachmentPreviewUrl = '';
$sIconClass = '';
$iMaxWidth = MetaModel::GetModuleSetting('itop-attachments', 'preview_max_width', 290);
$iMaxSizeForPreview = MetaModel::GetModuleSetting('itop-attachments', 'icon_preview_max_size', self::DEFAULT_MAX_SIZE_FOR_PREVIEW);
@@ -530,9 +535,10 @@ JS
if ($oDoc->IsPreviewAvailable())
{
$sIconClass = ' preview';
$sAttachmentPreviewUrl = $sDocDisplayUrl;
if ($oDoc->GetSize() <= $iMaxSizeForPreview)
{
$sAttachmentThumbUrl = $sDocDownloadUrl;
$sAttachmentThumbUrl = $sDocDisplayUrl;
}
$sPreviewMarkup = utils::HtmlEntities('<img src="'.$sDocDownloadUrl.'" style="max-width: '.$iMaxWidth.'"/>');
}
@@ -541,12 +547,13 @@ JS
$aAttachmentLine = array(
'@id' => $sTrId,
'@meta' => 'data-file-type="'.utils::HtmlEntities($sFileType).'" data-file-size-raw="'.utils::HtmlEntities($iFileSize).'" data-file-size-formatted="'.utils::HtmlEntities($sFileFormattedSize).'" data-file-uploader="'.utils::HtmlEntities($sAttachmentUploader).'"',
'icon' => '<a href="'.$sDocDownloadUrl.'" target="_blank" class="trigger-preview '.$sIconClass.'"><img class="ibo-attachment--datatable--icon-preview '.$sIconClass.'" data-tooltip-content="'.$sPreviewMarkup.'" data-tooltip-html-enabled="true" src="'.$sAttachmentThumbUrl.'"></a>',
'icon' => '<a href="'.$sDocDownloadUrl.'" target="_blank" class="trigger-preview '.$sIconClass.'" data-preview-url="$sAttachmentPreviewUrl"><img class="ibo-attachment--datatable--icon-preview '.$sIconClass.'" data-tooltip-content="'.$sPreviewMarkup.'" data-tooltip-html-enabled="true" src="'.$sAttachmentThumbUrl.'"></a>',
'filename' => '<a href="'.$sDocDownloadUrl.'" target="_blank" class="$sIconClass">'.$sFileName.'</a>'.$sAttachmentMeta,
'formatted-size' => $sFileFormattedSize,
'upload-date' => $sAttachmentDateFormatted,
'uploader' => $sAttachmentUploaderForHtml,
'type' => $sFileType,
'downloads-count' => $oDoc->GetDownloadsCount(),
'js' => '',
);

View File

@@ -1245,6 +1245,7 @@ class ObjectController extends BrickController
$aData['att_id'] = $iAttId;
$aData['preview'] = $oDocument->IsPreviewAvailable();
$aData['file_size'] = $oDocument->GetFormattedSize();
$aData['downloads_count'] = $oDocument->GetDownloadsCount();
$aData['creation_date'] = $oAttachment->Get('creation_date');
$aData['user_id_friendlyname'] = $oAttachment->Get('user_id_friendlyname');
$aData['file_type'] = $oDocument->GetMimeType();

View File

@@ -189,6 +189,7 @@ JS
'{{sAttachmentMeta}}',
'{{sFileSize}}',
'{{iFileSizeRaw}}',
'{{iFileDownloadsCount}}',
'{{sAttachmentDate}}',
'{{iAttachmentDateRaw}}',
$bIsDeleteAllowed
@@ -250,6 +251,7 @@ JS
{search: "{{sFileName}}", replace: data.result.msg },
{search: "{{sAttachmentMeta}}", replace:sAttachmentMeta },
{search: "{{sFileSize}}", replace:data.result.file_size },
{search: "{{iFileDownloadsCount}}", replace:data.result.downloads_count },
{search: "{{sAttachmentDate}}", replace:data.result.creation_date },
];
var sAttachmentRow = attachmentRowTemplate ;
@@ -414,6 +416,7 @@ HTML
$iFileSizeRaw = $oDoc->GetSize();
$sFileSize = $oDoc->GetFormattedSize();
$iFileDownloadsCount = $oDoc->GetDownloadsCount();
$bIsTempAttachment = ($oAttachment->Get('item_id') === 0);
$sAttachmentDate = '';
@@ -434,6 +437,7 @@ HTML
$sAttachmentMeta,
$sFileSize,
$iFileSizeRaw,
$iFileDownloadsCount,
$sAttachmentDate,
$iAttachmentDateRaw,
$bIsDeleteAllowed
@@ -460,6 +464,7 @@ HTML
$sTitleFileName = Dict::S('Attachments:File:Name');
$sTitleFileSize = Dict::S('Attachments:File:Size');
$sTitleFileDate = Dict::S('Attachments:File:Date');
$sTitleFileDownloadsCount = Dict::S('Attachments:File:DownloadsCount');
// Optional column
$sDeleteHeaderAsHtml = ($bIsDeleteAllowed) ? '<th role="delete" data-priority="1"></th>' : '';
@@ -470,6 +475,7 @@ HTML
<th role="filename" data-priority="1">$sTitleFileName</th>
<th role="formatted-size">$sTitleFileSize</th>
<th role="upload-date">$sTitleFileDate</th>
<th role="downloads-count">$sTitleFileDownloadsCount</th>
$sDeleteHeaderAsHtml
</thead>
HTML;
@@ -494,7 +500,7 @@ HTML;
*/
protected static function GetAttachmentTableRow(
$iAttId, $sLineStyle, $sDocDownloadUrl, $bHasPreview, $sAttachmentThumbUrl, $sFileName, $sAttachmentMeta, $sFileSize,
$iFileSizeRaw, $sAttachmentDate, $iAttachmentDateRaw, $bIsDeleteAllowed
$iFileSizeRaw, $iFileDownloadsCount, $sAttachmentDate, $iAttachmentDateRaw, $bIsDeleteAllowed
) {
$sDeleteCell = '';
if ($bIsDeleteAllowed)
@@ -511,10 +517,11 @@ HTML;
}
$sHtml .= <<<HTML
<td role="filename"><a href="$sDocDownloadUrl" target="_blank">$sFileName</a>$sAttachmentMeta</td>
<td role="formatted-size" data-order="$iFileSizeRaw">$sFileSize</td>
<td role="upload-date" data-order="$iAttachmentDateRaw">$sAttachmentDate</td>
$sDeleteCell
<td role="filename"><a href="$sDocDownloadUrl" target="_blank">$sFileName</a>$sAttachmentMeta</td>
<td role="formatted-size" data-order="$iFileSizeRaw">$sFileSize</td>
<td role="upload-date" data-order="$iAttachmentDateRaw">$sAttachmentDate</td>
<td role="downloads-count">$iFileDownloadsCount</td>
$sDeleteCell
</tr>
HTML;
return $sHtml;