mirror of
https://github.com/Combodo/iTop.git
synced 2026-04-26 12:08:47 +02:00
New attribute: ImageAttribute
SVN:trunk[4129]
This commit is contained in:
@@ -1870,6 +1870,41 @@ EOF
|
||||
$sHTMLValue .= "<input title=\"$sHelpText\" name=\"attr_{$sFieldPrefix}{$sAttCode}{$sNameSuffix}[fcontents]\" type=\"file\" id=\"file_$iId\" onChange=\"UpdateFileName('$iId', this.value)\"/> {$sValidationSpan}{$sReloadSpan}\n";
|
||||
break;
|
||||
|
||||
case 'Image':
|
||||
$aEventsList[] ='validate';
|
||||
$aEventsList[] ='change';
|
||||
$oPage->add_linked_script(utils::GetAbsoluteUrlAppRoot().'js/edit_image.js');
|
||||
$oDocument = $value; // Value is an ormDocument object
|
||||
$sDefaultUrl = $oAttDef->Get('default_image');
|
||||
if (is_object($oDocument) && !$oDocument->IsEmpty())
|
||||
{
|
||||
$sUrl = 'data:'.$oDocument->GetMimeType().';base64,'.base64_encode($oDocument->GetData());
|
||||
}
|
||||
else
|
||||
{
|
||||
$sUrl = $sDefaultUrl;
|
||||
}
|
||||
|
||||
$sHTMLValue = "<div id=\"edit_$iInputId\" class=\"edit-image\"></div>";
|
||||
$sHTMLValue .= " {$sValidationSpan}{$sReloadSpan}\n";
|
||||
|
||||
$aEditImage = array(
|
||||
'input_name' => 'attr_'.$sFieldPrefix.$sAttCode.$sNameSuffix,
|
||||
'max_file_size' => utils::ConvertToBytes(ini_get('upload_max_filesize')),
|
||||
'max_width_px' => $oAttDef->Get('display_max_width'),
|
||||
'max_height_px' => $oAttDef->Get('display_max_height'),
|
||||
'current_image_url' => $sUrl,
|
||||
'default_image_url' => $sDefaultUrl,
|
||||
'labels' => array(
|
||||
'reset_button' => htmlentities(Dict::S('UI:Button:ResetImage'), ENT_QUOTES, 'UTF-8'),
|
||||
'remove_button' => htmlentities(Dict::S('UI:Button:RemoveImage'), ENT_QUOTES, 'UTF-8'),
|
||||
'upload_button' => $sHelpText
|
||||
)
|
||||
);
|
||||
$sEditImageOptions = json_encode($aEditImage);
|
||||
$oPage->add_ready_script("$('#edit_$iInputId').edit_image($sEditImageOptions);");
|
||||
break;
|
||||
|
||||
case 'StopWatch':
|
||||
$sHTMLValue = "The edition of a stopwatch is not allowed!!!";
|
||||
break;
|
||||
@@ -3008,6 +3043,23 @@ EOF
|
||||
$this->Set($sAttCode, $oDocument);
|
||||
}
|
||||
}
|
||||
elseif ($oAttDef->GetEditClass() == 'Image')
|
||||
{
|
||||
// There should be an uploaded file with the named attr_<attCode>
|
||||
if ($value['remove'])
|
||||
{
|
||||
$this->Set($sAttCode, null);
|
||||
}
|
||||
else
|
||||
{
|
||||
$oDocument = $value['fcontents'];
|
||||
if (!$oDocument->IsEmpty())
|
||||
{
|
||||
// A new file has been uploaded
|
||||
$this->Set($sAttCode, $oDocument);
|
||||
}
|
||||
}
|
||||
}
|
||||
elseif ($oAttDef->GetEditClass() == 'One Way Password')
|
||||
{
|
||||
// Check if the password was typed/changed
|
||||
@@ -3143,6 +3195,14 @@ EOF
|
||||
{
|
||||
$value = array('fcontents' => utils::ReadPostedDocument("attr_{$sFormPrefix}{$sAttCode}", 'fcontents'));
|
||||
}
|
||||
elseif ($oAttDef->GetEditClass() == 'Image')
|
||||
{
|
||||
$oImage = utils::ReadPostedDocument("attr_{$sFormPrefix}{$sAttCode}", 'fcontents');
|
||||
$aSize = utils::GetImageSize($oImage->GetData());
|
||||
$oImage = utils::ResizeImageToFit($oImage, $aSize[0], $aSize[1], $oAttDef->Get('storage_max_width'), $oAttDef->Get('storage_max_height'));
|
||||
$aOtherData = utils::ReadPostedParam("attr_{$sFormPrefix}{$sAttCode}", array(), 'raw_data');
|
||||
$value = array('fcontents' => $oImage, 'remove' => $aOtherData['remove']);
|
||||
}
|
||||
elseif ($oAttDef->GetEditClass() == 'RedundancySetting')
|
||||
{
|
||||
$value = $oAttDef->ReadValueFromPostedForm($sFormPrefix);
|
||||
|
||||
@@ -1223,4 +1223,108 @@ class utils
|
||||
}
|
||||
return $sCssRelPath;
|
||||
}
|
||||
|
||||
|
||||
static public function GetImageSize($sImageData)
|
||||
{
|
||||
if (function_exists('getimagesizefromstring')) // PHP 5.4.0 or higher
|
||||
{
|
||||
$aRet = @getimagesizefromstring($sImageData);
|
||||
}
|
||||
else if(ini_get('allow_url_fopen'))
|
||||
{
|
||||
// work around to avoid creating a tmp file
|
||||
$sUri = 'data://application/octet-stream;base64,'.base64_encode($sImageData);
|
||||
$aRet = @getimagesize($sUri);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Damned, need to create a tmp file
|
||||
$sTempFile = tempnam(SetupUtils::GetTmpDir(), 'img-');
|
||||
@file_put_contents($sTempFile, $sImageData);
|
||||
$aRet = @getimagesize($sTempFile);
|
||||
@unlink($sTempFile);
|
||||
}
|
||||
return $aRet;
|
||||
}
|
||||
|
||||
/**
|
||||
* Resize an image attachment so that it fits in the given dimensions
|
||||
* @param ormDocument $oImage The original image stored as an ormDocument
|
||||
* @param int $iWidth Image's original width
|
||||
* @param int $iHeight Image's original height
|
||||
* @param int $iMaxImageWidth Maximum width for the resized image
|
||||
* @param int $iMaxImageHeight Maximum height for the resized image
|
||||
* @return ormDocument The resampled image
|
||||
*/
|
||||
public static function ResizeImageToFit(ormDocument $oImage, $iWidth, $iHeight, $iMaxImageWidth, $iMaxImageHeight)
|
||||
{
|
||||
if (($iWidth <= $iMaxImageWidth) && ($iHeight <= $iMaxImageHeight))
|
||||
{
|
||||
return $oImage;
|
||||
}
|
||||
switch($oImage->GetMimeType())
|
||||
{
|
||||
case 'image/gif':
|
||||
case 'image/jpeg':
|
||||
case 'image/png':
|
||||
$img = @imagecreatefromstring($oImage->GetData());
|
||||
break;
|
||||
|
||||
default:
|
||||
// Unsupported image type, return the image as-is
|
||||
//throw new Exception("Unsupported image type: '".$oImage->GetMimeType()."'. Cannot resize the image, original image will be used.");
|
||||
return $oImage;
|
||||
}
|
||||
if ($img === false)
|
||||
{
|
||||
//throw new Exception("Warning: corrupted image: '".$oImage->GetFileName()." / ".$oImage->GetMimeType()."'. Cannot resize the image, original image will be used.");
|
||||
return $oImage;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Let's scale the image, preserving the transparency for GIFs and PNGs
|
||||
|
||||
$fScale = min($iMaxImageWidth / $iWidth, $iMaxImageHeight / $iHeight);
|
||||
|
||||
$iNewWidth = $iWidth * $fScale;
|
||||
$iNewHeight = $iHeight * $fScale;
|
||||
|
||||
$new = imagecreatetruecolor($iNewWidth, $iNewHeight);
|
||||
|
||||
// Preserve transparency
|
||||
if(($oImage->GetMimeType() == "image/gif") || ($oImage->GetMimeType() == "image/png"))
|
||||
{
|
||||
imagecolortransparent($new, imagecolorallocatealpha($new, 0, 0, 0, 127));
|
||||
imagealphablending($new, false);
|
||||
imagesavealpha($new, true);
|
||||
}
|
||||
|
||||
imagecopyresampled($new, $img, 0, 0, 0, 0, $iNewWidth, $iNewHeight, $iWidth, $iHeight);
|
||||
|
||||
ob_start();
|
||||
switch ($oImage->GetMimeType())
|
||||
{
|
||||
case 'image/gif':
|
||||
imagegif($new); // send image to output buffer
|
||||
break;
|
||||
|
||||
case 'image/jpeg':
|
||||
imagejpeg($new, null, 80); // null = send image to output buffer, 80 = good quality
|
||||
break;
|
||||
|
||||
case 'image/png':
|
||||
imagepng($new, null, 5); // null = send image to output buffer, 5 = medium compression
|
||||
break;
|
||||
}
|
||||
$oResampledImage = new ormDocument(ob_get_contents(), $oImage->GetMimeType(), $oImage->GetFileName());
|
||||
@ob_end_clean();
|
||||
|
||||
imagedestroy($img);
|
||||
imagedestroy($new);
|
||||
|
||||
return $oResampledImage;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -109,6 +109,20 @@ class WizardHelper
|
||||
$oObj->Set($sAttCode, $oDocument);
|
||||
}
|
||||
}
|
||||
else if ( $oAttDef->GetEditClass() == 'Image' )
|
||||
{
|
||||
if ($bReadUploadedFiles)
|
||||
{
|
||||
$oDocument = utils::ReadPostedDocument('attr_'.$sAttCode, 'fcontents');
|
||||
$oObj->Set($sAttCode, $oDocument);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Create a new empty document, just for displaying the file name
|
||||
$oDocument = new ormDocument(null, '', $value);
|
||||
$oObj->Set($sAttCode, $oDocument);
|
||||
}
|
||||
}
|
||||
else if (($oAttDef->IsExternalKey()) && (!empty($value)) && ($value > 0) )
|
||||
{
|
||||
// For external keys: load the target object so that external fields
|
||||
|
||||
@@ -4880,7 +4880,7 @@ class AttributeBlob extends AttributeDefinition
|
||||
// (temporary tables created on disk)
|
||||
// We will have to remove the blobs from the list of attributes when doing the select
|
||||
// then the use of Get() should finalize the load
|
||||
if ($value instanceOf ormDocument)
|
||||
if ($value instanceOf ormDocument && !$value->IsEmpty())
|
||||
{
|
||||
$aValues = array();
|
||||
$aValues[$this->GetCode().'_data'] = $value->GetData();
|
||||
@@ -4946,7 +4946,17 @@ class AttributeBlob extends AttributeDefinition
|
||||
|
||||
public function GetAsXML($value, $oHostObject = null, $bLocalize = true)
|
||||
{
|
||||
return ''; // Not exportable in XML, or as CDATA + some subtags ??
|
||||
$sRet = '';
|
||||
if (is_object($value))
|
||||
{
|
||||
if (!$value->IsEmpty())
|
||||
{
|
||||
$sRet = '<mimetype>'.$value->GetMimeType().'</mimetype>';
|
||||
$sRet .= '<filename>'.$value->GetFileName().'</filename>';
|
||||
$sRet .= '<data>'.base64_encode($value->GetData()).'</data>';
|
||||
}
|
||||
}
|
||||
return $sRet;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -4998,6 +5008,58 @@ class AttributeBlob extends AttributeDefinition
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* An image is a specific type of document, it is stored as several columns in the database
|
||||
*
|
||||
* @package iTopORM
|
||||
*/
|
||||
class AttributeImage extends AttributeBlob
|
||||
{
|
||||
public function GetEditClass() {return "Image";}
|
||||
|
||||
// Facilitate things: allow administrators to upload a document
|
||||
// from a CSV by specifying its path/URL
|
||||
public function MakeRealValue($proposedValue, $oHostObj)
|
||||
{
|
||||
if (!is_object($proposedValue))
|
||||
{
|
||||
if (file_exists($proposedValue) && UserRights::IsAdministrator())
|
||||
{
|
||||
$sContent = file_get_contents($proposedValue);
|
||||
$sExtension = strtolower(pathinfo($proposedValue, PATHINFO_EXTENSION));
|
||||
$sMimeType = "application/x-octet-stream";
|
||||
$aKnownExtensions = array(
|
||||
'jpg' => 'image/jpeg',
|
||||
'jpeg' => 'image/jpeg',
|
||||
'gif' => 'image/gif',
|
||||
'png' => 'image/png'
|
||||
);
|
||||
|
||||
if (!array_key_exists($sExtension, $aKnownExtensions) && extension_loaded('fileinfo'))
|
||||
{
|
||||
$finfo = new finfo(FILEINFO_MIME);
|
||||
$sMimeType = $finfo->file($proposedValue);
|
||||
}
|
||||
return new ormDocument($sContent, $sMimeType);
|
||||
}
|
||||
}
|
||||
return $proposedValue;
|
||||
}
|
||||
|
||||
public function GetAsHTML($value, $oHostObject = null, $bLocalize = true)
|
||||
{
|
||||
$iMaxWidthPx = $this->Get('display_max_width');
|
||||
$iMaxHeightPx = $this->Get('display_max_height');
|
||||
$sUrl = $this->Get('default_image');
|
||||
$sRet = '<img src="'.$sUrl.'" style="max-width: '.$iMaxWidthPx.'px; max-height: '.$iMaxHeightPx.'px">';
|
||||
if (is_object($value) && !$value->IsEmpty())
|
||||
{
|
||||
$sUrl = $value->GetDownloadURL(get_class($oHostObject), $oHostObject->GetKey(), $this->GetCode());
|
||||
$sRet = '<img src="'.$sUrl.'" style="max-width: '.$iMaxWidthPx.'px; max-height: '.$iMaxHeightPx.'px">';
|
||||
}
|
||||
return '<div class="view-image" style="width: '.$iMaxWidthPx.'px; height: '.$iMaxHeightPx.'px;"><span class="helper-middle"></span>'.$sRet.'</div>';
|
||||
}
|
||||
}
|
||||
/**
|
||||
* A stop watch is an ormStopWatch object, it is stored as several columns in the database
|
||||
*
|
||||
|
||||
@@ -355,12 +355,20 @@ class CMDBChangeOpSetAttributeBlob extends CMDBChangeOpSetAttribute
|
||||
$sAttName = $this->Get('attcode');
|
||||
}
|
||||
$oPrevDoc = $this->Get('prevdata');
|
||||
if ($oPrevDoc->IsEmpty())
|
||||
{
|
||||
$sPrevious = '';
|
||||
$sResult = Dict::Format('Change:AttName_Changed_PreviousValue_OldValue', $sAttName, $sPrevious);
|
||||
}
|
||||
else
|
||||
{
|
||||
$sDocView = $oPrevDoc->GetAsHtml();
|
||||
$sDocView .= "<br/>".Dict::Format('UI:OpenDocumentInNewWindow_',$oPrevDoc->GetDisplayLink(get_class($this), $this->GetKey(), 'prevdata')).", \n";
|
||||
$sDocView .= "<br/>".Dict::Format('UI:OpenDocumentInNewWindow_', $oPrevDoc->GetDisplayLink(get_class($this), $this->GetKey(), 'prevdata')).", \n";
|
||||
$sDocView .= Dict::Format('UI:DownloadDocument_', $oPrevDoc->GetDownloadLink(get_class($this), $this->GetKey(), 'prevdata'))."\n";
|
||||
//$sDocView = $oPrevDoc->GetDisplayInline(get_class($this), $this->GetKey(), 'prevdata');
|
||||
$sResult = Dict::Format('Change:AttName_Changed_PreviousValue_OldValue', $sAttName, $sDocView);
|
||||
}
|
||||
}
|
||||
return $sResult;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -187,6 +187,55 @@ EOF
|
||||
return $sPDF;
|
||||
}
|
||||
|
||||
protected function GetValue($oObj, $sAttCode)
|
||||
{
|
||||
switch($sAttCode)
|
||||
{
|
||||
case 'id':
|
||||
$sRet = parent::GetValue($oObj, $sAttCode);
|
||||
break;
|
||||
|
||||
default:
|
||||
$value = $oObj->Get($sAttCode);
|
||||
if ($value instanceof ormDocument)
|
||||
{
|
||||
$oAttDef = MetaModel::GetAttributeDef(get_class($oObj), $sAttCode);
|
||||
if ($oAttDef instanceof AttributeImage)
|
||||
{
|
||||
// To limit the image size in the PDF output, we have to enforce the size as height/width because max-width/max-height have no effect
|
||||
//
|
||||
list($iWidth, $iHeight) = utils::GetImageSize($value->GetData());
|
||||
$iMaxWidthPx = min(48, $oAttDef->Get('display_max_width'));
|
||||
$iMaxHeightPx = min(48, $oAttDef->Get('display_max_height'));
|
||||
|
||||
$fScale = min($iMaxWidthPx / $iWidth, $iMaxHeightPx / $iHeight);
|
||||
$iNewWidth = $iWidth * $fScale;
|
||||
$iNewHeight = $iHeight * $fScale;
|
||||
if ($value->IsEmpty())
|
||||
{
|
||||
$sUrl = $oAttDef->Get('default_image');
|
||||
$sRet = '<img src="'.$sUrl.'" style="width: '.$iNewWidth.'px; height: '.$iNewHeight.'px">';
|
||||
}
|
||||
else
|
||||
{
|
||||
$sUrl = 'data:'.$value->GetMimeType().';base64,'.base64_encode($value->GetData());
|
||||
$sRet = '<img src="'.$sUrl.'" style="width: '.$iNewWidth.'px; height: '.$iNewHeight.'px">';
|
||||
}
|
||||
$sRet = '<div class="view-image">'.$sRet.'</div>';
|
||||
}
|
||||
else
|
||||
{
|
||||
$sRet = parent::GetValue($oObj, $sAttCode);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
$sRet = parent::GetValue($oObj, $sAttCode);
|
||||
}
|
||||
}
|
||||
return $sRet;
|
||||
}
|
||||
|
||||
public function GetSupportedFormats()
|
||||
{
|
||||
return array('pdf' => Dict::S('Core:BulkExport:PDFFormat'));
|
||||
|
||||
@@ -143,6 +143,10 @@ EOF
|
||||
{
|
||||
$sRet = $value->GetTimeSpent();
|
||||
}
|
||||
elseif ($value instanceof ormDocument)
|
||||
{
|
||||
$sRet = '';
|
||||
}
|
||||
elseif ($oAttDef instanceof AttributeString)
|
||||
{
|
||||
$sRet = $oObj->GetAsHTML($sAttCode);
|
||||
|
||||
@@ -107,6 +107,78 @@ table.listResults td {
|
||||
}
|
||||
|
||||
|
||||
table.listResults td .view-image {
|
||||
width: inherit !important;
|
||||
height: inherit !important;
|
||||
}
|
||||
|
||||
table.listResults td .view-image img {
|
||||
max-width: 48px !important;
|
||||
max-height: 48px !important;
|
||||
display: block;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
}
|
||||
|
||||
|
||||
.edit-image .view-image {
|
||||
display: inline-block;
|
||||
}
|
||||
.edit-image .view-image.dirty.compat {
|
||||
background-image: url("ui-lightness/images/ui-bg_diagonals-thick_20_666666_40x40.png");
|
||||
}
|
||||
.edit-image .view-image.dirty.compat img {
|
||||
opacity: 0.3;
|
||||
}
|
||||
|
||||
.edit-image .edit-buttons {
|
||||
display: inline-block;
|
||||
vertical-align: top;
|
||||
margin-top: 4px;
|
||||
margin-left: 3px;
|
||||
}
|
||||
.edit-image .edit-buttons .button {
|
||||
cursor: pointer;
|
||||
margin-bottom: 3px;
|
||||
padding: 2px;
|
||||
background-color: #e87c1e;
|
||||
}
|
||||
.edit-image .edit-buttons .button.disabled {
|
||||
cursor: default;
|
||||
background-color: #555555;
|
||||
opacity: 0.3;
|
||||
}
|
||||
.edit-image .edit-buttons .button .ui-icon {
|
||||
background-image: url("ui-lightness/images/ui-icons_ffffff_256x240.png");
|
||||
}
|
||||
|
||||
.edit-image .file-input {
|
||||
display: block;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Center the image both horizontally and vertically, withing a box which size is fixed (depends on the attribute definition)
|
||||
*/
|
||||
.details .view-image {
|
||||
text-align: center;
|
||||
padding: 2px;
|
||||
border: 2px solid #dddddd;
|
||||
border-radius: 6px;
|
||||
}
|
||||
|
||||
.details .view-image img {
|
||||
display: inline-block;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.details .view-image .helper-middle {
|
||||
display: inline-block;
|
||||
height: 100%;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
|
||||
table.listContainer {
|
||||
border: 0;
|
||||
padding: 0;
|
||||
@@ -2057,6 +2129,22 @@ table.export_parameters td {
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Format for the PDF output
|
||||
*/
|
||||
.table_preview .view-image {
|
||||
width: inherit !important;
|
||||
height: inherit !important;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.table_preview .view-image img {
|
||||
max-width: 48px !important;
|
||||
max-height: 48px !important;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
|
||||
.graph_zoom {
|
||||
display: inline-block;
|
||||
float: right;
|
||||
|
||||
@@ -90,6 +90,83 @@ table.listResults td {
|
||||
padding: 2px;
|
||||
}
|
||||
|
||||
table.listResults td .view-image {
|
||||
// Counteract the forced dimensions (usefull for displaying in the details view)
|
||||
width: inherit !important;
|
||||
height: inherit !important;
|
||||
img {
|
||||
max-width: 48px !important;
|
||||
max-height: 48px !important;
|
||||
display: block;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
}
|
||||
}
|
||||
|
||||
.edit-image {
|
||||
.view-image {
|
||||
display: inline-block;
|
||||
|
||||
&.dirty {
|
||||
// The image will be modified when saving the changes
|
||||
|
||||
&.compat {
|
||||
// Browser not supporting FileReader
|
||||
background-image: url("ui-lightness/images/ui-bg_diagonals-thick_20_666666_40x40.png");
|
||||
img {
|
||||
opacity: 0.3;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.edit-buttons {
|
||||
display: inline-block;
|
||||
vertical-align: top;
|
||||
margin-top: 4px;
|
||||
margin-left: 3px;
|
||||
|
||||
.button {
|
||||
cursor: pointer;
|
||||
margin-bottom: 3px;
|
||||
padding: 2px;
|
||||
background-color: $highlight-color;
|
||||
|
||||
&.disabled {
|
||||
cursor: default;
|
||||
background-color: $grey-color;
|
||||
opacity: 0.3;
|
||||
}
|
||||
.ui-icon {
|
||||
background-image: url("ui-lightness/images/ui-icons_ffffff_256x240.png");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.file-input {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
|
||||
/* Center the image both horizontally and vertically, withing a box which size is fixed (depends on the attribute definition) */
|
||||
.details .view-image {
|
||||
text-align: center;
|
||||
padding: 2px;
|
||||
border: 2px solid #DDDDDD;
|
||||
border-radius: 6px;
|
||||
|
||||
img {
|
||||
display: inline-block;
|
||||
vertical-align: middle;
|
||||
}
|
||||
.helper-middle {
|
||||
// Helper to center the image (requires a span dedicated to this)
|
||||
display: inline-block;
|
||||
height: 100%;
|
||||
vertical-align: middle;
|
||||
}
|
||||
}
|
||||
|
||||
table.listContainer {
|
||||
border: 0;
|
||||
padding: 0;
|
||||
@@ -1520,6 +1597,18 @@ table.export_parameters td {
|
||||
.table_preview div.text-preview {
|
||||
white-space: pre-wrap;
|
||||
}
|
||||
/* Format for the PDF output */
|
||||
.table_preview .view-image {
|
||||
// Counteract the forced dimensions (usefull for displaying in the details view)
|
||||
width: inherit !important;
|
||||
height: inherit !important;
|
||||
text-align: center;
|
||||
img {
|
||||
max-width: 48px !important;
|
||||
max-height: 48px !important;
|
||||
display: inline-block;
|
||||
}
|
||||
}
|
||||
.graph_zoom {
|
||||
display: inline-block;
|
||||
float: right;
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -458,6 +458,14 @@
|
||||
</reconciliation>
|
||||
</properties>
|
||||
<fields>
|
||||
<field id="picture" xsi:type="AttributeImage">
|
||||
<display_max_width>96</display_max_width>
|
||||
<display_max_height>96</display_max_height>
|
||||
<storage_max_width>128</storage_max_width>
|
||||
<storage_max_height>128</storage_max_height>
|
||||
<default_image>images/silhouette.png</default_image>
|
||||
<is_null_allowed>true</is_null_allowed>
|
||||
</field>
|
||||
<field id="first_name" xsi:type="AttributeString">
|
||||
<sql>first_name</sql>
|
||||
<default_value/>
|
||||
@@ -568,6 +576,14 @@
|
||||
<item id="col:col2">
|
||||
<rank>50</rank>
|
||||
<items>
|
||||
<item id="fieldset:Person:personal_info">
|
||||
<rank>5</rank>
|
||||
<items>
|
||||
<item id="picture">
|
||||
<rank>10</rank>
|
||||
</item>
|
||||
</items>
|
||||
</item>
|
||||
<item id="fieldset:Person:notifiy">
|
||||
<rank>10</rank>
|
||||
<items>
|
||||
|
||||
@@ -201,6 +201,8 @@ Dict::Add('EN US', 'English', 'English', array(
|
||||
'Class:Person/Attribute:tickets_list+' => 'All the tickets this person is the caller',
|
||||
'Class:Person/Attribute:manager_id_friendlyname' => 'Manager friendly name',
|
||||
'Class:Person/Attribute:manager_id_friendlyname+' => '',
|
||||
'Class:Person/Attribute:picture' => 'Picture',
|
||||
'Class:Person/Attribute:picture+' => '',
|
||||
));
|
||||
|
||||
//
|
||||
@@ -1880,6 +1882,7 @@ Dict::Add('EN US', 'English', 'English', array(
|
||||
'Server:otherinfo' => 'Other information',
|
||||
'Server:power' => 'Power supply',
|
||||
'Person:info' => 'General information',
|
||||
'Person:personal_info' => 'Personal information',
|
||||
'Person:notifiy' => 'Notification',
|
||||
'Class:Subnet/Tab:IPUsage' => 'IP Usage',
|
||||
'Class:Subnet/Tab:IPUsage-explain' => 'Interfaces having an IP in the range: <em>%1$s</em> to <em>%2$s</em>',
|
||||
|
||||
@@ -146,6 +146,8 @@ Dict::Add('FR FR', 'French', 'Français', array(
|
||||
'Class:Person/Attribute:tickets_list+' => '',
|
||||
'Class:Person/Attribute:manager_id_friendlyname' => 'Manager friendly name',
|
||||
'Class:Person/Attribute:manager_id_friendlyname+' => '',
|
||||
'Class:Person/Attribute:picture' => 'Photo',
|
||||
'Class:Person/Attribute:picture+' => '',
|
||||
));
|
||||
|
||||
//
|
||||
@@ -1850,6 +1852,7 @@ Dict::Add('FR FR', 'French', 'Français', array(
|
||||
'Server:otherinfo' => 'Autres informations',
|
||||
'Server:power' => 'Alimentation électrique',
|
||||
'Person:info' => 'Informations générales',
|
||||
'Person:personal_info' => 'Informations personnelles',
|
||||
'Person:notifiy' => 'Notification',
|
||||
'Class:Subnet/Tab:IPUsage' => 'IP utilisées',
|
||||
'Class:Subnet/Tab:IPUsage-explain' => 'Interfaces ayant une IP dans la plage: <em>%1$s</em> à <em>%2$s</em>',
|
||||
|
||||
BIN
datamodels/2.x/itop-config-mgmt/images/silhouette.png
Normal file
BIN
datamodels/2.x/itop-config-mgmt/images/silhouette.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 3.0 KiB |
@@ -424,6 +424,25 @@ class ApplicationHelper
|
||||
}
|
||||
|
||||
$oApp['combodo.current_user'] = $oUser;
|
||||
|
||||
$sUrl = $oApp['combodo.portal.base.absolute_url'].'img/user-profile-default-256px.png';
|
||||
$oContact = UserRights::GetContactObject();
|
||||
if ($oContact)
|
||||
{
|
||||
if (MetaModel::IsValidAttCode(get_class($oContact), 'picture'))
|
||||
{
|
||||
$oImage = $oContact->Get('picture');
|
||||
if (is_object($oImage) && !$oImage->IsEmpty())
|
||||
{
|
||||
$sUrl = $oImage->GetDownloadURL(get_class($oContact), $oContact->GetKey(), 'picture');
|
||||
}
|
||||
else
|
||||
{
|
||||
$sUrl = MetaModel::GetAttributeDef(get_class($oContact), 'picture')->Get('default_image');
|
||||
}
|
||||
}
|
||||
}
|
||||
$oApp['combodo.current_user_img'] = $sUrl;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -5,8 +5,7 @@
|
||||
{% set bUserConnected = true %}
|
||||
{% set sUserFullname = app['combodo.current_user'].Get('first_name') ~ ' ' ~ app['combodo.current_user'].Get('last_name') %}
|
||||
{% set sUserEmail = app['combodo.current_user'].Get('email') %}
|
||||
{% set sUserPhotoUrl = app['combodo.portal.base.absolute_url'] ~ 'img/user-profile-default-256px.png' %}
|
||||
{% set sUserPhotoUrl = 'https://scontent-fra3-1.xx.fbcdn.net/v/t1.0-9/11050099_10153305298138954_7206181025917413544_n.jpg?oh=728b8e7b1f073b81a2e6b43858c795f8&oe=57E2B0D3' %}
|
||||
{% set sUserPhotoUrl = app['combodo.current_user_img'] %}
|
||||
{% else %}
|
||||
{% set bUserConnected = false %}
|
||||
{% set sUserFullname = '' %}
|
||||
|
||||
@@ -1326,5 +1326,6 @@ When associated with a trigger, each action is given an "order" number, specifyi
|
||||
'UI:NoInlineImage' => 'There is no image available on the server. Use the "Browse" button above to select an image from your computer and upload it to the server.',
|
||||
|
||||
'UI:ToggleFullScreen' => 'Toggle Maximize / Minimize',
|
||||
'UI:Button:ResetImage' => 'Recover the previous image',
|
||||
'UI:Button:RemoveImage' => 'Remove the image',
|
||||
));
|
||||
?>
|
||||
|
||||
@@ -1168,4 +1168,6 @@ Lors de l\'association à un déclencheur, on attribue à chaque action un numé
|
||||
'UI:NoInlineImage' => 'Il n\'y a aucune image de disponible sur le serveur. Utilisez le bouton "Parcourir" (ci-dessus) pour sélectionner une image sur votre ordinateur et la télécharger sur le serveur.',
|
||||
|
||||
'UI:ToggleFullScreen' => 'Agrandir / Minimiser',
|
||||
'UI:Button:ResetImage' => 'Récupérer l\'image initiale',
|
||||
'UI:Button:RemoveImage' => 'Supprimer l\'image',
|
||||
));
|
||||
153
js/edit_image.js
Normal file
153
js/edit_image.js
Normal file
@@ -0,0 +1,153 @@
|
||||
// jQuery UI style "widget" for editing an image (file upload)
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// graph
|
||||
//
|
||||
$(function()
|
||||
{
|
||||
// the widget definition, where "itop" is the namespace,
|
||||
// "dashboard" the widget name
|
||||
$.widget( "itop.edit_image",
|
||||
{
|
||||
// default options
|
||||
options: {
|
||||
input_name: '_image_input_',
|
||||
max_file_size: 0,
|
||||
max_width_px: 32,
|
||||
max_height_px: 32,
|
||||
current_image_url: '',
|
||||
default_image_url: '',
|
||||
labels: {
|
||||
reset_button: 'Reset',
|
||||
remove_button: 'Remove',
|
||||
upload_button: 'Upload'
|
||||
}
|
||||
},
|
||||
|
||||
// the constructor
|
||||
_create: function () {
|
||||
var me = this;
|
||||
me.bLoadedEmpty = (me.options.current_image_url == '');
|
||||
|
||||
var sMarkup = '';
|
||||
sMarkup += '<input type="hidden" id="do_remove_' + me.options.input_name + '" name="' + me.options.input_name + '[remove]" value="0"/>';
|
||||
sMarkup += '<div id="preview_' + me.options.input_name + '" class="view-image" style="width: ' + me.options.max_width_px + 'px; height: ' + me.options.max_height_px + 'px;">';
|
||||
sMarkup += '<span class="helper-middle"></span>';
|
||||
sMarkup += '<img src="' + me.options.current_image_url + '" data-original-src="' + me.options.current_image_url + '" data-default-src="' + me.options.default_image_url + '" style="max-width: ' + me.options.max_width_px + 'px; max-height: ' + me.options.max_height_px + 'px">';
|
||||
sMarkup += '</div>';
|
||||
sMarkup += '<div id="buttons_' + me.options.input_name + '" class="edit-buttons">';
|
||||
sMarkup += '<div title="' + me.options.labels.reset_button + '" id="reset_' + me.options.input_name + '" class="button disabled"><div class="ui-icon ui-icon-arrowreturnthick-1-w"></div></div>';
|
||||
|
||||
var sDisabled = me.bLoadedEmpty ? 'disabled' : '';
|
||||
var sLoadedDisabled = me.bLoadedEmpty ? 'yes' : 'no';
|
||||
sMarkup += '<div title="' + me.options.labels.remove_button + '" id="remove_' + me.options.input_name + '" data-loaded-disabled="' + sLoadedDisabled + '" class="button ' + sDisabled + '"><div class="ui-icon ui-icon-trash"></div></div>';
|
||||
sMarkup += '</div>';
|
||||
|
||||
sMarkup += '<input type="hidden" name="MAX_FILE_SIZE" value="'+me.options.max_file_size+'" />';
|
||||
sMarkup += '<input class="file-input" title="' + me.options.labels.upload_button + '" name="' + me.options.input_name + '[fcontents]" type="file" id="file_' + me.options.input_name + '" />';
|
||||
|
||||
this.element
|
||||
.addClass('edit-image')
|
||||
.append(sMarkup);
|
||||
|
||||
$('#file_' + me.options.input_name).change(function () {
|
||||
|
||||
$('#do_remove_' + me.options.input_name).val('0');
|
||||
|
||||
me.previewImage(this, '#preview_' + me.options.input_name + ' img');
|
||||
|
||||
var oImage = $('#preview_' + me.options.input_name + ' img');
|
||||
oImage.closest('.view-image').addClass('dirty');
|
||||
|
||||
$('#reset_' + me.options.input_name).removeClass('disabled');
|
||||
$('#remove_' + me.options.input_name).removeClass('disabled');
|
||||
});
|
||||
$('#reset_' + me.options.input_name).click(function () {
|
||||
|
||||
if ($(this).hasClass('disabled')) return;
|
||||
|
||||
$('#do_remove_' + me.options.input_name).val('0');
|
||||
|
||||
// Restore the image
|
||||
var oImage = $('#preview_' + me.options.input_name + ' img');
|
||||
oImage.attr('src', oImage.attr('data-original-src'));
|
||||
oImage.closest('.view-image').removeClass('dirty').removeClass('compat');
|
||||
|
||||
// Reset the file input without losing events bound to it
|
||||
var oInput = $('#file_' + me.options.input_name);
|
||||
oInput.replaceWith(oInput.val('').clone(true));
|
||||
|
||||
$('#reset_' + me.options.input_name).addClass('disabled');
|
||||
var oRemoveBtn = $('#remove_' + me.options.input_name);
|
||||
if (oRemoveBtn.attr('data-loaded-disabled') == 'yes') {
|
||||
oRemoveBtn.addClass('disabled');
|
||||
}
|
||||
else {
|
||||
oRemoveBtn.removeClass('disabled');
|
||||
}
|
||||
});
|
||||
$('#remove_' + me.options.input_name).click(function () {
|
||||
|
||||
if ($(this).hasClass('disabled')) return;
|
||||
|
||||
$('#do_remove_' + me.options.input_name).val('1');
|
||||
|
||||
// Restore the default image
|
||||
var oImage = $('#preview_' + me.options.input_name + ' img');
|
||||
oImage.attr('src', oImage.attr('data-default-src'));
|
||||
oImage.closest('.view-image')
|
||||
.removeClass('compat')
|
||||
.addClass('dirty');
|
||||
|
||||
// Reset the file input without losing events bound to it
|
||||
var oInput = $('#file_' + me.options.input_name);
|
||||
oInput.replaceWith(oInput.val('').clone(true));
|
||||
|
||||
var oRemoveBtn = $('#remove_' + me.options.input_name);
|
||||
if (oRemoveBtn.attr('data-loaded-disabled') == 'yes') {
|
||||
$('#reset_' + me.options.input_name).addClass('disabled');
|
||||
}
|
||||
else {
|
||||
$('#reset_' + me.options.input_name).removeClass('disabled');
|
||||
}
|
||||
oRemoveBtn.addClass('disabled');
|
||||
});
|
||||
},
|
||||
// called when created, and later when changing options
|
||||
_refresh: function () {
|
||||
},
|
||||
// events bound via _bind are removed automatically
|
||||
// revert other modifications here
|
||||
_destroy: function () {
|
||||
this.element.removeClass('edit-image');
|
||||
},
|
||||
// _setOptions is called with a hash of all options that are changing
|
||||
_setOptions: function () {
|
||||
this._superApply(arguments);
|
||||
},
|
||||
// _setOption is called for each individual option that is changing
|
||||
_setOption: function (key, value) {
|
||||
this._superApply(arguments);
|
||||
},
|
||||
previewImage: function (input, sImageSelector) {
|
||||
if (input.files && input.files[0]) {
|
||||
if (window.FileReader) {
|
||||
var reader = new FileReader();
|
||||
|
||||
reader.onload = function (e) {
|
||||
$(sImageSelector).attr('src', e.target.result);
|
||||
}
|
||||
|
||||
reader.readAsDataURL(input.files[0]);
|
||||
}
|
||||
else {
|
||||
$(sImageSelector).closest('.view-image').addClass('compat');
|
||||
}
|
||||
}
|
||||
else {
|
||||
$(sImageSelector).closest('.view-image').addClass('compat');
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
@@ -1158,6 +1158,20 @@ EOF
|
||||
$aParameters['is_null_allowed'] = $this->GetPropBoolean($oField, 'is_null_allowed', false);
|
||||
$aParameters['depends_on'] = $sDependencies;
|
||||
}
|
||||
elseif ($sAttType == 'AttributeImage')
|
||||
{
|
||||
$aParameters['is_null_allowed'] = $this->GetPropBoolean($oField, 'is_null_allowed', false);
|
||||
$aParameters['depends_on'] = $sDependencies;
|
||||
$aParameters['display_max_width'] = $this->GetPropNumber($oField, 'display_max_width', 128);
|
||||
$aParameters['display_max_height'] = $this->GetPropNumber($oField, 'display_max_height', 128);
|
||||
$aParameters['storage_max_width'] = $this->GetPropNumber($oField, 'storage_max_width', 256);
|
||||
$aParameters['storage_max_height'] = $this->GetPropNumber($oField, 'storage_max_height', 256);
|
||||
|
||||
if (($sDefault = $oField->GetChildText('default_image')) && (strlen($sDefault) > 0))
|
||||
{
|
||||
$aParameters['default_image'] = "utils::GetAbsoluteUrlModulesRoot().'$sModuleRelativeDir/$sDefault'";
|
||||
}
|
||||
}
|
||||
elseif ($sAttType == 'AttributeStopWatch')
|
||||
{
|
||||
$oStates = $oField->GetUniqueElement('states');
|
||||
|
||||
Reference in New Issue
Block a user