(HTML) Formatted Case Logs, Description and Notifications with inline images uploaded as Attachments. Beta Version !!

SVN:trunk[3916]
This commit is contained in:
Denis Flaven
2016-02-19 10:03:59 +00:00
parent c72bdae8d7
commit 4e24e9899e
46 changed files with 4220 additions and 210 deletions

321
application/Html2Text.php Normal file
View File

@@ -0,0 +1,321 @@
<?php
namespace Html2Text;
/**
* Replace all occurrences of the search string with the replacement string.
*
* @author Sean Murphy <sean@iamseanmurphy.com>
* @copyright Copyright 2012 Sean Murphy. All rights reserved.
* @license http://creativecommons.org/publicdomain/zero/1.0/
* @link http://php.net/manual/function.str-replace.php
*
* @param mixed $search
* @param mixed $replace
* @param mixed $subject
* @param int $count
* @return mixed
*/
function mb_str_replace($search, $replace, $subject, &$count = 0) {
if (!is_array($subject)) {
// Normalize $search and $replace so they are both arrays of the same length
$searches = is_array($search) ? array_values($search) : array($search);
$replacements = is_array($replace) ? array_values($replace) : array($replace);
$replacements = array_pad($replacements, count($searches), '');
foreach ($searches as $key => $search) {
$parts = mb_split(preg_quote($search), $subject);
$count += count($parts) - 1;
$subject = implode($replacements[$key], $parts);
}
} else {
// Call mb_str_replace for each subject in array, recursively
foreach ($subject as $key => $value) {
$subject[$key] = mb_str_replace($search, $replace, $value, $count);
}
}
return $subject;
}
/******************************************************************************
* Copyright (c) 2010 Jevon Wright and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* or
*
* LGPL which is available at http://www.gnu.org/licenses/lgpl.html
*
*
* Contributors:
* Jevon Wright - initial API and implementation
* Denis Flaven - some fixes for properly handling UTF-8 characters
****************************************************************************/
class Html2Text {
/**
* Tries to convert the given HTML into a plain text format - best suited for
* e-mail display, etc.
*
* <p>In particular, it tries to maintain the following features:
* <ul>
* <li>Links are maintained, with the 'href' copied over
* <li>Information in the &lt;head&gt; is lost
* </ul>
*
* @param string html the input HTML
* @return string the HTML converted, as best as possible, to text
* @throws Html2TextException if the HTML could not be loaded as a {@link DOMDocument}
*/
static function convert($html) {
// replace &nbsp; with spaces
$html = str_replace("&nbsp;", " ", $html);
$html = mb_str_replace("\xa0", " ", $html); // DO NOT USE str_replace since it breaks the "à" character which is \xc3 \xa0 in UTF-8
$html = static::fixNewlines($html);
$doc = new \DOMDocument();
if (!@$doc->loadHTML('<?xml encoding="UTF-8">'.$html)) // Forces the UTF-8 character set for HTML fragments
{
throw new Html2TextException("Could not load HTML - badly formed?", $html);
}
$output = static::iterateOverNode($doc);
// remove leading and trailing spaces on each line
$output = preg_replace("/[ \t]*\n[ \t]*/im", "\n", $output);
$output = preg_replace("/ *\t */im", "\t", $output);
// remove unnecessary empty lines
$output = preg_replace("/\n\n\n*/im", "\n\n", $output);
// remove leading and trailing whitespace
$output = trim($output);
return $output;
}
/**
* Unify newlines; in particular, \r\n becomes \n, and
* then \r becomes \n. This means that all newlines (Unix, Windows, Mac)
* all become \ns.
*
* @param string text text with any number of \r, \r\n and \n combinations
* @return string the fixed text
*/
static function fixNewlines($text) {
// replace \r\n to \n
$text = str_replace("\r\n", "\n", $text);
// remove \rs
$text = str_replace("\r", "\n", $text);
return $text;
}
static function nextChildName($node) {
// get the next child
$nextNode = $node->nextSibling;
while ($nextNode != null) {
if ($nextNode instanceof \DOMElement) {
break;
}
$nextNode = $nextNode->nextSibling;
}
$nextName = null;
if ($nextNode instanceof \DOMElement && $nextNode != null) {
$nextName = strtolower($nextNode->nodeName);
}
return $nextName;
}
static function prevChildName($node) {
// get the previous child
$nextNode = $node->previousSibling;
while ($nextNode != null) {
if ($nextNode instanceof \DOMElement) {
break;
}
$nextNode = $nextNode->previousSibling;
}
$nextName = null;
if ($nextNode instanceof \DOMElement && $nextNode != null) {
$nextName = strtolower($nextNode->nodeName);
}
return $nextName;
}
static function iterateOverNode($node) {
if ($node instanceof \DOMText) {
// Replace whitespace characters with a space (equivilant to \s)
return preg_replace("/[\\t\\n\\f\\r ]+/im", " ", $node->wholeText);
}
if ($node instanceof \DOMDocumentType) {
// ignore
return "";
}
$nextName = static::nextChildName($node);
$prevName = static::prevChildName($node);
$name = strtolower($node->nodeName);
// start whitespace
switch ($name) {
case "hr":
return "---------------------------------------------------------------\n";
case "style":
case "head":
case "title":
case "meta":
case "script":
// ignore these tags
return "";
case "h1":
case "h2":
case "h3":
case "h4":
case "h5":
case "h6":
case "ol":
case "ul":
// add two newlines, second line is added below
$output = "\n";
break;
case "td":
case "th":
// add tab char to separate table fields
$output = "\t";
break;
case "tr":
case "p":
case "div":
// add one line
$output = "\n";
break;
case "li":
$output = "- ";
break;
default:
// print out contents of unknown tags
$output = "";
break;
}
// debug
//$output .= "[$name,$nextName]";
if (isset($node->childNodes)) {
for ($i = 0; $i < $node->childNodes->length; $i++) {
$n = $node->childNodes->item($i);
$text = static::iterateOverNode($n);
$output .= $text;
}
}
// end whitespace
switch ($name) {
case "h1":
case "h2":
case "h3":
case "h4":
case "h5":
case "h6":
$output .= "\n";
break;
case "p":
case "br":
// add one line
if ($nextName != "div")
$output .= "\n";
break;
case "div":
// add one line only if the next child isn't a div
if ($nextName != "div" && $nextName != null)
$output .= "\n";
break;
case "a":
// links are returned in [text](link) format
$href = $node->getAttribute("href");
$output = trim($output);
// remove double [[ ]] s from linking images
if (substr($output, 0, 1) == "[" && substr($output, -1) == "]") {
$output = substr($output, 1, strlen($output) - 2);
// for linking images, the title of the <a> overrides the title of the <img>
if ($node->getAttribute("title")) {
$output = $node->getAttribute("title");
}
}
// if there is no link text, but a title attr
if (!$output && $node->getAttribute("title")) {
$output = $node->getAttribute("title");
}
if ($href == null) {
// it doesn't link anywhere
if ($node->getAttribute("name") != null) {
$output = "[$output]";
}
} else {
if ($href == $output || $href == "mailto:$output" || $href == "http://$output" || $href == "https://$output") {
// link to the same address: just use link
$output;
} else {
// replace it
if ($output) {
$output = "[$output]($href)";
} else {
// empty string
$output = $href;
}
}
}
// does the next node require additional whitespace?
switch ($nextName) {
case "h1": case "h2": case "h3": case "h4": case "h5": case "h6":
$output .= "\n";
break;
}
break;
case "img":
if ($node->getAttribute("title")) {
$output = "[" . $node->getAttribute("title") . "]";
} elseif ($node->getAttribute("alt")) {
$output = "[" . $node->getAttribute("alt") . "]";
} else {
$output = "";
}
break;
case "li":
$output .= "\n";
break;
default:
// do nothing
}
return $output;
}
}

View File

@@ -0,0 +1,28 @@
<?php
/******************************************************************************
* Copyright (c) 2010 Jevon Wright and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* or
*
* LGPL which is available at http://www.gnu.org/licenses/lgpl.html
*
*
* Contributors:
* Jevon Wright - initial API and implementation
****************************************************************************/
namespace Html2Text;
class Html2TextException extends \Exception {
var $more_info;
public function __construct($message = "", $more_info = "") {
parent::__construct($message);
$this->more_info = $more_info;
}
}

View File

@@ -1825,7 +1825,7 @@ EOF
$sPreviousLog = is_object($value) ? $value->GetAsHTML($oPage, true /* bEditMode */, array('AttributeText', 'RenderWikiHtml')) : '';
$iEntriesCount = is_object($value) ? count($value->GetIndex()) : 0;
$sHidden = "<input type=\"hidden\" id=\"{$iId}_count\" value=\"$iEntriesCount\"/>"; // To know how many entries the case log already contains
$sHTMLValue = "<div class=\"caselog\" $sStyle><table style=\"width:100%;\"><tr><td>$sHeader<textarea style=\"border:0;width:100%\" title=\"$sHelpText\" name=\"attr_{$sFieldPrefix}{$sAttCode}{$sNameSuffix}\" rows=\"8\" cols=\"40\" id=\"$iId\">".htmlentities($sEditValue, ENT_QUOTES, 'UTF-8')."</textarea>$sPreviousLog</td><td>{$sValidationField}</td></tr></table>$sHidden</div>";
$sHTMLValue = "<div class=\"caselog\" $sStyle><table style=\"width:100%;\"><tr><td>$sHeader<textarea class=\"htmlEditor\" style=\"border:0;width:100%\" title=\"$sHelpText\" name=\"attr_{$sFieldPrefix}{$sAttCode}{$sNameSuffix}\" rows=\"8\" cols=\"40\" id=\"$iId\">".htmlentities($sEditValue, ENT_QUOTES, 'UTF-8')."</textarea>$sPreviousLog</td><td>{$sValidationField}</td></tr></table>$sHidden</div>";
$oPage->add_ready_script("$('#$iId').bind('keyup change validate', function(evt, sFormId) { return ValidateCaseLogField('$iId', $bMandatory, sFormId) } );"); // Custom validation function
break;
@@ -3364,9 +3364,23 @@ EOF
{
$sHTMLValue = '<span>'.$sComment.'</span><br/>';
}
$sHTMLValue .= "<span id=\"field_{$sInputId}\">".self::GetFormElementForField($oPage, $sClass, $sAttCode, $oAttDef, $sValue, $sDisplayValue, $sInputId, '', $iFlags, $aArgs).'</span>';
$sHTMLValue .= "<span style=\"font-family:Tahoma,Verdana,Arial,Helvetica;font-size:12px;\" id=\"field_{$sInputId}\">".self::GetFormElementForField($oPage, $sClass, $sAttCode, $oAttDef, $sValue, $sDisplayValue, $sInputId, '', $iFlags, $aArgs).'</span>';
$aFieldsMap[$sAttCode] = $sInputId;
// Replace the text area with CKEditor
// To change the default settings of the editor,
// a) edit the file /js/ckeditor/config.js
// b) or override some of the configuration settings, using the second parameter of ckeditor()
$aConfig = array();
$sLanguage = strtolower(trim(UserRights::GetUserLanguage()));
$aConfig['font_style'] = $sLanguage;
$aConfig['language'] = $sLanguage;
$aConfig['contentsLanguage'] = $sLanguage;
$aConfig['extraPlugins'] = 'disabler';
$sConfigJS = json_encode($aConfig);
$oPage->add_ready_script("$('#$sInputId').ckeditor(function() { /* callback code */ }, $sConfigJS);"); // Transform $iId into a CKEdit
}
//$aVal = array('label' => '<span title="'.$oAttDef->GetDescription().'">'.$oAttDef->GetLabel().'</span>', 'value' => $sHTMLValue, 'comments' => $sComments, 'infos' => $sInfos);
$oPage->add('<fieldset><legend>'.$oAttDef->GetLabel().'</legend>');

View File

@@ -1,5 +1,5 @@
<?php
// Copyright (C) 2010-2015 Combodo SARL
// Copyright (C) 2010-2016 Combodo SARL
//
// This file is part of iTop.
//
@@ -19,7 +19,7 @@
/**
* DisplayBlock and derived class
*
* @copyright Copyright (C) 2010-2015 Combodo SARL
* @copyright Copyright (C) 2010-2016 Combodo SARL
* @license http://opensource.org/licenses/AGPL-3.0
*/
@@ -1289,8 +1289,34 @@ class HistoryBlock extends DisplayBlock
else
{
$sHtml .= $this->GetHistoryTable($oPage, $oSet);
}
}
$sMaxWidth = MetaModel::GetModuleSetting('itop-attachment', 'inline_image_max_width', '450px');
$oPage->add_ready_script("$('.case-log-history-entry-toggle').on('click', function () { $(this).closest('.case-log-history-entry').toggleClass('expanded');});");
$oPage->add_ready_script(
<<<EOF
$('.history_entry').each(function() {
var jMe = $(this)
jMe.find('img[data-att-id]').each(function() {
if ('$sMaxWidth' != '')
{
$(this).css({'max-width': '$sMaxWidth', width: '', height: '', 'max-height': ''});
}
$(this).addClass('inline-image');
$(this).attr('href', $(this).attr('src'));
}).magnificPopup({type: 'image', closeOnContentClick: true });
var oContent = $(this).find('.history_html_content');
if (jMe.height() < oContent.height())
{
jMe.prepend('<span class="history_truncated_toggler"></span>');
jMe.find('.history_truncated_toggler').on('click', function() {
jMe.toggleClass('history_entry_truncated');
});
}
});
EOF
);
}
return $sHtml;
}
@@ -1319,7 +1345,7 @@ class HistoryBlock extends DisplayBlock
}
$aAttribs = array('date' => array('label' => Dict::S('UI:History:Date'), 'description' => Dict::S('UI:History:Date+')),
'userinfo' => array('label' => Dict::S('UI:History:User'), 'description' => Dict::S('UI:History:User+')),
'log' => array('label' => Dict::S('UI:History:Changes'), 'description' => Dict::S('UI:History:Changes+')),
'log' => array('label' => Dict::S('UI:History:Changes').'<span style="display:block;float:right">Expand All / Collapse All</span>', 'description' => Dict::S('UI:History:Changes+')),
);
$aValues = array();
foreach($aChanges as $aChange)

View File

@@ -1,5 +1,5 @@
<?php
// Copyright (C) 2010-2015 Combodo SARL
// Copyright (C) 2010-2016 Combodo SARL
//
// This file is part of iTop.
//
@@ -20,7 +20,7 @@
/**
* Class iTopWebPage
*
* @copyright Copyright (C) 2010-2015 Combodo SARL
* @copyright Copyright (C) 2010-2016 Combodo SARL
* @license http://opensource.org/licenses/AGPL-3.0
*/
@@ -416,7 +416,7 @@ EOF
ShowDebug();
$('#logOffBtn>ul').popupmenu();
$('.caselog_header').click( function () { $(this).toggleClass('open').next('.caselog_entry').toggle(); });
$('.caselog_header').click( function () { $(this).toggleClass('open').next('.caselog_entry,.caselog_entry_html').toggle(); });
$(document).ajaxSend(function(event, jqxhr, options) {
jqxhr.setRequestHeader('X-Combodo-Ajax', 'true');

View File

@@ -1,4 +1,5 @@
<?php
use Html2Text\Html2Text;
// Copyright (C) 2010-2016 Combodo SARL
//
// This file is part of iTop.
@@ -26,11 +27,14 @@
require_once(APPROOT.'/core/config.class.inc.php');
require_once(APPROOT.'/application/transaction.class.inc.php');
require_once(APPROOT.'application/Html2Text.php');
require_once(APPROOT.'application/Html2TextException.php');
define('ITOP_CONFIG_FILE', 'config-itop.php');
define('ITOP_DEFAULT_CONFIG_FILE', APPCONF.ITOP_DEFAULT_ENV.'/'.ITOP_CONFIG_FILE);
define('SERVER_NAME_PLACEHOLDER', '$SERVER_NAME$');
define('ATTACHMENT_DOWNLOAD_URL', 'pages/ajax.render.php?operation=download_document&class=Attachment&field=contents&id=');
class FileUploadException extends Exception
{
@@ -1134,4 +1138,65 @@ class utils
asort($aPossibleEncodings);
return $aPossibleEncodings;
}
/**
* Convert a string containing some (valid) HTML markup to plain text
* @param string $sHtml
* @return string
*/
public static function HtmlToText($sHtml)
{
try
{
//return '<?xml encoding="UTF-8">'.$sHtml;
return \Html2Text\Html2Text::convert('<?xml encoding="UTF-8">'.$sHtml);
}
catch(Exception $e)
{
return $e->getMessage();
}
}
/**
* Convert (?) plain text to some HTML markup by replacing newlines by </br> tags
* and escaping HTML entities
* @param string $sText
* @return string
*/
public static function TextToHtml($sText)
{
$sText = str_replace("\r\n", "\n", $sText);
$sText = str_replace("\r", "\n", $sText);
return str_replace("\n", '</br>', htmlentities($sText, ENT_QUOTES, 'UTF-8'));
}
/**
* Parses the supplied HTML fragment to rebuild the attribute src="" for images
* that refer to an attachment (detected via the attribute data-att-id="") so that
* the URL is consistent with the current URL of the application.
* @param string $sHtml The HTML fragment to process
* @return string The modified HTML
*/
public static function FixInlineAttachments($sHtml)
{
$aNeedles = array();
$aReplacements = array();
// Find img tags with an attribute data-att-id
if (preg_match_all('/<img ([^>]*)data-att-id="([0-9]+)"([^>]*)>/i', $sHtml, $aMatches, PREG_SET_ORDER | PREG_OFFSET_CAPTURE))
{
$sUrl = utils::GetAbsoluteUrlAppRoot().ATTACHMENT_DOWNLOAD_URL;
foreach($aMatches as $aImgInfo)
{
$sImgTag = $aImgInfo[0][0];
$sAttId = $aImgInfo[2][0];
$sNewImgTag = preg_replace('/src="[^"]+"/', 'src="'.$sUrl.$sAttId.'"', $sImgTag); // preserve other attributes
$aNeedles[] = $sImgTag;
$aReplacements[] = $sNewImgTag;
}
$sHtml = str_replace($aNeedles, $aReplacements, $sHtml);
}
return $sHtml;
}
}

View File

@@ -1,5 +1,5 @@
<?php
// Copyright (C) 2010-2012 Combodo SARL
// Copyright (C) 2010-2016 Combodo SARL
//
// This file is part of iTop.
//
@@ -20,7 +20,7 @@
/**
* Persistent classes (internal): user defined actions
*
* @copyright Copyright (C) 2010-2012 Combodo SARL
* @copyright Copyright (C) 2010-2016 Combodo SARL
* @license http://opensource.org/licenses/AGPL-3.0
*/
@@ -157,7 +157,7 @@ class ActionEmail extends ActionNotification
MetaModel::Init_AddAttribute(new AttributeOQL("cc", array("allowed_values"=>null, "sql"=>"cc", "default_value"=>null, "is_null_allowed"=>true, "depends_on"=>array())));
MetaModel::Init_AddAttribute(new AttributeOQL("bcc", array("allowed_values"=>null, "sql"=>"bcc", "default_value"=>null, "is_null_allowed"=>true, "depends_on"=>array())));
MetaModel::Init_AddAttribute(new AttributeTemplateString("subject", array("allowed_values"=>null, "sql"=>"subject", "default_value"=>null, "is_null_allowed"=>false, "depends_on"=>array())));
MetaModel::Init_AddAttribute(new AttributeTemplateText("body", array("allowed_values"=>null, "sql"=>"body", "default_value"=>null, "is_null_allowed"=>false, "depends_on"=>array())));
MetaModel::Init_AddAttribute(new AttributeTemplateHTML("body", array("allowed_values"=>null, "sql"=>"body", "default_value"=>null, "is_null_allowed"=>false, "depends_on"=>array())));
MetaModel::Init_AddAttribute(new AttributeEnum("importance", array("allowed_values"=>new ValueSetEnum('low,normal,high'), "sql"=>"importance", "default_value"=>'normal', "is_null_allowed"=>false, "depends_on"=>array())));
// Display lists

View File

@@ -30,6 +30,7 @@ require_once('ormdocument.class.inc.php');
require_once('ormstopwatch.class.inc.php');
require_once('ormpassword.class.inc.php');
require_once('ormcaselog.class.inc.php');
require_once('htmlsanitizer.class.inc.php');
/**
* MissingColumnException - sent if an attribute is being created but the column is missing in the row
@@ -436,6 +437,15 @@ abstract class AttributeDefinition
{
return (string)$sValue;
}
/**
* For fields containing a potential markup, return the value without this markup
* @return string
*/
public function GetAsPlainText($sValue, $oHostObj = null)
{
return (string) $this->GetEditValue($sValue, $oHostObj);
}
/**
* Helper to get a value that will be JSON encoded
@@ -476,7 +486,7 @@ abstract class AttributeDefinition
/**
* Override to escape the value when read by DBObject::GetAsCSV()
*/
public function GetAsCSV($sValue, $sSeparator = ',', $sTextQualifier = '"', $oHostObject = null, $bLocalize = true)
public function GetAsCSV($sValue, $sSeparator = ',', $sTextQualifier = '"', $oHostObject = null, $bLocalize = true, $bConvertToPlainText = false)
{
return (string)$sValue;
}
@@ -499,6 +509,7 @@ abstract class AttributeDefinition
'' => 'Plain text (unlocalized) representation',
'html' => 'HTML representation',
'label' => 'Localized representation',
'text' => 'Plain text representation (without any markup)',
);
}
@@ -524,6 +535,10 @@ abstract class AttributeDefinition
case 'label':
return $this->GetEditValue($value);
case 'text':
return $this->GetAsPlainText($value);
break;
default:
throw new Exception("Unknown verb '$sVerb' for attribute ".$this->GetCode().' in class '.get_class($oHostObj));
}
@@ -780,7 +795,7 @@ class AttributeLinkedSet extends AttributeDefinition
return $sRes;
}
public function GetAsCSV($sValue, $sSeparator = ',', $sTextQualifier = '"', $oHostObject = null, $bLocalize = true)
public function GetAsCSV($sValue, $sSeparator = ',', $sTextQualifier = '"', $oHostObject = null, $bLocalize = true, $bConvertToPlainText = false)
{
$sSepItem = MetaModel::GetConfig()->Get('link_set_item_separator');
$sSepAttribute = MetaModel::GetConfig()->Get('link_set_attribute_separator');
@@ -1676,7 +1691,7 @@ class AttributeBoolean extends AttributeInteger
{
return $sValue ? '1' : '0';
}
public function GetAsCSV($sValue, $sSeparator = ',', $sTextQualifier = '"', $oHostObject = null, $bLocalize = true)
public function GetAsCSV($sValue, $sSeparator = ',', $sTextQualifier = '"', $oHostObject = null, $bLocalize = true, $bConvertToPlainText = false)
{
return $sValue ? '1' : '0';
}
@@ -1806,7 +1821,7 @@ class AttributeString extends AttributeDBField
return $value;
}
public function GetAsCSV($sValue, $sSeparator = ',', $sTextQualifier = '"', $oHostObject = null, $bLocalize = true)
public function GetAsCSV($sValue, $sSeparator = ',', $sTextQualifier = '"', $oHostObject = null, $bLocalize = true, $bConvertToPlainText = false)
{
$sFrom = array("\r\n", $sTextQualifier);
$sTo = array("\n", $sTextQualifier.$sTextQualifier);
@@ -1972,7 +1987,7 @@ class AttributeFinalClass extends AttributeString
return $value;
}
public function GetAsCSV($value, $sSeparator = ',', $sTextQualifier = '"', $oHostObject = null, $bLocalize = true)
public function GetAsCSV($value, $sSeparator = ',', $sTextQualifier = '"', $oHostObject = null, $bLocalize = true, $bConvertToPlainText = false)
{
if ($bLocalize && $value != '')
{
@@ -1982,7 +1997,7 @@ class AttributeFinalClass extends AttributeString
{
$sRawValue = $value;
}
return parent::GetAsCSV($sRawValue, $sSeparator, $sTextQualifier, null, false);
return parent::GetAsCSV($sRawValue, $sSeparator, $sTextQualifier, null, false, $bConvertToPlainText);
}
public function GetAsXML($value, $oHostObject = null, $bLocalize = true)
@@ -2162,9 +2177,43 @@ define('WIKI_OBJECT_REGEXP', '/\[\[(.+):(.+)\]\]/U');
*/
class AttributeText extends AttributeString
{
public function GetEditClass() {return "Text";}
public function GetEditClass() {return ($this->GetFormat() == 'text') ? 'Text' : "HTML";}
protected function GetSQLCol($bFullSpec = false) {return "TEXT";}
public function GetSQLColumns($bFullSpec = false)
{
$aColumns = array();
$aColumns[$this->Get('sql')] = $this->GetSQLCol($bFullSpec);
if ($this->GetOptional('format', null) != null )
{
// Add the extra column only if the property 'format' is specified for the attribute
$aColumns[$this->Get('sql').'_format'] = "ENUM('text','html')";
if ($bFullSpec)
{
$aColumns[$this->Get('sql').'_format'].= " DEFAULT 'text'"; // default 'text' is for migrating old records
}
}
return $aColumns;
}
public function GetSQLExpressions($sPrefix = '')
{
if ($sPrefix == '')
{
$sPrefix = $this->Get('sql');
}
$aColumns = array();
// Note: to optimize things, the existence of the attribute is determined by the existence of one column with an empty suffix
$aColumns[''] = $sPrefix;
if ($this->GetOptional('format', null) != null )
{
// Add the extra column only if the property 'format' is specified for the attribute
$aColumns['_format'] = $sPrefix.'_format';
}
return $aColumns;
}
public function GetMaxSize()
{
// Is there a way to know the current limitation for mysql?
@@ -2224,8 +2273,6 @@ class AttributeText extends AttributeString
public function GetAsHTML($sValue, $oHostObject = null, $bLocalize = true)
{
$sValue = parent::GetAsHTML($sValue, $oHostObject, $bLocalize);
$sValue = self::RenderWikiHtml($sValue);
$aStyles = array();
if ($this->GetWidth() != '')
{
@@ -2241,44 +2288,83 @@ class AttributeText extends AttributeString
$aStyles[] = 'overflow:auto';
$sStyle = 'style="'.implode(';', $aStyles).'"';
}
return "<div $sStyle>".str_replace("\n", "<br>\n", $sValue).'</div>';
if ($this->GetFormat() == 'text')
{
$sValue = parent::GetAsHTML($sValue, $oHostObject, $bLocalize);
$sValue = self::RenderWikiHtml($sValue);
return "<div $sStyle>".str_replace("\n", "<br>\n", $sValue).'</div>';
}
else
{
return "<div class=\"HTML\" $sStyle>".utils::FixInlineAttachments($sValue).'</div>';
}
}
public function GetEditValue($sValue, $oHostObj = null)
{
if (preg_match_all(WIKI_OBJECT_REGEXP, $sValue, $aAllMatches, PREG_SET_ORDER))
if ($this->GetFormat() == 'text')
{
foreach($aAllMatches as $iPos => $aMatches)
if (preg_match_all(WIKI_OBJECT_REGEXP, $sValue, $aAllMatches, PREG_SET_ORDER))
{
$sClass = $aMatches[1];
$sName = $aMatches[2];
if (MetaModel::IsValidClass($sClass))
foreach($aAllMatches as $iPos => $aMatches)
{
$sClassLabel = MetaModel::GetName($sClass);
$sValue = str_replace($aMatches[0], "[[$sClassLabel:$sName]]", $sValue);
$sClass = $aMatches[1];
$sName = $aMatches[2];
if (MetaModel::IsValidClass($sClass))
{
$sClassLabel = MetaModel::GetName($sClass);
$sValue = str_replace($aMatches[0], "[[$sClassLabel:$sName]]", $sValue);
}
}
}
}
return $sValue;
}
/**
* For fields containing a potential markup, return the value without this markup
* @return string
*/
public function GetAsPlainText($sValue, $oHostObj = null)
{
if ($this->GetFormat() == 'html')
{
return (string) utils::HtmlToText($this->GetEditValue($sValue, $oHostObj));
}
else
{
return parent::GetAsPlainText($sValue, $oHostObj);
}
}
public function MakeRealValue($proposedValue, $oHostObj)
{
$sValue = $proposedValue;
if (preg_match_all(WIKI_OBJECT_REGEXP, $sValue, $aAllMatches, PREG_SET_ORDER))
switch ($this->GetFormat())
{
foreach($aAllMatches as $iPos => $aMatches)
case 'html':
$sValue = HTMLSanitizer::Sanitize($sValue);
break;
case 'text':
default:
if (preg_match_all(WIKI_OBJECT_REGEXP, $sValue, $aAllMatches, PREG_SET_ORDER))
{
$sClassLabel = $aMatches[1];
$sName = $aMatches[2];
if (!MetaModel::IsValidClass($sClassLabel))
foreach($aAllMatches as $iPos => $aMatches)
{
$sClass = MetaModel::GetClassFromLabel($sClassLabel);
if ($sClass)
$sClassLabel = $aMatches[1];
$sName = $aMatches[2];
if (!MetaModel::IsValidClass($sClassLabel))
{
$sValue = str_replace($aMatches[0], "[[$sClass:$sName]]", $sValue);
$sClass = MetaModel::GetClassFromLabel($sClassLabel);
if ($sClass)
{
$sValue = str_replace($aMatches[0], "[[$sClass:$sName]]", $sValue);
}
}
}
}
@@ -2300,6 +2386,92 @@ class AttributeText extends AttributeString
{
return $this->GetOptional('height', '');
}
/**
* The actual formatting of the field: either text (=plain text) or html (= text with HTML markup)
* @return string
*/
public function GetFormat()
{
return $this->GetOptional('format', 'text');
}
/**
* Read the value from the row returned by the SQL query and transorms it to the appropriate
* internal format (either text or html)
* @see AttributeDBFieldVoid::FromSQLToValue()
*/
public function FromSQLToValue($aCols, $sPrefix = '')
{
$value = $aCols[$sPrefix.''];
if ($this->GetOptional('format', null) != null )
{
// Read from the extra column only if the property 'format' is specified for the attribute
$sFormat = $aCols[$sPrefix.'_format'];
}
else
{
$sFormat = $this->GetFormat();
}
switch($sFormat)
{
case 'text':
if ($this->GetFormat() == 'html')
{
$value = utils::TextToHtml($value);
}
break;
case 'html':
if ($this->GetFormat() == 'text')
{
$value = utils::HtmlToText($value);
}
else
{
$value = utils::FixInlineAttachments((string)$value);
}
break;
default:
// unknown format ??
}
return $value;
}
public function GetSQLValues($value)
{
$aValues = array();
$aValues[$this->Get("sql")] = $this->ScalarToSQL($value);
if ($this->GetOptional('format', null) != null )
{
// Add the extra column only if the property 'format' is specified for the attribute
$aValues[$this->Get("sql").'_format'] = $this->GetFormat();
}
return $aValues;
}
public function GetAsCSV($sValue, $sSeparator = ',', $sTextQualifier = '"', $oHostObject = null, $bLocalize = true, $bConvertToPlainText = false)
{
switch($this->GetFormat())
{
case 'html':
if ($bConvertToPlainText)
{
$sValue = utils::HtmlToText((string)$sValue);
}
$sFrom = array("\r\n", $sTextQualifier);
$sTo = array("\n", $sTextQualifier.$sTextQualifier);
$sEscaped = str_replace($sFrom, $sTo, (string)$sValue);
return $sTextQualifier.$sEscaped.$sTextQualifier;
break;
case 'text':
default:
return parent::GetAsCSV($sValue, $sSeparator, $sTextQualifier, $oHostObject, $bLocalize, $bConvertToPlainText);
}
}
}
/**
@@ -2358,7 +2530,25 @@ class AttributeCaseLog extends AttributeLongText
}
return $sValue->GetModifiedEntry();
}
/**
* For fields containing a potential markup, return the value without this markup
* @return string
*/
public function GetAsPlainText($value, $oHostObj = null)
{
$value = $oObj->Get($sAttCode);
if ($value instanceOf ormCaseLog)
{
return $value->GetAsPlainText();
}
else
{
return (string) $value;
}
}
public function GetDefaultValue() {return new ormCaseLog();}
public function Equals($val1, $val2) {return ($val1->GetText() == $val2->GetText());}
@@ -2402,7 +2592,7 @@ class AttributeCaseLog extends AttributeLongText
{
if (strlen($proposedValue) > 0)
{
$oCaseLog->AddLogEntry(parent::MakeRealValue($proposedValue, $oHostObj));
$oCaseLog->AddLogEntry($proposedValue);
}
}
$ret = $oCaseLog;
@@ -2414,7 +2604,7 @@ class AttributeCaseLog extends AttributeLongText
{
if ($sPrefix == '')
{
$sPrefix = $this->GetCode();
$sPrefix = $this->Get('sql');
}
$aColumns = array();
// Note: to optimize things, the existence of the attribute is determined by the existence of one column with an empty suffix
@@ -2502,11 +2692,11 @@ class AttributeCaseLog extends AttributeLongText
return "<div class=\"caselog\" $sStyle>".$sContent.'</div>'; }
public function GetAsCSV($value, $sSeparator = ',', $sTextQualifier = '"', $oHostObject = null, $bLocalize = true)
public function GetAsCSV($value, $sSeparator = ',', $sTextQualifier = '"', $oHostObject = null, $bLocalize = true, $bConvertToPlainText = false)
{
if ($value instanceOf ormCaseLog)
{
return parent::GetAsCSV($value->GetText(), $sSeparator, $sTextQualifier, $oHostObject, $bLocalize);
return parent::GetAsCSV($value->GetText($bConvertToPlainText), $sSeparator, $sTextQualifier, $oHostObject, $bLocalize, $bConvertToPlainText);
}
else
{
@@ -2615,6 +2805,15 @@ class AttributeCaseLog extends AttributeLongText
}
return $sFingerprint;
}
/**
* The actual formatting of the text: either text (=plain text) or html (= text with HTML markup)
* @return string
*/
public function GetFormat()
{
return $this->GetOptional('format', 'html'); // default format for case logs is now HTML
}
}
/**
@@ -2624,11 +2823,29 @@ class AttributeCaseLog extends AttributeLongText
*/
class AttributeHTML extends AttributeLongText
{
public function GetEditClass() {return "HTML";}
public function GetAsHTML($sValue, $oHostObject = null, $bLocalize = true)
public function GetSQLColumns($bFullSpec = false)
{
return $sValue;
$aColumns = array();
$aColumns[$this->GetCode()] = $this->GetSQLCol();
if ($this->GetOptional('format', null) != null )
{
// Add the extra column only if the property 'format' is specified for the attribute
$aColumns[$this->Get('sql').'_format'] = "ENUM('text','html')";
if ($bFullSpec)
{
$aColumns[$this->Get('sql').'_format'].= " DEFAULT 'html'"; // default 'html' is for migrating old records
}
}
return $aColumns;
}
/**
* The actual formatting of the text: either text (=plain text) or html (= text with HTML markup)
* @return string
*/
public function GetFormat()
{
return $this->GetOptional('format', 'html'); // Defaults to HTML
}
}
@@ -2706,11 +2923,29 @@ class AttributeTemplateText extends AttributeText
*/
class AttributeTemplateHTML extends AttributeText
{
public function GetEditClass() {return "HTML";}
public function GetAsHTML($sValue, $oHostObject = null, $bLocalize = true)
public function GetSQLColumns($bFullSpec = false)
{
return $sValue;
$aColumns = array();
$aColumns[$this->GetCode()] = $this->GetSQLCol();
if ($this->GetOptional('format', null) != null )
{
// Add the extra column only if the property 'format' is specified for the attribute
$aColumns[$this->Get('sql').'_format'] = "ENUM('text','html')";
if ($bFullSpec)
{
$aColumns[$this->Get('sql').'_format'].= " DEFAULT 'html'"; // default 'html' is for migrating old records
}
}
return $aColumns;
}
/**
* The actual formatting of the text: either text (=plain text) or html (= text with HTML markup)
* @return string
*/
public function GetFormat()
{
return $this->GetOptional('format', 'html'); // Defaults to HTML
}
}
@@ -2885,7 +3120,7 @@ class AttributeEnum extends AttributeString
return $sRes;
}
public function GetAsCSV($sValue, $sSeparator = ',', $sTextQualifier = '"', $oHostObject = null, $bLocalize = true)
public function GetAsCSV($sValue, $sSeparator = ',', $sTextQualifier = '"', $oHostObject = null, $bLocalize = true, $bConvertToPlainText = false)
{
if (is_null($sValue))
{
@@ -3257,7 +3492,7 @@ class AttributeDateTime extends AttributeDBField
return Str::pure2xml($value);
}
public function GetAsCSV($sValue, $sSeparator = ',', $sTextQualifier = '"', $oHostObject = null, $bLocalize = true)
public function GetAsCSV($sValue, $sSeparator = ',', $sTextQualifier = '"', $oHostObject = null, $bLocalize = true, $bConvertToPlainText = false)
{
$sFrom = array("\r\n", $sTextQualifier);
$sTo = array("\n", $sTextQualifier.$sTextQualifier);
@@ -3818,7 +4053,7 @@ class AttributeExternalField extends AttributeDefinition
{
if ($sPrefix == '')
{
return array('' => $this->GetCode());
return array('' => $this->GetCode()); // Warning: Use GetCode() since AttributeExternalField does not have any 'sql' property
}
else
{
@@ -4013,10 +4248,10 @@ class AttributeExternalField extends AttributeDefinition
$oExtAttDef = $this->GetExtAttDef();
return $oExtAttDef->GetAsXML($value, null, $bLocalize);
}
public function GetAsCSV($value, $sSeparator = ',', $sTestQualifier = '"', $oHostObject = null, $bLocalize = true)
public function GetAsCSV($value, $sSeparator = ',', $sTestQualifier = '"', $oHostObject = null, $bLocalize = true, $bConvertToPlainText = false)
{
$oExtAttDef = $this->GetExtAttDef();
return $oExtAttDef->GetAsCSV($value, $sSeparator, $sTestQualifier, null, $bLocalize);
return $oExtAttDef->GetAsCSV($value, $sSeparator, $sTestQualifier, null, $bLocalize, $bConvertToPlainText);
}
public function IsPartOfFingerprint() { return false; }
@@ -4257,7 +4492,7 @@ class AttributeBlob extends AttributeDefinition
}
}
public function GetAsCSV($sValue, $sSeparator = ',', $sTextQualifier = '"', $oHostObject = null, $bLocalize = true)
public function GetAsCSV($sValue, $sSeparator = ',', $sTextQualifier = '"', $oHostObject = null, $bLocalize = true, $bConvertToPlainText = false)
{
return ''; // Not exportable in CSV !
}
@@ -4379,7 +4614,7 @@ class AttributeStopWatch extends AttributeDefinition
{
if ($sPrefix == '')
{
$sPrefix = $this->GetCode();
$sPrefix = $this->GetCode(); // Warning: a stopwatch does not have any 'sql' property, so its SQL column is equal to its attribute code !!
}
$aColumns = array();
// Note: to optimize things, the existence of the attribute is determined by the existence of one column with an empty suffix
@@ -4551,7 +4786,7 @@ class AttributeStopWatch extends AttributeDefinition
}
}
public function GetAsCSV($value, $sSeparator = ',', $sTextQualifier = '"', $oHostObject = null, $bLocalize = true)
public function GetAsCSV($value, $sSeparator = ',', $sTextQualifier = '"', $oHostObject = null, $bLocalize = true, $bConvertToPlainText = false)
{
return $value->GetTimeSpent();
}
@@ -4773,7 +5008,7 @@ class AttributeStopWatch extends AttributeDefinition
return $sHtml;
}
public function GetSubItemAsCSV($sItemCode, $value, $sSeparator = ',', $sTextQualifier = '"')
public function GetSubItemAsCSV($sItemCode, $value, $sSeparator = ',', $sTextQualifier = '"', $bConvertToPlainText = false)
{
$sFrom = array("\r\n", $sTextQualifier);
$sTo = array("\n", $sTextQualifier.$sTextQualifier);
@@ -5026,10 +5261,10 @@ class AttributeSubItem extends AttributeDefinition
return $res;
}
public function GetAsCSV($value, $sSeparator = ',', $sTextQualifier = '"', $oHostObject = null, $bLocalize = true)
public function GetAsCSV($value, $sSeparator = ',', $sTextQualifier = '"', $oHostObject = null, $bLocalize = true, $bConvertToPlainText = false)
{
$oParent = $this->GetTargetAttDef();
$res = $oParent->GetSubItemAsCSV($this->Get('item_code'), $value, $sSeparator, $sTextQualifier);
$res = $oParent->GetSubItemAsCSV($this->Get('item_code'), $value, $sSeparator, $sTextQualifier, $bConvertToPlainText);
return $res;
}
@@ -5087,7 +5322,7 @@ class AttributeOneWayPassword extends AttributeDefinition
{
if ($sPrefix == '')
{
$sPrefix = $this->GetCode();
$sPrefix = $this->GetCode(); // Warning: AttributeOneWayPassword does not have any sql property so code = sql !
}
$aColumns = array();
// Note: to optimize things, the existence of the attribute is determined by the existence of one column with an empty suffix
@@ -5196,7 +5431,7 @@ class AttributeOneWayPassword extends AttributeDefinition
}
}
public function GetAsCSV($sValue, $sSeparator = ',', $sTextQualifier = '"', $oHostObject = null, $bLocalize = true)
public function GetAsCSV($sValue, $sSeparator = ',', $sTextQualifier = '"', $oHostObject = null, $bLocalize = true, $bConvertToPlainText = false)
{
return ''; // Not exportable in CSV
}
@@ -5300,7 +5535,7 @@ class AttributeTable extends AttributeDBField
return $sRes;
}
public function GetAsCSV($sValue, $sSeparator = ',', $sTextQualifier = '"', $oHostObject = null, $bLocalize = true)
public function GetAsCSV($sValue, $sSeparator = ',', $sTextQualifier = '"', $oHostObject = null, $bLocalize = true, $bConvertToPlainText = false)
{
// Not implemented
return '';
@@ -5372,7 +5607,7 @@ class AttributePropertySet extends AttributeTable
return $sRes;
}
public function GetAsCSV($value, $sSeparator = ',', $sTextQualifier = '"', $oHostObject = null, $bLocalize = true)
public function GetAsCSV($value, $sSeparator = ',', $sTextQualifier = '"', $oHostObject = null, $bLocalize = true, $bConvertToPlainText = false)
{
if (count($value) == 0)
{
@@ -5451,7 +5686,7 @@ class AttributeComputedFieldVoid extends AttributeDefinition
{
if ($sPrefix == '')
{
$sPrefix = $this->GetCode();
$sPrefix = $this->GetCode(); // Warning AttributeComputedFieldVoid does not have any sql property
}
return array('' => $sPrefix);
}
@@ -5602,7 +5837,7 @@ class AttributeFriendlyName extends AttributeComputedFieldVoid
return Str::pure2html((string)$sValue);
}
public function GetAsCSV($sValue, $sSeparator = ',', $sTextQualifier = '"', $oHostObject = null, $bLocalize = true)
public function GetAsCSV($sValue, $sSeparator = ',', $sTextQualifier = '"', $oHostObject = null, $bLocalize = true, $bConvertToPlainText = false)
{
$sFrom = array("\r\n", $sTextQualifier);
$sTo = array("\n", $sTextQualifier.$sTextQualifier);
@@ -5769,7 +6004,7 @@ class AttributeRedundancySettings extends AttributeDBField
return sprintf($this->GetUserOptionFormat($sCurrentOption), $this->GetMinUpValue($sValue), MetaModel::GetName($sClass));
}
public function GetAsCSV($sValue, $sSeparator = ',', $sTextQualifier = '"', $oHostObject = null, $bLocalize = true)
public function GetAsCSV($sValue, $sSeparator = ',', $sTextQualifier = '"', $oHostObject = null, $bLocalize = true, $bConvertToPlainText = false)
{
$sFrom = array("\r\n", $sTextQualifier);
$sTo = array("\n", $sTextQualifier.$sTextQualifier);

View File

@@ -1,5 +1,5 @@
<?php
// Copyright (C) 2010-2013 Combodo SARL
// Copyright (C) 2010-2016 Combodo SARL
//
// This file is part of iTop.
//
@@ -20,7 +20,7 @@
/**
* Persistent classes (internal) : cmdbChangeOp and derived
*
* @copyright Copyright (C) 2010-2012 Combodo SARL
* @copyright Copyright (C) 2010-2016 Combodo SARL
* @license http://opensource.org/licenses/AGPL-3.0
*/
@@ -529,9 +529,6 @@ class CMDBChangeOpSetAttributeLongText extends CMDBChangeOpSetAttribute
*/
public function GetDescription()
{
// Temporary, until we change the options of GetDescription() -needs a more global revision
$bIsHtml = true;
$sResult = '';
$oTargetObjectClass = $this->Get('objclass');
$oTargetObjectKey = $this->Get('objkey');
@@ -560,6 +557,66 @@ class CMDBChangeOpSetAttributeLongText extends CMDBChangeOpSetAttribute
}
}
/**
* Record the modification of a multiline string (text) containing some HTML markup
*
* @package iTopORM
*/
class CMDBChangeOpSetAttributeHTML extends CMDBChangeOpSetAttributeLongText
{
public static function Init()
{
$aParams = array
(
"category" => "core/cmdb",
"key_type" => "",
"name_attcode" => "change",
"state_attcode" => "",
"reconc_keys" => array(),
"db_table" => "priv_changeop_setatt_html",
"db_key_field" => "id",
"db_finalclass_field" => "",
);
MetaModel::Init_Params($aParams);
MetaModel::Init_InheritAttributes();
// Display lists
MetaModel::Init_SetZListItems('details', array('date', 'userinfo', 'attcode')); // Attributes to be displayed for the complete details
MetaModel::Init_SetZListItems('list', array('date', 'userinfo', 'attcode')); // Attributes to be displayed for a list
}
/**
* Describe (as a text string) the modifications corresponding to this change
*/
public function GetDescription()
{
$sResult = '';
$oTargetObjectClass = $this->Get('objclass');
$oTargetObjectKey = $this->Get('objkey');
$oTargetSearch = new DBObjectSearch($oTargetObjectClass);
$oTargetSearch->AddCondition('id', $oTargetObjectKey, '=');
$oMonoObjectSet = new DBObjectSet($oTargetSearch);
if (UserRights::IsActionAllowedOnAttribute($this->Get('objclass'), $this->Get('attcode'), UR_ACTION_READ, $oMonoObjectSet) == UR_ALLOWED_YES)
{
if (MetaModel::IsValidAttCode($this->Get('objclass'), $this->Get('attcode')))
{
$oAttDef = MetaModel::GetAttributeDef($this->Get('objclass'), $this->Get('attcode'));
$sAttName = $oAttDef->GetLabel();
}
else
{
// The attribute was renamed or removed from the object ?
$sAttName = $this->Get('attcode');
}
$sTextView = '<div class="history_entry history_entry_truncated"><div class="history_html_content">'.$this->Get('prevdata').'</div></div>';
//$sDocView = $oPrevDoc->GetDisplayInline(get_class($this), $this->GetKey(), 'prevdata');
$sResult = Dict::Format('Change:AttName_Changed_PreviousValue_OldValue', $sAttName, $sTextView);
}
return $sResult;
}
}
/**
* Record the modification of a caselog (text)
* since the caselog itself stores the history
@@ -622,27 +679,8 @@ class CMDBChangeOpSetAttributeCaseLog extends CMDBChangeOpSetAttribute
$oObj = $oMonoObjectSet->Fetch();
$oCaseLog = $oObj->Get($this->Get('attcode'));
$iMaxVisibleLength = MetaModel::getConfig()->Get('max_history_case_log_entry_length', 0);
$sTextEntry = $oCaseLog->GetEntryAt($this->Get('lastentry'));
if (($iMaxVisibleLength > 0) && (strlen($sTextEntry) > $iMaxVisibleLength))
{
if (function_exists('mb_strcut'))
{
// Safe with multi-byte strings
$sBefore = $this->ToHtml(mb_strcut($sTextEntry, 0, $iMaxVisibleLength, 'UTF-8'));
$sAfter = $this->ToHtml(mb_strcut($sTextEntry, $iMaxVisibleLength, null, 'UTF-8'));
}
else
{
// Let's hope we have no multi-byte characters around the cuttting point...
$sBefore = $this->ToHtml(substr($sTextEntry, 0, $iMaxVisibleLength));
$sAfter = $this->ToHtml(substr($sTextEntry, $iMaxVisibleLength));
}
$sTextEntry = '<span class="case-log-history-entry">'.$sBefore.'<span class="case-log-history-entry-end">'.$sAfter.'<span class="case-log-history-entry-toggle ui-icon ui-icon-circle-minus"></span></span><span class="case-log-history-entry-more">...<span class="case-log-history-entry-toggle ui-icon ui-icon-circle-plus"></span></span></span>';
}
else
{
$sTextEntry = $this->ToHtml($sTextEntry);
}
$sTextEntry = '<div class="history_entry history_entry_truncated"><div class="history_html_content">'.$oCaseLog->GetEntryAt($this->Get('lastentry')).'</div></div>';
$sResult = Dict::Format('Change:AttName_EntryAdded', $sAttName, $sTextEntry);
}
return $sResult;

View File

@@ -1,5 +1,5 @@
<?php
// Copyright (C) 2010-2015 Combodo SARL
// Copyright (C) 2010-2016 Combodo SARL
//
// This file is part of iTop.
//
@@ -20,7 +20,7 @@
/**
* Class cmdbObject
*
* @copyright Copyright (C) 2010-2015 Combodo SARL
* @copyright Copyright (C) 2010-2016 Combodo SARL
* @license http://opensource.org/licenses/AGPL-3.0
*/
@@ -337,7 +337,14 @@ abstract class CMDBObject extends DBObject
elseif ($oAttDef instanceOf AttributeLongText)
{
// Data blobs
$oMyChangeOp = MetaModel::NewObject("CMDBChangeOpSetAttributeLongText");
if ($oAttDef->GetFormat() == 'html')
{
$oMyChangeOp = MetaModel::NewObject("CMDBChangeOpSetAttributeHTML");
}
else
{
$oMyChangeOp = MetaModel::NewObject("CMDBChangeOpSetAttributeLongText");
}
$oMyChangeOp->Set("objclass", get_class($this));
$oMyChangeOp->Set("objkey", $this->GetKey());
$oMyChangeOp->Set("attcode", $sAttCode);
@@ -352,7 +359,14 @@ abstract class CMDBObject extends DBObject
elseif ($oAttDef instanceOf AttributeText)
{
// Data blobs
$oMyChangeOp = MetaModel::NewObject("CMDBChangeOpSetAttributeText");
if ($oAttDef->GetFormat() == 'html')
{
$oMyChangeOp = MetaModel::NewObject("CMDBChangeOpSetAttributeHTML");
}
else
{
$oMyChangeOp = MetaModel::NewObject("CMDBChangeOpSetAttributeText");
}
$oMyChangeOp->Set("objclass", get_class($this));
$oMyChangeOp->Set("objkey", $this->GetKey());
$oMyChangeOp->Set("attcode", $sAttCode);

View File

@@ -1,5 +1,5 @@
<?php
// Copyright (C) 2010-2013 Combodo SARL
// Copyright (C) 2010-2016 Combodo SARL
//
// This file is part of iTop.
//
@@ -30,7 +30,7 @@ define('ACCESS_READONLY', 0);
/**
* Configuration read/write
*
* @copyright Copyright (C) 2010-2012 Combodo SARL
* @copyright Copyright (C) 2010-2016 Combodo SARL
* @license http://opensource.org/licenses/AGPL-3.0
*/
@@ -875,6 +875,14 @@ class Config
'source_of_value' => '',
'show_in_conf_sample' => false,
),
'html_sanitizer' => array(
'type' => 'string',
'description' => 'The class to use for HTML sanitization: HTMLDOMSanitizer, HTMLPurifierSanitizer or HTMLNullSanitizer',
'default' => 'HTMLDOMSanitizer',
'value' => '',
'source_of_value' => '',
'show_in_conf_sample' => false,
),
);
public function IsProperty($sPropCode)

View File

@@ -1,5 +1,5 @@
<?php
// Copyright (C) 2015 Combodo SARL
// Copyright (C) 2015-2016 Combodo SARL
//
// This file is part of iTop.
//
@@ -19,7 +19,7 @@
/**
* Bulk export: CSV export
*
* @copyright Copyright (C) 2015 Combodo SARL
* @copyright Copyright (C) 2015-2016 Combodo SARL
* @license http://opensource.org/licenses/AGPL-3.0
*/
@@ -33,6 +33,7 @@ class CSVBulkExport extends TabularBulkExport
$oP->p(" *\tcharset: (optional) character set for encoding the result (default is 'UTF-8').");
$oP->p(" *\ttext-qualifier: (optional) character to be used around text strings (default is '\"').");
$oP->p(" *\tno_localize: set to 1 to retrieve non-localized values (for instance for ENUM values). Default is 0 (= localized values)");
$oP->p(" *\tformatted_text: set to 1 to export case logs and formatted text fields with their HTML markup. Default is 0 (= plain text)");
}
public function ReadParameters()
@@ -55,6 +56,7 @@ class CSVBulkExport extends TabularBulkExport
}
$this->aStatusInfo['charset'] = strtoupper(utils::ReadParam('charset', 'UTF-8', true, 'raw_data'));
$this->aStatusInfo['formatted_text'] = (bool)utils::ReadParam('formatted_text', 0, true);
}
@@ -79,7 +81,7 @@ class CSVBulkExport extends TabularBulkExport
public function EnumFormParts()
{
return array_merge(parent::EnumFormParts(), array('csv_options' => array('separator', 'charset', 'text-qualifier', 'no_localize') ,'interactive_fields_csv' => array('interactive_fields_csv')));
return array_merge(parent::EnumFormParts(), array('csv_options' => array('separator', 'charset', 'text-qualifier', 'no_localize', 'formatted_text') ,'interactive_fields_csv' => array('interactive_fields_csv')));
}
public function DisplayFormPart(WebPage $oP, $sPartId)
@@ -157,6 +159,10 @@ class CSVBulkExport extends TabularBulkExport
}
$oP->add('</select>');
$sChecked = (utils::ReadParam('formatted_text', 0) == 1) ? ' checked ' : '';
$oP->add('<h3>'.Dict::S('Core:BulkExport:TextFormat').'</h3>');
$oP->add('<input type="checkbox" id="csv_formatted_text" name="formatted_text" value="1"'.$sChecked.'><label for="csv_formatted_text"> '.Dict::S('Core:BulkExport:OptionFormattedText').'</label>');
$oP->add('</td></tr></table>');
$oP->add('</fieldset>');
@@ -182,7 +188,7 @@ class CSVBulkExport extends TabularBulkExport
break;
default:
$sRet = trim($oObj->GetAsCSV($sAttCode), '"');
$sRet = trim($oObj->GetAsCSV($sAttCode), '"');
}
return $sRet;
}
@@ -251,7 +257,7 @@ class CSVBulkExport extends TabularBulkExport
break;
default:
$sField = $oObj->GetAsCSV($sAttCode, $this->aStatusInfo['separator'], $this->aStatusInfo['text_qualifier'], $this->bLocalizeOutput);
$sField = $oObj->GetAsCSV($sAttCode, $this->aStatusInfo['separator'], $this->aStatusInfo['text_qualifier'], $this->bLocalizeOutput, !$this->aStatusInfo['formatted_text']);
}
}
if ($this->aStatusInfo['charset'] != 'UTF-8')

View File

@@ -754,10 +754,10 @@ abstract class DBObject implements iDisplay
return $oAtt->GetAsXML($this->Get($sAttCode), $this, $bLocalize);
}
public function GetAsCSV($sAttCode, $sSeparator = ',', $sTextQualifier = '"', $bLocalize = true)
public function GetAsCSV($sAttCode, $sSeparator = ',', $sTextQualifier = '"', $bLocalize = true, $bConvertToPlainText = false)
{
$oAtt = MetaModel::GetAttributeDef(get_class($this), $sAttCode);
return $oAtt->GetAsCSV($this->Get($sAttCode), $sSeparator, $sTextQualifier, $this, $bLocalize);
return $oAtt->GetAsCSV($this->Get($sAttCode), $sSeparator, $sTextQualifier, $this, $bLocalize, $bConvertToPlainText);
}
public function GetOriginalAsHTML($sAttCode, $bLocalize = true)
@@ -772,10 +772,10 @@ abstract class DBObject implements iDisplay
return $oAtt->GetAsXML($this->GetOriginal($sAttCode), $this, $bLocalize);
}
public function GetOriginalAsCSV($sAttCode, $sSeparator = ',', $sTextQualifier = '"', $bLocalize = true)
public function GetOriginalAsCSV($sAttCode, $sSeparator = ',', $sTextQualifier = '"', $bLocalize = true, $bConvertToPlainText = false)
{
$oAtt = MetaModel::GetAttributeDef(get_class($this), $sAttCode);
return $oAtt->GetAsCSV($this->GetOriginal($sAttCode), $sSeparator, $sTextQualifier, $this, $bLocalize);
return $oAtt->GetAsCSV($this->GetOriginal($sAttCode), $sSeparator, $sTextQualifier, $this, $bLocalize, $bConvertToPlainText);
}
public static function MakeHyperLink($sObjClass, $sObjKey, $sLabel = '', $sUrlMakerClass = null, $bWithNavigationContext = true)

View File

@@ -1,5 +1,5 @@
<?php
// Copyright (C) 2010-2012 Combodo SARL
// Copyright (C) 2010-2016 Combodo SARL
//
// This file is part of iTop.
//
@@ -20,7 +20,7 @@
/**
* Send an email (abstraction for synchronous/asynchronous modes)
*
* @copyright Copyright (C) 2010-2012 Combodo SARL
* @copyright Copyright (C) 2010-2016 Combodo SARL
* @license http://opensource.org/licenses/AGPL-3.0
*/
@@ -157,6 +157,9 @@ class EMail
protected function SendSynchronous(&$aIssues, $oLog = null)
{
// If the body of the message is in HTML, embed all images based on attachments
$this->EmbedInlineImages();
$this->LoadConfig();
$sTransport = self::$m_oConfig->Get('email_transport');
@@ -208,9 +211,46 @@ class EMail
return EMAIL_SEND_OK;
}
}
/**
* Reprocess the body of the message (if it is an HTML message)
* to replace the URL of images based on attachments by a link
* to an embedded image (i.e. cid:....)
*/
protected function EmbedInlineImages()
{
if ($this->m_aData['body']['mimeType'] == 'text/html')
{
$oDOMDoc = new DOMDocument();
$oDOMDoc->preserveWhitespace = true;
@$oDOMDoc->loadHTML('<?xml encoding="UTF-8"?>'.$this->m_aData['body']['body']); // For loading HTML chunks where the character set is not specified
$oXPath = new DOMXPath($oDOMDoc);
$sXPath = "//img[@data-att-id]";
$oImagesList = $oXPath->query($sXPath);
if ($oImagesList->length != 0)
{
foreach($oImagesList as $oImg)
{
$iAttId = $oImg->getAttribute('data-att-id');
$oAttachment = MetaModel::GetObject('Attachment', $iAttId, false, true /* Allow All Data */);
if ($oAttachment)
{
$oDoc = $oAttachment->Get('contents');
$oSwiftImage = new Swift_Image($oDoc->GetData(), $oDoc->GetFileName(), $oDoc->GetMimeType());
$sCid = $this->m_oMessage->embed($oSwiftImage);
$oImg->setAttribute('src', $sCid);
}
}
}
$sHtmlBody = $oDOMDoc->saveHTML();
$this->m_oMessage->setBody($sHtmlBody, 'text/html', 'UTF-8');
}
}
public function Send(&$aIssues, $bForceSynchronous = false, $oLog = null)
{
{
if ($bForceSynchronous)
{
return $this->SendSynchronous($aIssues, $oLog);

View File

@@ -1,5 +1,5 @@
<?php
// Copyright (C) 2015 Combodo SARL
// Copyright (C) 2015-2016 Combodo SARL
//
// This file is part of iTop.
//
@@ -19,7 +19,7 @@
/**
* Bulk export: Excel (xlsx) export
*
* @copyright Copyright (C) 2015 Combodo SARL
* @copyright Copyright (C) 2015-2016 Combodo SARL
* @license http://opensource.org/licenses/AGPL-3.0
*/
@@ -46,12 +46,18 @@ class ExcelBulkExport extends TabularBulkExport
{
$oP->p(" * xlsx format options:");
$oP->p(" *\tfields: the comma separated list of field codes to export (e.g: name,org_id,service_name...).");
$oP->p(" *\tformatted_text: set to 1 to export case logs and formatted text fields with their HTML markup. Default is 0 (= plain text)");
}
public function ReadParameters()
{
parent::ReadParameters();
$this->aStatusInfo['formatted_text'] = (bool)utils::ReadParam('formatted_text', 0, true);
}
public function EnumFormParts()
{
return array_merge(parent::EnumFormParts(), array('interactive_fields_xlsx' => array('interactive_fields_xlsx')));
return array_merge(parent::EnumFormParts(), array('xlsx_options' => array('formatted_text') ,'interactive_fields_xlsx' => array('interactive_fields_xlsx')));
}
public function DisplayFormPart(WebPage $oP, $sPartId)
@@ -62,7 +68,20 @@ class ExcelBulkExport extends TabularBulkExport
$this->GetInteractiveFieldsWidget($oP, 'interactive_fields_xlsx');
break;
default:
case 'xlsx_options':
$oP->add('<fieldset><legend>'.Dict::S('Core:BulkExport:XLSXOptions').'</legend>');
$oP->add('<table class="export_parameters"><tr><td style="vertical-align:top">');
$sChecked = (utils::ReadParam('formatted_text', 0) == 1) ? ' checked ' : '';
$oP->add('<h3>'.Dict::S('Core:BulkExport:TextFormat').'</h3>');
$oP->add('<input type="checkbox" id="xlsx_formatted_text" name="formatted_text" value="1"'.$sChecked.'><label for="xlsx_formatted_text"> '.Dict::S('Core:BulkExport:OptionFormattedText').'</label>');
$oP->add('</td></tr></table>');
$oP->add('</fieldset>');
break;
default:
return parent:: DisplayFormPart($oP, $sPartId);
}
}
@@ -103,8 +122,16 @@ class ExcelBulkExport extends TabularBulkExport
$value = $oObj->Get($sAttCode);
if ($value instanceOf ormCaseLog)
{
if (array_key_exists('formatted_text', $this->aStatusInfo) && $this->aStatusInfo['formatted_text'])
{
$sText = $value->GetText();
}
else
{
$sText = $value->GetAsPlainText();
}
// Extract the case log as text and remove the "===" which make Excel think that the cell contains a formula the next time you edit it!
$sRet = trim(preg_replace('/========== ([^=]+) ============/', '********** $1 ************', $value->GetText()));
$sRet = trim(preg_replace('/========== ([^=]+) ============/', '********** $1 ************', $sText));
}
else if ($value instanceOf DBObjectSet)
{
@@ -114,7 +141,14 @@ class ExcelBulkExport extends TabularBulkExport
else
{
$oAttDef = MetaModel::GetAttributeDef(get_class($oObj), $sAttCode);
$sRet = $oAttDef->GetEditValue($value, $oObj);
if (array_key_exists('formatted_text', $this->aStatusInfo) && $this->aStatusInfo['formatted_text'])
{
$sRet = $oAttDef->GetEditValue($value, $oObj);
}
else
{
$sRet = $oAttDef->GetAsPlainText($value, $oObj);
}
}
}
return $sRet;

View File

@@ -0,0 +1,338 @@
<?php
/**
* Base class for all possible implementations of HTML Sanitization
*/
abstract class HTMLSanitizer
{
public function __construct()
{
// Do nothing..
}
/**
* Sanitizes the given HTML document
* @param string $sHTML
* @return string
*/
abstract public function DoSanitize($sHTML);
/**
* Sanitize an HTML string with the configured sanitizer, falling back to HTMLDOMSanitizer in case of Exception or invalid configuration
* @param string $sHTML
* @return string
*/
public static function Sanitize($sHTML)
{
$sSanitizerClass = MetaModel::GetConfig()->Get('html_sanitizer');
if(!class_exists($sSanitizerClass))
{
IssueLog::Warning('The configured "html_sanitizer" class "'.$sSanitizerClass.'" is not a valid class. Will use HTMLDOMSanitizer as the default sanitizer.');
$sSanitizerClass = 'HTMLDOMSanitizer';
}
else if(!is_subclass_of($sSanitizerClass, 'HTMLSanitizer'))
{
IssueLog::Warning('The configured "html_sanitizer" class "'.$sSanitizerClass.'" is not a subclass of HTMLSanitizer. Will use HTMLDOMSanitizer as the default sanitizer.');
$sSanitizerClass = 'HTMLDOMSanitizer';
}
try
{
$oSanitizer = new $sSanitizerClass();
$sCleanHTML = $oSanitizer->DoSanitize($sHTML);
}
catch(Exception $e)
{
if($sSanitizerClass != 'HTMLDOMSanitizer')
{
IssueLog::Warning('Failed to sanitize an HTML string with "'.$sSanitizerClass.'". The following exception occured: '.$e->getMessage());
IssueLog::Warning('Will try to sanitize with HTMLDOMSanitizer.');
// try again with the HTMLDOMSanitizer
$oSanitizer = new HTMLDOMSanitizer();
$sCleanHTML = $oSanitizer->DoSanitize($sHTML);
}
else
{
IssueLog::Error('Failed to sanitize an HTML string with "HTMLDOMSanitizer". The following exception occured: '.$e->getMessage());
IssueLog::Error('The HTML will NOT be sanitized.');
$sCleanHTML = $sHTML;
}
}
return $sCleanHTML;
}
}
/**
* Dummy HTMLSanitizer which does nothing at all!
* Can be used if HTML Sanitization is not important
* (for example when importing "safe" data during an on-boarding)
* and performance is at stake
*
*/
class HTMLNullSanitizer extends HTMLSanitizer
{
/**
* (non-PHPdoc)
* @see HTMLSanitizer::Sanitize()
*/
public function DoSanitize($sHTML)
{
return $sHTML;
}
}
/**
* A standard-compliant HTMLSanitizer based on the HTMLPurifier library by Edward Z. Yang
* Complete but quite slow
* http://htmlpurifier.org
*/
/*
class HTMLPurifierSanitizer extends HTMLSanitizer
{
protected static $oPurifier = null;
public function __construct()
{
if (self::$oPurifier == null)
{
$sLibPath = APPROOT.'lib/htmlpurifier/HTMLPurifier.auto.php';
if (!file_exists($sLibPath))
{
throw new Exception("Missing library '$sLibPath', cannot use HTMLPurifierSanitizer.");
}
require_once($sLibPath);
$oPurifierConfig = HTMLPurifier_Config::createDefault();
$oPurifierConfig->set('Core.Encoding', 'UTF-8'); // defaults to 'UTF-8'
$oPurifierConfig->set('HTML.Doctype', 'XHTML 1.0 Strict'); // defaults to 'XHTML 1.0 Transitional'
$oPurifierConfig->set('URI.AllowedSchemes', array (
'http' => true,
'https' => true,
'data' => true, // This one is not present by default
));
$sPurifierCache = APPROOT.'data/HTMLPurifier';
if (!is_dir($sPurifierCache))
{
mkdir($sPurifierCache);
}
if (!is_dir($sPurifierCache))
{
throw new Exception("Could not create the cache directory '$sPurifierCache'");
}
$oPurifierConfig->set('Cache.SerializerPath', $sPurifierCache); // no trailing slash
self::$oPurifier = new HTMLPurifier($oPurifierConfig);
}
}
public function DoSanitize($sHTML)
{
$sCleanHtml = self::$oPurifier->purify($sHTML);
return $sCleanHtml;
}
}
*/
class HTMLDOMSanitizer extends HTMLSanitizer
{
protected $oDoc;
protected static $aTagsWhiteList = array(
'html' => array(),
'body' => array(),
'a' => array('href', 'name', 'style'),
'p' => array('style'),
'br' => array(),
'span' => array('style'),
'div' => array('style'),
'b' => array(),
'i' => array(),
'em' => array(),
'strong' => array(),
'img' => array('src','style'),
'ul' => array('style'),
'ol' => array('style'),
'li' => array('style'),
'h1' => array('style'),
'h2' => array('style'),
'h3' => array('style'),
'h4' => array('style'),
'nav' => array('style'),
'section' => array('style'),
'code' => array('style'),
'table' => array('style', 'width'),
'thead' => array('style'),
'tbody' => array('style'),
'tr' => array('style'),
'td' => array('style', 'colspan'),
'th' => array('style'),
'fieldset' => array('style'),
'legend' => array('style'),
'font' => array('face', 'color', 'style', 'size'),
'big' => array(),
'small' => array(),
'tt' => array(),
'code' => array(),
'kbd' => array(),
'samp' => array(),
'var' => array(),
'del' => array(),
's' => array(), // strikethrough
'ins' => array(),
'cite' => array(),
'q' => array(),
'hr' => array('style'),
'pre' => array(),
'center' => array(),
);
protected static $aAttrsWhiteList = array(
'href' => '/^(http:|https:)/i',
'src' => '/^(http:|https:|data:)/i',
);
protected static $aStylesWhiteList = array(
'background-color', 'color', 'font', 'font-style', 'font-size', 'font-family', 'padding', 'margin', 'border', 'cellpadding', 'cellspacing', 'bordercolor', 'border-collapse', 'width', 'height',
);
public function DoSanitize($sHTML)
{
$this->oDoc = new DOMDocument();
$this->oDoc->preserveWhitespace = true;
@$this->oDoc->loadHTML('<?xml encoding="UTF-8"?>'.$sHTML); // For loading HTML chunks where the character set is not specified
$this->CleanNode($this->oDoc);
$oXPath = new DOMXPath($this->oDoc);
$sXPath = "//body";
$oNodesList = $oXPath->query($sXPath);
if ($oNodesList->length == 0)
{
// No body, save the whole document
$sCleanHtml = $this->oDoc->saveHTML();
}
else
{
// Export only the content of the body tag
$sCleanHtml = $this->oDoc->saveHTML($oNodesList->item(0));
// remove the body tag itself
$sCleanHtml = str_replace( array('<body>', '</body>'), '', $sCleanHtml);
}
return $sCleanHtml;
}
protected function CleanNode(DOMNode $oElement)
{
$aAttrToRemove = array();
// Gather the attributes to remove
if ($oElement->hasAttributes())
{
foreach($oElement->attributes as $oAttr)
{
$sAttr = strtolower($oAttr->name);
if (!in_array($sAttr, self::$aTagsWhiteList[strtolower($oElement->tagName)]))
{
// Forbidden (or unknown) attribute
$aAttrToRemove[] = $oAttr->name;
}
else if (!$this->IsValidAttributeContent($sAttr, $oAttr->value))
{
// Invalid content
$aAttrToRemove[] = $oAttr->name;
}
else if ($sAttr == 'style')
{
// Special processing for style tags
$sCleanStyle = $this->CleanStyle($oAttr->value);
if ($sCleanStyle == '')
{
// Invalid content
$aAttrToRemove[] = $oAttr->name;
}
else
{
$oElement->setAttribute($oAttr->name, $sCleanStyle);
}
}
}
// Now remove them
foreach($aAttrToRemove as $sName)
{
$oElement->removeAttribute($sName);
}
}
if ($oElement->hasChildNodes())
{
$aChildElementsToRemove = array();
// Gather the child noes to remove
foreach($oElement->childNodes as $oNode)
{
if (($oNode instanceof DOMElement) && (!array_key_exists(strtolower($oNode->tagName), self::$aTagsWhiteList)))
{
$aChildElementsToRemove[] = $oNode;
}
else if ($oNode instanceof DOMComment)
{
$aChildElementsToRemove[] = $oNode;
}
else
{
// Recurse
$this->CleanNode($oNode);
if (($oNode instanceof DOMElement) && (strtolower($oNode->tagName) == 'img'))
{
$this->ProcessImage($oNode);
}
}
}
// Now remove them
foreach($aChildElementsToRemove as $oDomElement)
{
$oElement->removeChild($oDomElement);
}
}
}
/**
* Add an extra attribute data-att-id for images which are based on an actual attachment
* so that we can later reconstruct the full "src" URL when needed
* @param DOMNode $oElement
*/
protected function ProcessImage(DOMNode $oElement)
{
$sSrc = $oElement->getAttribute('src');
$sDownloadUrl = str_replace(array('.', '?'), array('\.', '\?'), ATTACHMENT_DOWNLOAD_URL); // Escape . and ?
$sUrlPattern = '|'.$sDownloadUrl.'([0-9]+)|';
if (preg_match($sUrlPattern, $sSrc, $aMatches))
{
$oElement->setAttribute('data-att-id', $aMatches[1]);
}
}
protected function CleanStyle($sStyle)
{
$aAllowedStyles = array();
$aItems = explode(';', $sStyle);
{
foreach($aItems as $sItem)
{
$aElements = explode(':', trim($sItem));
if (in_array(trim(strtolower($aElements[0])), static::$aStylesWhiteList))
{
$aAllowedStyles[] = trim($sItem);
}
}
}
return implode(';', $aAllowedStyles);
}
protected function IsValidAttributeContent($sAttributeName, $sValue)
{
if (array_key_exists($sAttributeName, self::$aAttrsWhiteList))
{
return preg_match(self::$aAttrsWhiteList[$sAttributeName], $sValue);
}
return true;
}
}

View File

@@ -3538,7 +3538,6 @@ abstract class MetaModel
{
// Skip this attribute if not originaly defined in this class
if (self::$m_aAttribOrigins[$sClass][$sAttCode] != $sClass) continue;
foreach($oAttDef->GetSQLColumns(true) as $sField => $sDBFieldSpec)
{
// Keep track of columns used by iTop
@@ -4893,9 +4892,9 @@ abstract class MetaModel
{
// Expand the parameters for the object
$sName = substr($sSearch, 0, $iPos);
if (preg_match_all('/\\$'.$sName.'->([^\\$]+)\\$/', $sInput, $aMatches))
if (preg_match_all('/\\$'.$sName.'-(>|&gt;)([^\\$]+)\\$/', $sInput, $aMatches)) // Support both syntaxes: $this->xxx$ or $this-&gt;xxx$ for HTML compatibility
{
foreach($aMatches[1] as $sPlaceholderAttCode)
foreach($aMatches[2] as $idx => $sPlaceholderAttCode)
{
try
{
@@ -4903,7 +4902,7 @@ abstract class MetaModel
if ($sReplacement !== null)
{
$aReplacements[] = $sReplacement;
$aSearches[] = '$'.$sName.'->'.$sPlaceholderAttCode.'$';
$aSearches[] = '$'.$sName.'-'.$aMatches[1][$idx].$sPlaceholderAttCode.'$';
}
}
catch(Exception $e)

View File

@@ -1,5 +1,5 @@
<?php
// Copyright (C) 2010-2015 Combodo SARL
// Copyright (C) 2010-2016 Combodo SARL
//
// This file is part of iTop.
//
@@ -23,7 +23,7 @@ define('CASELOG_SEPARATOR', "\n".'========== %1$s : %2$s (%3$d) ============'."\
/**
* Class to store a "case log" in a structured way, keeping track of its successive entries
*
* @copyright Copyright (C) 2010-2015 Combodo SARL
* @copyright Copyright (C) 2010-2016 Combodo SARL
* @license http://opensource.org/licenses/AGPL-3.0
*/
class ormCaseLog {
@@ -43,9 +43,17 @@ class ormCaseLog {
$this->m_bModified = false;
}
public function GetText()
public function GetText($bConvertToPlainText = false)
{
return $this->m_sLog;
if ($bConvertToPlainText)
{
// Rebuild the log, but filtering any HTML markup for the all 'html' entries in the log
return $this->GetAsPlainText();
}
else
{
return $this->m_sLog;
}
}
public static function FromJSON($oJson)
@@ -97,11 +105,24 @@ class ormCaseLog {
$sDate = '';
}
}
$sFormat = array_key_exists('format', $this->m_aIndex[$index]) ? $this->m_aIndex[$index]['format'] : 'text';
switch($sFormat)
{
case 'text':
$sHtmlEntry = utils::TextToHtml($sTextEntry);
break;
case 'html':
$sHtmlEntry = $sTextEntry;
$sTextEntry = utils::HtmlToText($sHtmlEntry);
break;
}
$aEntries[] = array(
'date' => $sDate,
'user_login' => $this->m_aIndex[$index]['user_name'],
'user_id' => $this->m_aIndex[$index]['user_id'],
'message' => $sTextEntry
'message' => $sTextEntry,
'message_html' => $sHtmlEntry,
);
}
@@ -113,7 +134,8 @@ class ormCaseLog {
$aEntries[] = array(
'date' => '',
'user_login' => '',
'message' => $sTextEntry
'message' => $sTextEntry,
'message_html' => utils::TextToHtml($sTextEntry),
);
}
@@ -122,6 +144,22 @@ class ormCaseLog {
return $aRet;
}
/**
* Returns a "plain text" version of the log (equivalent to $this->m_sLog) where all the HTML markup from the 'html' entries have been removed
* @return string
*/
public function GetAsPlainText()
{
$sPlainText = '';
$aJSON = $this->GetForJSON();
foreach($aJSON['entries'] as $aData)
{
$sSeparator = sprintf(CASELOG_SEPARATOR, $aData['date'], $aData['user_login'], $aData['user_id']);
$sPlainText .= $sSeparator.$aData['message'];
}
return $sPlainText;
}
public function GetIndex()
{
return $this->m_aIndex;
@@ -152,7 +190,16 @@ class ormCaseLog {
{
$iPos += $aIndex[$index]['separator_length'];
$sTextEntry = substr($this->m_sLog, $iPos, $aIndex[$index]['text_length']);
$sTextEntry = str_replace(array("\r\n", "\n", "\r"), "<br/>", htmlentities($sTextEntry, ENT_QUOTES, 'UTF-8'));
$sCSSClass = 'caselog_entry_html';
if (!array_key_exists('format', $aIndex[$index]) || ($aIndex[$index]['format'] == 'text'))
{
$sCSSClass = 'caselog_entry';
$sTextEntry = str_replace(array("\r\n", "\n", "\r"), "<br/>", htmlentities($sTextEntry, ENT_QUOTES, 'UTF-8'));
}
else
{
$sTextEntry = utils::FixInlineAttachments($sTextEntry);
}
$iPos += $aIndex[$index]['text_length'];
$sEntry = '<div class="caselog_header" style="'.$sStyleCaseLogHeader.'">';
@@ -180,7 +227,7 @@ class ormCaseLog {
}
$sEntry .= sprintf(Dict::S('UI:CaseLog:Header_Date_UserName'), '<span class="caselog_header_date">'.$sDate.'</span>', '<span class="caselog_header_user">'.$aIndex[$index]['user_name'].'</span>');
$sEntry .= '</div>';
$sEntry .= '<div class="caselog_entry" style="'.$sStyleCaseLogEntry.'">';
$sEntry .= '<div class="'.$sCSSClass.'" style="'.$sStyleCaseLogEntry.'">';
$sEntry .= $sTextEntry;
$sEntry .= '</div>';
$sHtml = $sHtml.$sEntry;
@@ -227,7 +274,20 @@ class ormCaseLog {
{
$iPos += $aIndex[$index]['separator_length'];
$sTextEntry = substr($this->m_sLog, $iPos, $aIndex[$index]['text_length']);
$sTextEntry = str_replace(array("\r\n", "\n", "\r"), "<br/>", htmlentities($sTextEntry, ENT_QUOTES, 'UTF-8'));
$sCSSClass = 'case_log_simple_html_entry_html';
if (!array_key_exists('format', $aIndex[$index]) || ($aIndex[$index]['format'] == 'text'))
{
$sCSSClass = 'case_log_simple_html_entry';
$sTextEntry = str_replace(array("\r\n", "\n", "\r"), "<br/>", htmlentities($sTextEntry, ENT_QUOTES, 'UTF-8'));
if (!is_null($aTransfoHandler))
{
$sTextEntry = call_user_func($aTransfoHandler, $sTextEntry);
}
}
else
{
$sTextEntry = utils::FixInlineAttachments($sTextEntry);
}
$iPos += $aIndex[$index]['text_length'];
$sEntry = '<li>';
@@ -254,7 +314,7 @@ class ormCaseLog {
}
}
$sEntry .= sprintf(Dict::S('UI:CaseLog:Header_Date_UserName'), '<span class="caselog_header_date">'.$sDate.'</span>', '<span class="caselog_header_user">'.$aIndex[$index]['user_name'].'</span>');
$sEntry .= '<div class="case_log_simple_html_entry" style="'.$sStyleCaseLogEntry.'">';
$sEntry .= '<div class="'.$sCSSClass.'" style="'.$sStyleCaseLogEntry.'">';
$sEntry .= $sTextEntry;
$sEntry .= '</div>';
$sEntry .= '</li>';
@@ -317,10 +377,19 @@ class ormCaseLog {
}
$iPos += $aIndex[$index]['separator_length'];
$sTextEntry = substr($this->m_sLog, $iPos, $aIndex[$index]['text_length']);
$sTextEntry = str_replace(array("\r\n", "\n", "\r"), "<br/>", htmlentities($sTextEntry, ENT_QUOTES, 'UTF-8'));
if (!is_null($aTransfoHandler))
$sCSSClass= 'caselog_entry_html';
if (!array_key_exists('format', $aIndex[$index]) || ($aIndex[$index]['format'] == 'text'))
{
$sTextEntry = call_user_func($aTransfoHandler, $sTextEntry);
$sCSSClass= 'caselog_entry';
$sTextEntry = str_replace(array("\r\n", "\n", "\r"), "<br/>", htmlentities($sTextEntry, ENT_QUOTES, 'UTF-8'));
if (!is_null($aTransfoHandler))
{
$sTextEntry = call_user_func($aTransfoHandler, $sTextEntry);
}
}
else
{
$sTextEntry = utils::FixInlineAttachments($sTextEntry);
}
$iPos += $aIndex[$index]['text_length'];
@@ -349,7 +418,7 @@ class ormCaseLog {
}
$sEntry .= sprintf(Dict::S('UI:CaseLog:Header_Date_UserName'), $sDate, $aIndex[$index]['user_name']);
$sEntry .= '</div>';
$sEntry .= '<div class="caselog_entry"'.$sDisplay.'>';
$sEntry .= '<div class="'.$sCSSClass.'"'.$sDisplay.'>';
$sEntry .= $sTextEntry;
$sEntry .= '</div>';
$sHtml = $sHtml.$sEntry;
@@ -358,6 +427,7 @@ class ormCaseLog {
// Process the case of an eventual remainder (quick migration of AttributeText fields)
if ($iPos < (strlen($this->m_sLog) - 1))
{
// In this case the format is always "text"
$sTextEntry = substr($this->m_sLog, $iPos);
$sTextEntry = str_replace(array("\r\n", "\n", "\r"), "<br/>", htmlentities($sTextEntry, ENT_QUOTES, 'UTF-8'));
if (!is_null($aTransfoHandler))
@@ -402,6 +472,7 @@ class ormCaseLog {
*/
public function AddLogEntry($sText, $sOnBehalfOf = '')
{
$sText = HTMLSanitizer::Sanitize($sText);
$bMergeEntries = false;
$sDate = date(Dict::S('UI:CaseLog:DateFormat'));
if ($sOnBehalfOf == '')
@@ -439,7 +510,8 @@ class ormCaseLog {
'user_id' => $iUserId,
'date' => time(),
'text_length' => $aLatestEntry['text_length'] + $iTextlength,
'separator_length' => $iSepLength,
'separator_length' => $iSepLength,
'format' => 'html',
);
}
@@ -455,6 +527,7 @@ class ormCaseLog {
'date' => time(),
'text_length' => $iTextlength,
'separator_length' => $iSepLength,
'format' => 'html',
);
}
$this->m_bModified = true;
@@ -463,7 +536,7 @@ class ormCaseLog {
public function AddLogEntryFromJSON($oJson, $bCheckUserId = true)
{
$sText = isset($oJson->message) ? $oJson->message : '';
$sText = HTMLSanitizer::Sanitize(isset($oJson->message) ? $oJson->message : '');
if (isset($oJson->user_id))
{
@@ -505,6 +578,16 @@ class ormCaseLog {
{
$iDate = time();
}
if (isset($oJson->format))
{
$sFormat = $oJson->format;
}
else
{
// TODO: what is the default format ? text ?
$sFormat = 'html';
}
$sDate = date(Dict::S('UI:CaseLog:DateFormat'), $iDate);
$sSeparator = sprintf(CASELOG_SEPARATOR, $sDate, $sOnBehalfOf, $iUserId);
@@ -516,7 +599,8 @@ class ormCaseLog {
'user_id' => $iUserId,
'date' => $iDate,
'text_length' => $iTextlength,
'separator_length' => $iSepLength,
'separator_length' => $iSepLength,
'format' => $sFormat,
);
$this->m_bModified = true;

View File

@@ -280,16 +280,33 @@ legend.transparent {
}
.ui-widget-content td a.cke_toolbox_collapser {
.ui-widget-content td a.cke_button, .ui-widget-content td a.cke_combo_button, .ui-widget-content td a.cke_toolbox_collapser, cke_dialog a {
padding-left: 0;
background-image: none;
}
p a:hover, td a:hover {
.ui-widget-content td a:hover, p a:hover, td a:hover {
text-decoration: underline;
color: #e87c1e;
padding-left: 14px;
background: url(../images/mini-arrow-orange.gif) no-repeat left;
}
.cke_reset_all *:hover {
text-decoration: none;
color: black;
}
table.cke_dialog_contents a.cke_dialog_ui_button_ok {
color: black;
border-color: #e87c1e;
background: #e87c1e;
}
.cke_notifications_area {
display: none;
}
@@ -2154,3 +2171,33 @@ span.refresh-button {
}
.history_entry {
position: relative !important;
max-width: 100%;
}
.history_entry_truncated {
max-height: 7em;
overflow: hidden;
}
.history_truncated_toggler {
position: absolute !important;
bottom: 0;
right: 0;
display: block;
cursor: pointer;
width: 16px;
height: 16px;
background-image: url(ui-lightness/images/ui-icons_222222_256x240.png);
background-position: -16px -192px;
}
.history_entry_truncated .history_truncated_toggler {
background-position: 0 -192px;
}

View File

@@ -224,16 +224,27 @@ legend.transparent {
padding-left:14px;
background: url(../images/mini-arrow-orange.gif) no-repeat left;
}
.ui-widget-content td a.cke_toolbox_collapser {
.ui-widget-content td a.cke_button, .ui-widget-content td a.cke_toolbox_collapser, .ui-widget-content td a.cke_combo_button, cke_dialog a {
padding-left: 0;
}
p a:hover, td a:hover {
text-decoration:underline;
color:$highlight-color;
padding-left:14px;
background: url(../images/mini-arrow-orange.gif) no-repeat left;
background-image: none;
}
.ui-widget-content td a:hover, p a:hover, td a:hover {
text-decoration:underline;
color:$highlight-color;
}
.cke_reset_all *:hover {
text-decoration: none;
color: #000;
}
table.cke_dialog_contents a.cke_dialog_ui_button_ok {
color: #000;
border-color: $highlight-color;
background: $highlight-color;
}
.cke_notifications_area {
display: none;
}
td a.no-arrow, td a.no-arrow:visited, .SearchDrawer a.no-arrow, .SearchDrawer a.no-arrow:visited {
text-decoration:none;
color:#000000;
@@ -1583,3 +1594,25 @@ span.refresh-button {
.printable-tab .case-log-history-entry .case-log-history-entry-toggle {
display: none;
}
.history_entry {
position: relative !important;
max-width: 100%;
}
.history_entry_truncated {
max-height: 7em;
overflow: hidden;
}
.history_truncated_toggler {
position: absolute !important;
bottom: 0;
right: 0;
display: block;
cursor: pointer;
width: 16px;
height: 16px;
background-image: url(ui-lightness/images/ui-icons_222222_256x240.png);
background-position: -16px -192px;
}
.history_entry_truncated .history_truncated_toggler {
background-position: 0 -192px;
}

View File

@@ -1,5 +1,5 @@
<?php
// Copyright (C) 2010-2012 Combodo SARL
// Copyright (C) 2010-2016 Combodo SARL
//
// This file is part of iTop.
//
@@ -288,7 +288,7 @@ EOF
{
$('#display_attachment_'+data.att_id).hover( function() { $(this).children(':button').toggleClass('btn_hidden'); } );
}
$('#attachment_plugin').trigger('add_attachment', [data.att_id, data.msg]);
$('#attachment_plugin').trigger('add_attachment', [data.att_id, data.msg, false /* not an inline image */]);
//alert(data.msg);
}
@@ -342,7 +342,7 @@ EOF
$sIcon = utils::GetAbsoluteUrlAppRoot().AttachmentPlugIn::GetFileIcon($sFileName);
$sDownloadLink = utils::GetAbsoluteUrlAppRoot().'pages/ajax.render.php?operation=download_document&class=Attachment&id='.$iAttId.'&field=contents';
$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>');
$oPage->add_ready_script("$('#attachment_plugin').trigger('add_attachment', [$iAttId, '".addslashes($sFileName)."']);");
$oPage->add_ready_script("$('#attachment_plugin').trigger('add_attachment', [$iAttId, '".addslashes($sFileName)."', false /* not an line image */]);");
}
}
}

View File

@@ -1,5 +1,5 @@
<?php
// Copyright (C) 2010-2012 Combodo SARL
// Copyright (C) 2010-2016 Combodo SARL
//
// This file is part of iTop.
//
@@ -20,7 +20,7 @@
/**
* Handles various ajax requests
*
* @copyright Copyright (C) 2010-2012 Combodo SARL
* @copyright Copyright (C) 2010-2016 Combodo SARL
* @license http://opensource.org/licenses/AGPL-3.0
*/
@@ -97,6 +97,118 @@ try
}
break;
case 'cke_img_upload':
// Image uploaded via CKEditor
$aResult = array(
'uploaded' => 0,
'fileName' => '',
'url' => '',
'icon' => '',
'msg' => '',
'att_id' => 0,
'preview' => 'false',
);
$sObjClass = stripslashes(utils::ReadParam('obj_class', '', false, 'class'));
$sTempId = utils::ReadParam('temp_id', '');
if (empty($sObjClass))
{
$aResult['error'] = "Missing argument 'obj_class'";
}
elseif (empty($sTempId))
{
$aResult['error'] = "Missing argument 'temp_id'";
}
else
{
try
{
$oDoc = utils::ReadPostedDocument('upload');
$oAttachment = MetaModel::NewObject('Attachment');
$oAttachment->Set('expire', time() + 3600); // one hour...
$oAttachment->Set('temp_id', $sTempId);
$oAttachment->Set('item_class', $sObjClass);
$oAttachment->SetDefaultOrgId();
$oAttachment->Set('contents', $oDoc);
$iAttId = $oAttachment->DBInsert();
$aResult['uploaded'] = 1;
$aResult['msg'] = $oDoc->GetFileName();
$aResult['fileName'] = $oDoc->GetFileName();
$aResult['url'] = utils::GetAbsoluteUrlAppRoot().ATTACHMENT_DOWNLOAD_URL.$iAttId;
$aResult['icon'] = utils::GetAbsoluteUrlAppRoot().AttachmentPlugIn::GetFileIcon($oDoc->GetFileName());
$aResult['att_id'] = $iAttId;
$aResult['preview'] = $oDoc->IsPreviewAvailable() ? 'true' : 'false';
}
catch (FileUploadException $e)
{
$aResult['error'] = $e->GetMessage();
}
}
$oPage->add(json_encode($aResult));
break;
case 'cke_browse':
$oPage = new NiceWebPage('Browse for image...');
$oPage->add_linked_stylesheet(utils::GetAbsoluteUrlModulesRoot().'itop-attachments/css/magnific-popup.css');
$oPage->add_linked_script(utils::GetAbsoluteUrlModulesRoot().'itop-attachments/js/jquery.magnific-popup.min.js');
$sImgUrl = utils::GetAbsoluteUrlAppRoot().ATTACHMENT_DOWNLOAD_URL;
$oPage->add_script(
<<<EOF
// Helper function to get parameters from the query string.
function getUrlParam( paramName ) {
var reParam = new RegExp( '(?:[\?&]|&)' + paramName + '=([^&]+)', 'i' );
var match = window.location.search.match( reParam );
return ( match && match.length > 1 ) ? match[1] : null;
}
// Simulate user action of selecting a file to be returned to CKEditor.
function returnFileUrl(iAttId, sAltText) {
var funcNum = getUrlParam( 'CKEditorFuncNum' );
var fileUrl = '$sImgUrl'+iAttId;
window.opener.CKEDITOR.tools.callFunction( funcNum, fileUrl, function() {
// Get the reference to a dialog window.
var dialog = this.getDialog();
// Check if this is the Image Properties dialog window.
if ( dialog.getName() == 'image' ) {
// Get the reference to a text field that stores the "alt" attribute.
var element = dialog.getContentElement( 'info', 'txtAlt' );
// Assign the new value.
if ( element )
element.setValue(sAltText);
}
// Return "false" to stop further execution. In such case CKEditor will ignore the second argument ("fileUrl")
// and the "onSelect" function assigned to the button that called the file manager (if defined).
// return false;
} );
window.close();
}
EOF
);
$oPage->add_ready_script(
<<<EOF
$('.img-picker').magnificPopup({type: 'image', closeOnContentClick: true });
EOF
);
$sTempId = utils::ReadParam('temp_id');
$sClass = utils::ReadParam('obj_class', '', false, 'class');
$iObjectId = utils::ReadParam('obj_key', 0, false, 'integer');
$sOQL = "SELECT Attachment WHERE ((temp_id = :temp_id) OR (item_class = :obj_class AND item_id = :obj_id))";
$oSet = new DBObjectSet(DBObjectSearch::FromOQL($sOQL), array(), array('temp_id' => $sTempId, 'obj_class' => $sClass, 'obj_id' => $iObjectId));
while($oAttachment = $oSet->Fetch())
{
$oDoc = $oAttachment->Get('contents');
if ($oDoc->GetMainMimeType() == 'image')
{
$sDocName = addslashes(htmlentities($oDoc->GetFileName(), ENT_QUOTES, 'UTF-8'));
$iAttId = $oAttachment->GetKey();
$oPage->add("<div style=\"float:left;margin:1em;text-align:center;\"><img class=\"img-picker\" style=\"max-width:300px;cursor:zoom-in\" href=\"{$sImgUrl}{$iAttId}\" alt=\"$sDocName\" title=\"$sDocName\" src=\"{$sImgUrl}{$iAttId}\"><br/><button onclick=\"returnFileUrl($iAttId, '$sDocName')\">Insert</button></div>");
}
}
break;
default:
$oPage->p("Missing argument 'operation'");
}

View File

@@ -0,0 +1,374 @@
/* Magnific Popup CSS */
.mfp-bg {
top: 0;
left: 0;
width: 100%;
height: 100%;
z-index: 1042;
overflow: hidden;
position: fixed;
background: #0b0b0b;
opacity: 0.8;
filter: alpha(opacity=80); }
.mfp-wrap {
top: 0;
left: 0;
width: 100%;
height: 100%;
z-index: 1043;
position: fixed;
outline: none !important;
-webkit-backface-visibility: hidden; }
.mfp-container {
text-align: center;
position: absolute;
width: 100%;
height: 100%;
left: 0;
top: 0;
padding: 0 8px;
-webkit-box-sizing: border-box;
-moz-box-sizing: border-box;
box-sizing: border-box; }
.mfp-container:before {
content: '';
display: inline-block;
height: 100%;
vertical-align: middle; }
.mfp-align-top .mfp-container:before {
display: none; }
.mfp-content {
position: relative;
display: inline-block;
vertical-align: middle;
margin: 0 auto;
text-align: left;
z-index: 1045; }
.mfp-inline-holder .mfp-content, .mfp-ajax-holder .mfp-content {
width: 100%;
cursor: auto; }
.mfp-ajax-cur {
cursor: progress; }
.mfp-zoom-out-cur, .mfp-zoom-out-cur .mfp-image-holder .mfp-close {
cursor: -moz-zoom-out;
cursor: -webkit-zoom-out;
cursor: zoom-out; }
.mfp-zoom {
cursor: pointer;
cursor: -webkit-zoom-in;
cursor: -moz-zoom-in;
cursor: zoom-in; }
.mfp-auto-cursor .mfp-content {
cursor: auto; }
.mfp-close, .mfp-arrow, .mfp-preloader, .mfp-counter {
-webkit-user-select: none;
-moz-user-select: none;
user-select: none; }
.mfp-loading.mfp-figure {
display: none; }
.mfp-hide {
display: none !important; }
.mfp-preloader {
color: #CCC;
position: absolute;
top: 50%;
width: auto;
text-align: center;
margin-top: -0.8em;
left: 8px;
right: 8px;
z-index: 1044; }
.mfp-preloader a {
color: #CCC; }
.mfp-preloader a:hover {
color: #FFF; }
.mfp-s-ready .mfp-preloader {
display: none; }
.mfp-s-error .mfp-content {
display: none; }
button.mfp-close, button.mfp-arrow {
overflow: visible;
cursor: pointer;
background: transparent;
border: 0;
-webkit-appearance: none;
display: block;
outline: none;
padding: 0;
z-index: 1046;
-webkit-box-shadow: none;
box-shadow: none; }
button::-moz-focus-inner {
padding: 0;
border: 0; }
.mfp-close {
width: 44px;
height: 44px;
line-height: 44px;
position: absolute;
right: 0;
top: 0;
text-decoration: none;
text-align: center;
opacity: 0.65;
filter: alpha(opacity=65);
padding: 0 0 18px 10px;
color: #FFF;
font-style: normal;
font-size: 28px;
font-family: Arial, Baskerville, monospace; }
.mfp-close:hover, .mfp-close:focus {
opacity: 1;
filter: alpha(opacity=100); }
.mfp-close:active {
top: 1px; }
.mfp-close-btn-in .mfp-close {
color: #333; }
.mfp-image-holder .mfp-close, .mfp-iframe-holder .mfp-close {
color: #FFF;
right: -6px;
text-align: right;
padding-right: 6px;
width: 100%; }
.mfp-counter {
position: absolute;
top: 0;
right: 0;
color: #CCC;
font-size: 12px;
line-height: 18px;
white-space: nowrap; }
.mfp-arrow {
position: absolute;
opacity: 0.65;
filter: alpha(opacity=65);
margin: 0;
top: 50%;
margin-top: -55px;
padding: 0;
width: 90px;
height: 110px;
-webkit-tap-highlight-color: rgba(0, 0, 0, 0); }
.mfp-arrow:active {
margin-top: -54px; }
.mfp-arrow:hover, .mfp-arrow:focus {
opacity: 1;
filter: alpha(opacity=100); }
.mfp-arrow:before, .mfp-arrow:after, .mfp-arrow .mfp-b, .mfp-arrow .mfp-a {
content: '';
display: block;
width: 0;
height: 0;
position: absolute;
left: 0;
top: 0;
margin-top: 35px;
margin-left: 35px;
border: medium inset transparent; }
.mfp-arrow:after, .mfp-arrow .mfp-a {
border-top-width: 13px;
border-bottom-width: 13px;
top: 8px; }
.mfp-arrow:before, .mfp-arrow .mfp-b {
border-top-width: 21px;
border-bottom-width: 21px;
opacity: 0.7; }
.mfp-arrow-left {
left: 0; }
.mfp-arrow-left:after, .mfp-arrow-left .mfp-a {
border-right: 17px solid #FFF;
margin-left: 31px; }
.mfp-arrow-left:before, .mfp-arrow-left .mfp-b {
margin-left: 25px;
border-right: 27px solid #3F3F3F; }
.mfp-arrow-right {
right: 0; }
.mfp-arrow-right:after, .mfp-arrow-right .mfp-a {
border-left: 17px solid #FFF;
margin-left: 39px; }
.mfp-arrow-right:before, .mfp-arrow-right .mfp-b {
border-left: 27px solid #3F3F3F; }
.mfp-iframe-holder {
padding-top: 40px;
padding-bottom: 40px; }
.mfp-iframe-holder .mfp-content {
line-height: 0;
width: 100%;
max-width: 900px; }
.mfp-iframe-holder .mfp-close {
top: -40px; }
.mfp-iframe-scaler {
width: 100%;
height: 0;
overflow: hidden;
padding-top: 56.25%; }
.mfp-iframe-scaler iframe {
position: absolute;
display: block;
top: 0;
left: 0;
width: 100%;
height: 100%;
box-shadow: 0 0 8px rgba(0, 0, 0, 0.6);
background: #000; }
/* Main image in popup */
img.mfp-img {
width: auto;
max-width: 100%;
height: auto;
display: block;
line-height: 0;
-webkit-box-sizing: border-box;
-moz-box-sizing: border-box;
box-sizing: border-box;
padding: 40px 0 40px;
margin: 0 auto; }
/* The shadow behind the image */
.mfp-figure {
line-height: 0; }
.mfp-figure:after {
content: '';
position: absolute;
left: 0;
top: 40px;
bottom: 40px;
display: block;
right: 0;
width: auto;
height: auto;
z-index: -1;
box-shadow: 0 0 8px rgba(0, 0, 0, 0.6);
background: #444; }
.mfp-figure small {
color: #BDBDBD;
display: block;
font-size: 12px;
line-height: 14px; }
.mfp-figure figure {
margin: 0; }
.mfp-bottom-bar {
margin-top: -36px;
position: absolute;
top: 100%;
left: 0;
width: 100%;
cursor: auto; }
.mfp-title {
text-align: left;
line-height: 18px;
color: #F3F3F3;
word-wrap: break-word;
padding-right: 36px; }
.mfp-image-holder .mfp-content {
max-width: 100%; }
.mfp-gallery .mfp-image-holder .mfp-figure {
cursor: pointer; }
@media screen and (max-width: 800px) and (orientation: landscape), screen and (max-height: 300px) {
/**
* Remove all paddings around the image on small screen
*/
.mfp-img-mobile .mfp-image-holder {
padding-left: 0;
padding-right: 0; }
.mfp-img-mobile img.mfp-img {
padding: 0; }
.mfp-img-mobile .mfp-figure:after {
top: 0;
bottom: 0; }
.mfp-img-mobile .mfp-figure small {
display: inline;
margin-left: 5px; }
.mfp-img-mobile .mfp-bottom-bar {
background: rgba(0, 0, 0, 0.6);
bottom: 0;
margin: 0;
top: auto;
padding: 3px 5px;
position: fixed;
-webkit-box-sizing: border-box;
-moz-box-sizing: border-box;
box-sizing: border-box; }
.mfp-img-mobile .mfp-bottom-bar:empty {
padding: 0; }
.mfp-img-mobile .mfp-counter {
right: 5px;
top: 3px; }
.mfp-img-mobile .mfp-close {
top: 0;
right: 0;
width: 35px;
height: 35px;
line-height: 35px;
background: rgba(0, 0, 0, 0.6);
position: fixed;
text-align: center;
padding: 0; }
}
@media all and (max-width: 900px) {
.mfp-arrow {
-webkit-transform: scale(0.75);
transform: scale(0.75); }
.mfp-arrow-left {
-webkit-transform-origin: 0;
transform-origin: 0; }
.mfp-arrow-right {
-webkit-transform-origin: 100%;
transform-origin: 100%; }
.mfp-container {
padding-left: 6px;
padding-right: 6px; }
}
.mfp-ie7 .mfp-img {
padding: 0; }
.mfp-ie7 .mfp-bottom-bar {
width: 600px;
left: 50%;
margin-left: -300px;
margin-top: 5px;
padding-bottom: 5px; }
.mfp-ie7 .mfp-container {
padding: 0; }
.mfp-ie7 .mfp-content {
padding-top: 44px; }
.mfp-ie7 .mfp-close {
top: 0;
right: 0;
padding-top: 0; }

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

View File

@@ -1,5 +1,5 @@
<?php
// Copyright (C) 2010-2015 Combodo SARL
// Copyright (C) 2010-2016 Combodo SARL
//
// This file is part of iTop.
//
@@ -16,7 +16,6 @@
// You should have received a copy of the GNU Affero General Public License
// along with iTop. If not, see <http://www.gnu.org/licenses/>
class AttachmentPlugIn implements iApplicationUIExtension, iApplicationObjectExtension
{
protected static $m_bIsModified = false;
@@ -204,6 +203,7 @@ class AttachmentPlugIn implements iApplicationUIExtension, iApplicationObjectExt
$sTitle = ($oSet->Count() > 0)? Dict::Format('Attachments:TabTitle_Count', $oSet->Count()) : Dict::S('Attachments:EmptyTabTitle');
$oPage->SetCurrentTab($sTitle);
}
$sMaxWidth = MetaModel::GetModuleSetting('itop-attachment', 'inline_image_max_width', '450px');
$oPage->add_style(
<<<EOF
.attachment {
@@ -233,6 +233,9 @@ class AttachmentPlugIn implements iApplicationUIExtension, iApplicationObjectExt
padding: 0;
float: none;
}
.inline-image {
cursor: zoom-in;
}
EOF
);
$oPage->add('<fieldset>');
@@ -243,15 +246,24 @@ EOF
$sIsDeleteEnabled = $this->m_bDeleteEnabled ? 'true' : 'false';
$iTransactionId = $oPage->GetTransactionId();
$sClass = get_class($oObject);
$iObjectId = $oObject->Getkey();
$sTempId = session_id().'_'.$iTransactionId;
$sDeleteBtn = Dict::S('Attachments:DeleteBtn');
$oPage->add_script(
<<<EOF
function RemoveAttachment(att_id)
{
$('#attachment_'+att_id).attr('name', 'removed_attachments[]');
$('#display_attachment_'+att_id).hide();
$('#attachment_plugin').trigger('remove_attachment', [att_id]);
var bDelete = true;
if ($('#display_attachment_'+att_id).hasClass('image-in-use'))
{
bDelete = window.confirm('This image is used in a description. Delete it anyway?');
}
if (bDelete)
{
$('#attachment_'+att_id).attr('name', 'removed_attachments[]');
$('#display_attachment_'+att_id).hide();
$('#attachment_plugin').trigger('remove_attachment', [att_id]);
}
return false; // Do not submit the form !
}
EOF
@@ -264,7 +276,7 @@ EOF
$sFileName = $oDoc->GetFileName();
$sIcon = utils::GetAbsoluteUrlAppRoot().AttachmentPlugIn::GetFileIcon($sFileName);
$sPreview = $oDoc->IsPreviewAvailable() ? 'true' : 'false';
$sDownloadLink = utils::GetAbsoluteUrlAppRoot().'pages/ajax.render.php?operation=download_document&class=Attachment&id='.$iAttId.'&field=contents';
$sDownloadLink = utils::GetAbsoluteUrlAppRoot().ATTACHMENT_DOWNLOAD_URL.$iAttId;
$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>');
}
@@ -291,10 +303,10 @@ EOF
$oDoc = $oAttachment->Get('contents');
$sFileName = $oDoc->GetFileName();
$sIcon = utils::GetAbsoluteUrlAppRoot().AttachmentPlugIn::GetFileIcon($sFileName);
$sDownloadLink = utils::GetAbsoluteUrlAppRoot().'pages/ajax.render.php?operation=download_document&class=Attachment&id='.$iAttId.'&field=contents';
$sDownloadLink = utils::GetAbsoluteUrlAppRoot().ATTACHMENT_DOWNLOAD_URL.$iAttId;
$sPreview = $oDoc->IsPreviewAvailable() ? 'true' : 'false';
$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>');
$oPage->add_ready_script("$('#attachment_plugin').trigger('add_attachment', [$iAttId, '".addslashes($sFileName)."']);");
$oPage->add_ready_script("$('#attachment_plugin').trigger('add_attachment', [$iAttId, '".addslashes($sFileName)."', false /* not an line image */]);");
}
}
}
@@ -305,9 +317,21 @@ EOF
$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);
$oPage->add_linked_script(utils::GetAbsoluteUrlAppRoot().'js/jquery.iframe-transport.js');
$oPage->add_linked_script(utils::GetAbsoluteUrlAppRoot().'js/jquery.fileupload.js');
$oPage->add_ready_script(
$oPage->add_linked_script(utils::GetAbsoluteUrlAppRoot().'js/jquery.fileupload.js');
$oPage->add_linked_stylesheet(utils::GetAbsoluteUrlModulesRoot().'itop-attachments/css/magnific-popup.css');
$oPage->add_linked_script(utils::GetAbsoluteUrlModulesRoot().'itop-attachments/js/jquery.magnific-popup.min.js');
$maxWidth = MetaModel::GetModuleSetting('itop-standard-email-synchro', 'inline_image_max_width', '');
if ($maxWidth !== '')
{
$sStyle = "style=\"max-width:{$maxWidth}px;cursor:zoom-in;\"";
}
else
{
$sStyle = "style=\"cursor:zoom-in;\"";
}
$sDownloadLink = utils::GetAbsoluteUrlAppRoot().ATTACHMENT_DOWNLOAD_URL;
$oPage->add_ready_script(
<<< EOF
$('#file').fileupload({
url: GetAbsoluteUrlModulesRoot()+'itop-attachments/ajax.attachment.php',
@@ -323,13 +347,13 @@ $oPage->add_ready_script(
}
else
{
var sDownloadLink = GetAbsoluteUrlAppRoot()+'pages/ajax.render.php?operation=download_document&class=Attachment&id='+data.result.att_id+'&field=contents';
var sDownloadLink = '$sDownloadLink'+data.result.att_id;
$('#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>');
if($sIsDeleteEnabled)
{
$('#display_attachment_'+data.result.att_id).hover( function() { $(this).children(':button').toggleClass('btn_hidden'); } );
}
$('#attachment_plugin').trigger('add_attachment', [data.result.att_id, data.result.msg]);
$('#attachment_plugin').trigger('add_attachment', [data.result.att_id, data.result.msg, false /* inline image */]);
}
}
},
@@ -380,7 +404,65 @@ $oPage->add_ready_script(
window.dropZoneTimeout = null;
dropZone.removeClass('drag_in');
}, 300);
});
});
// Hook the file upload of all CKEditor instances
$('.htmlEditor').each(function() {
var oEditor = $(this).ckeditorGet();
oEditor.config.extraPlugins = 'uploadimage';
oEditor.config.uploadUrl = GetAbsoluteUrlModulesRoot()+'itop-attachments/ajax.attachment.php';
oEditor.config.filebrowserBrowseUrl = GetAbsoluteUrlModulesRoot()+'itop-attachments/ajax.attachment.php?operation=cke_browse&temp_id=$sTempId&obj_class=$sClass&obj_key=$iObjectId';
oEditor.on( 'fileUploadResponse', function( evt ) {
// Get XHR and response.
var data = evt.data,
xhr = data.fileLoader.xhr,
response = xhr.responseText.split( '|' );
var oValues = JSON.parse(response[0]);
var sDownloadLink = '$sDownloadLink'+oValues.att_id;
$('#attachments').append('<div class="attachment" id="display_attachment_'+oValues.att_id+'"><a data-preview="'+oValues.preview+'" href="'+sDownloadLink+'"><img src="'+oValues.icon+'"><br/>'+oValues.msg+'<input id="attachment_'+oValues.att_id+'" type="hidden" name="attachments[]" value="'+oValues.att_id+'"/></a><br/><input type="button" class="btn_hidden" value="{$sDeleteBtn}" onClick="RemoveAttachment('+oValues.att_id+');"/></div>');
if(true)
{
$('#display_attachment_'+oValues.att_id).hover( function() { $(this).children(':button').toggleClass('btn_hidden'); } );
}
$('#attachment_plugin').trigger('add_attachment', [oValues.att_id, oValues.msg, true /* inline image */]);
} );
oEditor.on( 'fileUploadRequest', function( evt ) {
evt.data.fileLoader.uploadUrl += '?operation=cke_img_upload&temp_id=$sTempId&obj_class=$sClass';
}, null, null, 4 ); // Listener with priority 4 will be executed before priority 5.
});
$('img[data-att-id]').each(function() {
if ('$sMaxWidth' != '')
{
$(this).css({'max-width': '$sMaxWidth', width: '', height: '', 'max-height': ''});
}
$(this).addClass('inline-image').attr('href', $(this).attr('src'));
}).magnificPopup({type: 'image', closeOnContentClick: true });
// check if the attachments are used by inline images
window.setTimeout( function() {
$('.attachment a').each(function() {
var sUrl = $(this).attr('href');
if($('img[src="'+sUrl+'"]').length > 0)
{
$(this).addClass('image-in-use').find('img').wrap('<div class="image-in-use-wrapper" style="position:relative;display:inline-block;"></div>');
}
});
$('.htmlEditor').each(function() {
var oEditor = $(this).ckeditorGet();
var sHtml = oEditor.getData();
var jElement = $('<div/>').html(sHtml).contents();
jElement.find('img').each(function() {
var sSrc = $(this).attr('src');
$('.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>');
});
});
$('.image-in-use-wrapper').append('<div style="position:absolute;top:0;left:0;"><img src="../images/transp-lock.png"></div>');
}, 200 );
EOF
);
$oPage->p('<span style="display:none;" id="attachment_loading">Loading, please wait...</span>');
@@ -406,7 +488,7 @@ EOF
$sFileName = $oDoc->GetFileName();
$sIcon = utils::GetAbsoluteUrlAppRoot().AttachmentPlugIn::GetFileIcon($sFileName);
$sPreview = $oDoc->IsPreviewAvailable() ? 'true' : 'false';
$sDownloadLink = utils::GetAbsoluteUrlAppRoot().'pages/ajax.render.php?operation=download_document&class=Attachment&id='.$iAttId.'&field=contents';
$sDownloadLink = utils::GetAbsoluteUrlAppRoot().ATTACHMENT_DOWNLOAD_URL.$iAttId;
$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>');
}
}
@@ -415,7 +497,24 @@ EOF
$oPage->add('</fieldset>');
$sPreviewNotAvailable = addslashes(Dict::S('Attachments:PreviewNotAvailable'));
$iMaxWidth = MetaModel::GetModuleSetting('itop-attachments', 'preview_max_width', 290);
$oPage->add_ready_script("$(document).tooltip({ items: '.attachment a', position: { my: 'left top', at: 'right top', using: function( position, feedback ) { $( this ).css( position ); }}, content: function() { if ($(this).attr('data-preview') == 'true') { return('<img style=\"max-width:{$iMaxWidth}px\" src=\"'+$(this).attr('href')+'\"></img>');} else { return '$sPreviewNotAvailable'; }}});");
$oPage->add_ready_script(
<<<EOF
$(document).tooltip({
items: '.attachment a',
position: { my: 'left top', at: 'right top', using: function( position, feedback ) { $( this ).css( position ); }},
content: function() { if ($(this).attr('data-preview') == 'true') { return('<img style=\"max-width:{$iMaxWidth}px\" src=\"'+$(this).attr('href')+'\"></img>');} else { return '$sPreviewNotAvailable'; }}
});
$('img[data-att-id]').each(function() {
if ('$sMaxWidth' != '')
{
$(this).css({'max-width': '$sMaxWidth', width: '', height: '', 'max-height': ''});
}
$(this).addClass('inline-image');
$(this).attr('href', $(this).attr('src'));
}).magnificPopup({type: 'image', closeOnContentClick: true });
EOF
);
}
protected static function UpdateAttachments($oObject, $oChange = null)

View File

@@ -130,6 +130,7 @@
<sql>description</sql>
<default_value/>
<is_null_allowed>false</is_null_allowed>
<format>html</format>
</field>
<field id="start_date" xsi:type="AttributeDateTime">
<always_load_in_tables>true</always_load_in_tables>

View File

@@ -1,6 +1,6 @@
<?php
// Copyright (C) 2010-2014 Combodo SARL
// Copyright (C) 2010-2016 Combodo SARL
//
// This file is part of iTop.
//
@@ -857,6 +857,7 @@ Dict::Add('CS CZ', 'Czech', 'Čeština', array(
'Core:BulkExport:SpreadsheetOptions' => 'Možnosti tabulky',
'Core:BulkExport:OptionNoLocalize' => 'Nepřekládat hodnoty číselníků',
'Core:BulkExport:OptionLinkSets' => 'Zahrnout odkazované objekty',
'Core:BulkExport:OptionFormattedText' => 'Preserve text formatting~~',
'Core:BulkExport:ScopeDefinition' => 'Definice objektů k exportu',
'Core:BulkExportLabelOQLExpression' => 'Dotaz OQL:',
'Core:BulkExportLabelPhrasebookEntry' => 'Query Phrasebook Entry:~~',

View File

@@ -1,5 +1,5 @@
<?php
// Copyright (C) 2010-2012 Combodo SARL
// Copyright (C) 2010-2016 Combodo SARL
//
// This file is part of iTop.
//
@@ -2478,6 +2478,7 @@ Operators:<br/>
'Core:BulkExport:SpreadsheetOptions' => 'Spreadsheet Options~~',
'Core:BulkExport:OptionNoLocalize' => 'Do not localize the values (for Enumerated fields)~~',
'Core:BulkExport:OptionLinkSets' => 'Include linked objects~~',
'Core:BulkExport:OptionFormattedText' => 'Preserve text formatting~~',
'Core:BulkExport:ScopeDefinition' => 'Definition of the objects to export~~',
'Core:BulkExportLabelOQLExpression' => 'OQL Query:~~',
'Core:BulkExportLabelPhrasebookEntry' => 'Query Phrasebook Entry:~~',

View File

@@ -1,5 +1,5 @@
<?php
// Copyright (C) 2010-2012 Combodo SARL
// Copyright (C) 2010-2016 Combodo SARL
//
// This file is part of iTop.
//
@@ -19,7 +19,7 @@
/**
* @author Stephan Rosenke <stephan.rosenke@itomig.de>
* @copyright Copyright (C) 2010-2012 Combodo SARL
* @copyright Copyright (C) 2010-2016 Combodo SARL
* @licence http://opensource.org/licenses/AGPL-3.0
*/
@@ -590,6 +590,7 @@ Operatoren:<br/>
'Core:BulkExport:SpreadsheetOptions' => 'Spreadsheet-Optionen',
'Core:BulkExport:OptionNoLocalize' => 'Werte von Aufzählungsfeldern nicht lokalisieren',
'Core:BulkExport:OptionLinkSets' => 'Include linked objects~~',
'Core:BulkExport:OptionFormattedText' => 'Preserve Textformatierung',
'Core:BulkExport:ScopeDefinition' => 'Definition der zu exportierenden Objekte',
'Core:BulkExportLabelOQLExpression' => 'OQL-Abfrage',
'Core:BulkExportLabelPhrasebookEntry' => 'Query-Bibliotheks-Eintrag:',

View File

@@ -1,5 +1,5 @@
<?php
// Copyright (C) 2010-2014 Combodo SARL
// Copyright (C) 2010-2016 Combodo SARL
//
// This file is part of iTop.
//
@@ -20,7 +20,7 @@
/**
* Localized data
*
* @copyright Copyright (C) 2010-2012 Combodo SARL
* @copyright Copyright (C) 2010-2016 Combodo SARL
* @license http://opensource.org/licenses/AGPL-3.0
*/
@@ -842,6 +842,7 @@ Dict::Add('EN US', 'English', 'English', array(
'Core:BulkExport:SpreadsheetOptions' => 'Spreadsheet Options',
'Core:BulkExport:OptionNoLocalize' => 'Do not localize the values (for Enumerated fields)',
'Core:BulkExport:OptionLinkSets' => 'Include linked objects',
'Core:BulkExport:OptionFormattedText' => 'Preserve text formatting',
'Core:BulkExport:ScopeDefinition' => 'Definition of the objects to export',
'Core:BulkExportLabelOQLExpression' => 'OQL Query:',
'Core:BulkExportLabelPhrasebookEntry' => 'Query Phrasebook Entry:',

View File

@@ -1,5 +1,5 @@
<?php
// Copyright (C) 2010-2012 Combodo SARL
// Copyright (C) 2010-2016 Combodo SARL
//
// This file is part of iTop.
//
@@ -20,7 +20,7 @@
/**
* Localized data
*
* @copyright Copyright (C) 2010-2012 Combodo SARL
* @copyright Copyright (C) 2010-2016 Combodo SARL
* @license http://opensource.org/licenses/AGPL-3.0
*/
@@ -836,6 +836,7 @@ Dict::Add('ES CR', 'Spanish', 'Español, Castellano', array(
'Core:BulkExport:SpreadsheetOptions' => 'Spreadsheet Options~~',
'Core:BulkExport:OptionNoLocalize' => 'Do not localize the values (for Enumerated fields)~~',
'Core:BulkExport:OptionLinkSets' => 'Include linked objects~~',
'Core:BulkExport:OptionFormattedText' => 'Preserve text formatting~~',
'Core:BulkExport:ScopeDefinition' => 'Definition of the objects to export~~',
'Core:BulkExportLabelOQLExpression' => 'OQL Query:~~',
'Core:BulkExportLabelPhrasebookEntry' => 'Query Phrasebook Entry:~~',

View File

@@ -1,5 +1,5 @@
<?php
// Copyright (C) 2010-2014 Combodo SARL
// Copyright (C) 2010-2016 Combodo SARL
//
// This file is part of iTop.
//
@@ -18,7 +18,7 @@
/**
* @copyright Copyright (C) 2010-2012 Combodo SARL
* @copyright Copyright (C) 2010-2016 Combodo SARL
* @license http://opensource.org/licenses/AGPL-3.0
*/
@@ -700,6 +700,7 @@ Opérateurs :<br/>
'Core:BulkExport:SpreadsheetOptions' => 'Options du format HTML pour Excel',
'Core:BulkExport:OptionNoLocalize' => 'Ne pas traduire les valeurs (pour les champs de type "Enum")',
'Core:BulkExport:OptionLinkSets' => 'Inclure les objets liés',
'Core:BulkExport:OptionFormattedText' => 'Préserver le formatage du texte',
'Core:BulkExport:ScopeDefinition' => 'Définition des objets à exporter',
'Core:BulkExportLabelOQLExpression' => 'Requête OQL:',
'Core:BulkExportLabelPhrasebookEntry' => 'Entrée du livre des requêtes:',

View File

@@ -1,5 +1,5 @@
<?php
// Copyright (C) 2010-2012 Combodo SARL
// Copyright (C) 2010-2016 Combodo SARL
//
// This file is part of iTop.
//
@@ -17,7 +17,7 @@
// along with iTop. If not, see <http://www.gnu.org/licenses/>
/**
* @copyright Copyright (C) 2010-2012 Combodo SARL
* @copyright Copyright (C) 2010-2016 Combodo SARL
* @license http://opensource.org/licenses/AGPL-3.0
*/
@@ -593,6 +593,7 @@ Operators:<br/>
'Core:BulkExport:SpreadsheetOptions' => 'Spreadsheet Options~~',
'Core:BulkExport:OptionNoLocalize' => 'Do not localize the values (for Enumerated fields)~~',
'Core:BulkExport:OptionLinkSets' => 'Include linked objects~~',
'Core:BulkExport:OptionFormattedText' => 'Preserve text formatting~~',
'Core:BulkExport:ScopeDefinition' => 'Definition of the objects to export~~',
'Core:BulkExportLabelOQLExpression' => 'OQL Query:~~',
'Core:BulkExportLabelPhrasebookEntry' => 'Query Phrasebook Entry:~~',

View File

@@ -1,5 +1,5 @@
<?php
// Copyright (C) 2010-2012 Combodo SARL
// Copyright (C) 2010-2016 Combodo SARL
//
// This file is part of iTop.
//
@@ -20,7 +20,7 @@
/**
* Localized data
*
* @copyright Copyright (C) 2010-2012 Combodo SARL
* @copyright Copyright (C) 2010-2016 Combodo SARL
* @license http://opensource.org/licenses/AGPL-3.0
*/
@@ -825,6 +825,7 @@ Dict::Add('IT IT', 'Italian', 'Italiano', array(
'Core:BulkExport:SpreadsheetOptions' => 'Spreadsheet Options~~',
'Core:BulkExport:OptionNoLocalize' => 'Do not localize the values (for Enumerated fields)~~',
'Core:BulkExport:OptionLinkSets' => 'Include linked objects~~',
'Core:BulkExport:OptionFormattedText' => 'Preserve text formatting~~',
'Core:BulkExport:ScopeDefinition' => 'Definition of the objects to export~~',
'Core:BulkExportLabelOQLExpression' => 'OQL Query:~~',
'Core:BulkExportLabelPhrasebookEntry' => 'Query Phrasebook Entry:~~',

View File

@@ -1,5 +1,5 @@
<?php
// Copyright (C) 2010-2012 Combodo SARL
// Copyright (C) 2010-2016 Combodo SARL
//
// This file is part of iTop.
//
@@ -17,7 +17,7 @@
// along with iTop. If not, see <http://www.gnu.org/licenses/>
/**
* @copyright Copyright (C) 2010-2012 Combodo SARL
* @copyright Copyright (C) 2010-2016 Combodo SARL
* @licence http://opensource.org/licenses/AGPL-3.0
*/
@@ -615,6 +615,7 @@ Operators:<br/>
'Core:BulkExport:SpreadsheetOptions' => 'Spreadsheet Options~~',
'Core:BulkExport:OptionLinkSets' => 'Include linked objects~~',
'Core:BulkExport:OptionNoLocalize' => 'Do not localize the values (for Enumerated fields)~~',
'Core:BulkExport:OptionFormattedText' => 'Preserve text formatting~~',
'Core:BulkExport:ScopeDefinition' => 'Definition of the objects to export~~',
'Core:BulkExportLabelOQLExpression' => 'OQL Query:~~',
'Core:BulkExportLabelPhrasebookEntry' => 'Query Phrasebook Entry:~~',

View File

@@ -1,5 +1,5 @@
<?php
// Copyright (C) 2010-2012 Combodo SARL
// Copyright (C) 2010-2016 Combodo SARL
//
// This file is part of iTop.
//
@@ -23,7 +23,7 @@
* Linux & Open Source Professionals
* http://www.linprofs.com
*
* @copyright Copyright (C) 2010-2012 Combodo SARL
* @copyright Copyright (C) 2010-2016 Combodo SARL
* @licence http://opensource.org/licenses/AGPL-3.0
*/
@@ -845,6 +845,7 @@ Dict::Add('NL NL', 'Dutch', 'Nederlands', array(
'Core:BulkExport:SpreadsheetOptions' => 'Spreadsheet Options~~',
'Core:BulkExport:OptionLinkSets' => 'Include linked objects~~',
'Core:BulkExport:OptionNoLocalize' => 'Do not localize the values (for Enumerated fields)~~',
'Core:BulkExport:OptionFormattedText' => 'Preserve text formatting~~',
'Core:BulkExport:ScopeDefinition' => 'Definition of the objects to export~~',
'Core:BulkExportLabelOQLExpression' => 'OQL Query:~~',
'Core:BulkExportLabelPhrasebookEntry' => 'Query Phrasebook Entry:~~',

View File

@@ -1,5 +1,5 @@
<?php
// Copyright (C) 2010-2012 Combodo SARL
// Copyright (C) 2010-2016 Combodo SARL
//
// This file is part of iTop.
//
@@ -20,7 +20,7 @@
/**
* Localized data
*
* @copyright Copyright (C) 2010-2012 Combodo SARL
* @copyright Copyright (C) 2010-2016 Combodo SARL
* @license http://opensource.org/licenses/AGPL-3.0
*/
@@ -838,6 +838,7 @@ Dict::Add('PT BR', 'Brazilian', 'Brazilian', array(
'Core:BulkExport:SpreadsheetOptions' => 'Spreadsheet Options~~',
'Core:BulkExport:OptionLinkSets' => 'Include linked objects~~',
'Core:BulkExport:OptionNoLocalize' => 'Do not localize the values (for Enumerated fields)~~',
'Core:BulkExport:OptionFormattedText' => 'Preserve text formatting~~',
'Core:BulkExport:ScopeDefinition' => 'Definition of the objects to export~~',
'Core:BulkExportLabelOQLExpression' => 'OQL Query:~~',
'Core:BulkExportLabelPhrasebookEntry' => 'Query Phrasebook Entry:~~',

View File

@@ -833,6 +833,7 @@ Dict::Add('RU RU', 'Russian', 'Русский', array(
'Core:BulkExport:SpreadsheetOptions' => 'Spreadsheet Options~~',
'Core:BulkExport:OptionLinkSets' => 'Include linked objects~~',
'Core:BulkExport:OptionNoLocalize' => 'Do not localize the values (for Enumerated fields)~~',
'Core:BulkExport:OptionFormattedText' => 'Preserve text formatting~~',
'Core:BulkExport:ScopeDefinition' => 'Definition of the objects to export~~',
'Core:BulkExportLabelOQLExpression' => 'OQL Query:~~',
'Core:BulkExportLabelPhrasebookEntry' => 'Query Phrasebook Entry:~~',

View File

@@ -1,5 +1,5 @@
<?php
// Copyright (C) 2010-2012 Combodo SARL
// Copyright (C) 2010-2016 Combodo SARL
//
// This file is part of iTop.
//
@@ -21,7 +21,7 @@
* Localized data
*
* @author Izzet Sirin <izzet.sirin@htr.com.tr>
* @copyright Copyright (C) 2010-2012 Combodo SARL
* @copyright Copyright (C) 2010-2016 Combodo SARL
* @license http://opensource.org/licenses/AGPL-3.0
*/
@@ -765,6 +765,7 @@ Operators:<br/>
'Core:BulkExport:SpreadsheetOptions' => 'Spreadsheet Options~~',
'Core:BulkExport:OptionLinkSets' => 'Include linked objects~~',
'Core:BulkExport:OptionNoLocalize' => 'Do not localize the values (for Enumerated fields)~~',
'Core:BulkExport:OptionFormattedText' => 'Preserve text formatting~~',
'Core:BulkExport:ScopeDefinition' => 'Definition of the objects to export~~',
'Core:BulkExportLabelOQLExpression' => 'OQL Query:~~',
'Core:BulkExportLabelPhrasebookEntry' => 'Query Phrasebook Entry:~~',

View File

@@ -1,5 +1,5 @@
<?php
// Copyright (C) 2010-2012 Combodo SARL
// Copyright (C) 2010-2016 Combodo SARL
//
// This file is part of iTop.
//
@@ -21,7 +21,7 @@
* Localized data
*
* @author Robert Deng <denglx@gmail.com>
* @copyright Copyright (C) 2010-2012 Combodo SARL
* @copyright Copyright (C) 2010-2016 Combodo SARL
* @license http://opensource.org/licenses/AGPL-3.0
*/
@@ -764,6 +764,7 @@ Operators:<br/>
'Core:BulkExport:SpreadsheetOptions' => 'Spreadsheet Options~~',
'Core:BulkExport:OptionLinkSets' => 'Include linked objects~~',
'Core:BulkExport:OptionNoLocalize' => 'Do not localize the values (for Enumerated fields)~~',
'Core:BulkExport:OptionFormattedText' => 'Preserve text formatting~~',
'Core:BulkExport:ScopeDefinition' => 'Definition of the objects to export~~',
'Core:BulkExportLabelOQLExpression' => 'OQL Query:~~',
'Core:BulkExportLabelPhrasebookEntry' => 'Query Phrasebook Entry:~~',

View File

@@ -249,6 +249,16 @@ function ValidateCKEditField(sFieldId, sPattern, bMandatory, sFormId, nullValue)
else
{
sTextContent = oFormattedContents.contents().find("body").text();
if (sTextContent == '')
{
// No plain text, maybe there is just an image...
var oImg = oFormattedContents.contents().find("body img");
if (oImg.length != 0)
{
sTextContent = 'image';
}
}
}
if (bMandatory && (sTextContent == ''))

View File

@@ -15,19 +15,22 @@ function sprintf(format, etc) {
function setPageSize(table,size, bReload) {
var c = table.config;
c.selectedSize = size;
if (size == -1)
if (c != undefined)
{
size = c.totalRows;
c.selectedSize = size;
if (size == -1)
{
size = c.totalRows;
}
c.size = size;
c.totalPages = Math.ceil(c.totalRows / c.size);
c.pagerPositionSet = false;
if (bReload)
{
moveToPage(table);
}
fixPosition(table);
}
c.size = size;
c.totalPages = Math.ceil(c.totalRows / c.size);
c.pagerPositionSet = false;
if (bReload)
{
moveToPage(table);
}
fixPosition(table);
}
function fixPosition(table) {
@@ -246,6 +249,8 @@ function sprintf(format, etc) {
function applySelection(table)
{
var c = table.config;
if (c == undefined) return;
if (c.selectionMode == 'negative')
{
$(table).find(':checkbox[name^=selectObj]').attr('checked', true);

View File

@@ -834,7 +834,7 @@ $(function()
jTab.find('span').html(sTabText+' <img style="vertical-align:bottom" src="../images/indicator.gif">');
}
$.post(sUrl, oParams, function(data) {
var sDownloadLink = GetAbsoluteUrlAppRoot()+'pages/ajax.render.php?operation=download_document&class=Attachment&id='+data.att_id+'&field=contents';
var sDownloadLink = GetAbsoluteUrlAppRoot()+'pages/ajax.render.php?operation=download_document&class=Attachment&field=contents&id='+data.att_id;
var sIcon = GetAbsoluteUrlModulesRoot()+'itop-attachments/icons/pdf.png';
if (jTab != null)
{

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.2 KiB

After

Width:  |  Height:  |  Size: 3.4 KiB

View File

@@ -1219,6 +1219,7 @@ EOF;
// Added if present...
//
$aParameters['validation_pattern'] = $this->GetPropString($oField, 'validation_pattern');
$aParameters['format'] = $this->GetPropString($oField, 'format');
$aParameters['width'] = $this->GetPropString($oField, 'width');
$aParameters['height'] = $this->GetPropString($oField, 'height');
$aParameters['digits'] = $this->GetPropNumber($oField, 'digits');