Commit ea7a2fac authored by Cédric Anne's avatar Cédric Anne Committed by Johan Cwiklinski
Browse files

Enhanced rich text

parent 65df212e
...@@ -28,16 +28,23 @@ The present file will list all changes made to the project; according to the ...@@ -28,16 +28,23 @@ The present file will list all changes made to the project; according to the
#### Deprecated #### Deprecated
- Usage of `GLPI_FORCE_EMPTY_SQL_MODE` constant - Usage of `GLPI_FORCE_EMPTY_SQL_MODE` constant
- Usage of `CommonDBTM::notificationqueueonaction` property - Usage of `CommonDBTM::notificationqueueonaction` property
- Usage of `NotificationTarget::html_tags` property
- `DBmysql::getTableSchema()` - `DBmysql::getTableSchema()`
- `Calendar::duplicate()` - `Calendar::duplicate()`
- `CommonDBTM::clone()` - `CommonDBTM::clone()`
- `CommonDBTM::prepareInputForClone()` - `CommonDBTM::prepareInputForClone()`
- `CommonDBTM::post_clone()` - `CommonDBTM::post_clone()`
- `Config::getCache()` - `Config::getCache()`
- `Html::clean()`
- `Html::setSimpleTextContent()`
- `Html::setRichTextContent()` - `Html::setRichTextContent()`
- `Html::weblink_extract()`
- `RuleImportComputer` class - `RuleImportComputer` class
- `RuleImportComputerCollection` class - `RuleImportComputerCollection` class
- `Toolbox::doubleEncodeEmails()`
- `Toolbox::getHtmlToDisplay()`
- `Toolbox::useCache()` - `Toolbox::useCache()`
- `Toolbox::unclean_html_cross_side_scripting_deep()`
#### Removed #### Removed
- `Update::declareOldItems()` - `Update::declareOldItems()`
......
...@@ -34,6 +34,8 @@ ...@@ -34,6 +34,8 @@
* @since 9.1 * @since 9.1
*/ */
use Glpi\Toolbox\RichText;
include ('../inc/includes.php'); include ('../inc/includes.php');
header("Content-Type: application/json; charset=UTF-8"); header("Content-Type: application/json; charset=UTF-8");
Html::header_nocache(); Html::header_nocache();
...@@ -52,14 +54,14 @@ $revision = new KnowbaseItem_Revision(); ...@@ -52,14 +54,14 @@ $revision = new KnowbaseItem_Revision();
$revision->getFromDB($oldid); $revision->getFromDB($oldid);
$old = [ $old = [
'name' => $revision->fields['name'], 'name' => $revision->fields['name'],
'answer' => Toolbox::unclean_html_cross_side_scripting_deep($revision->fields['answer']) 'answer' => RichText::getSafeHtml($revision->fields['answer'], true)
]; ];
$revision = $diffid == 0 ? new KnowbaseItem() : new KnowbaseItem_Revision(); $revision = $diffid == 0 ? new KnowbaseItem() : new KnowbaseItem_Revision();
$revision->getFromDB($diffid == 0 ? $kbid : $diffid); $revision->getFromDB($diffid == 0 ? $kbid : $diffid);
$diff = [ $diff = [
'name' => $revision->fields['name'], 'name' => $revision->fields['name'],
'answer' => Toolbox::unclean_html_cross_side_scripting_deep($revision->fields['answer']) 'answer' => RichText::getSafeHtml($revision->fields['answer'], true)
]; ];
echo json_encode([ echo json_encode([
......
...@@ -34,6 +34,8 @@ ...@@ -34,6 +34,8 @@
* @since 9.1 * @since 9.1
*/ */
use Glpi\Toolbox\RichText;
include ('../inc/includes.php'); include ('../inc/includes.php');
header("Content-Type: application/json; charset=UTF-8"); header("Content-Type: application/json; charset=UTF-8");
Html::header_nocache(); Html::header_nocache();
...@@ -50,7 +52,7 @@ $revision = new KnowbaseItem_Revision(); ...@@ -50,7 +52,7 @@ $revision = new KnowbaseItem_Revision();
$revision->getFromDB($revid); $revision->getFromDB($revid);
$rev = [ $rev = [
'name' => $revision->fields['name'], 'name' => $revision->fields['name'],
'answer' => html_entity_decode($revision->fields['answer']) 'answer' => RichText::getSafeHtml($revision->fields['answer'], true)
]; ];
echo json_encode($rev); echo json_encode($rev);
...@@ -34,6 +34,8 @@ ...@@ -34,6 +34,8 @@
* @since 9.5 * @since 9.5
*/ */
use Glpi\Toolbox\RichText;
$AJAX_INCLUDE = 1; $AJAX_INCLUDE = 1;
include ('../inc/includes.php'); include ('../inc/includes.php');
...@@ -47,6 +49,11 @@ if (isset($_POST['itilfollowuptemplates_id']) ...@@ -47,6 +49,11 @@ if (isset($_POST['itilfollowuptemplates_id'])
$template = new ITILFollowupTemplate(); $template = new ITILFollowupTemplate();
$template->getFromDB($_POST['itilfollowuptemplates_id']); $template->getFromDB($_POST['itilfollowuptemplates_id']);
$template->fields = array_map('html_entity_decode', $template->fields); echo json_encode(
echo json_encode($template->fields); [
'content' => RichText::getSafeHtml($template->fields['content'], true),
'requesttypes_id' => $template->fields['requesttypes_id'],
'is_private' => $template->fields['is_private'],
]
);
} }
...@@ -30,6 +30,8 @@ ...@@ -30,6 +30,8 @@
* --------------------------------------------------------------------- * ---------------------------------------------------------------------
*/ */
use Glpi\Toolbox\RichText;
$AJAX_INCLUDE = 1; $AJAX_INCLUDE = 1;
include ('../inc/includes.php'); include ('../inc/includes.php');
...@@ -44,7 +46,7 @@ if (isset($_POST['solutiontemplates_id']) && $_POST['solutiontemplates_id'] > 0) ...@@ -44,7 +46,7 @@ if (isset($_POST['solutiontemplates_id']) && $_POST['solutiontemplates_id'] > 0)
echo json_encode( echo json_encode(
[ [
'content' => Toolbox::unclean_cross_side_scripting_deep($template->fields['content']), 'content' => RichText::getSafeHtml($template->fields['content'], true),
'solutiontypes_id' => $template->fields['solutiontypes_id'], 'solutiontypes_id' => $template->fields['solutiontypes_id'],
] ]
); );
......
...@@ -4,7 +4,7 @@ ...@@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically" "This file is @generated automatically"
], ],
"content-hash": "5acd42ee96b63ebc1502a2ad3ceeb813", "content-hash": "087449288541ba61dffffff4cc7ce073",
"packages": [ "packages": [
{ {
"name": "blueimp/jquery-file-upload", "name": "blueimp/jquery-file-upload",
...@@ -499,6 +499,47 @@ ...@@ -499,6 +499,47 @@
}, },
"time": "2021-04-26T09:17:50+00:00" "time": "2021-04-26T09:17:50+00:00"
}, },
{
"name": "html2text/html2text",
"version": "4.3.1",
"source": {
"type": "git",
"url": "https://github.com/mtibben/html2text.git",
"reference": "61ad68e934066a6f8df29a3d23a6460536d0855c"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/mtibben/html2text/zipball/61ad68e934066a6f8df29a3d23a6460536d0855c",
"reference": "61ad68e934066a6f8df29a3d23a6460536d0855c",
"shasum": ""
},
"require-dev": {
"phpunit/phpunit": "~4"
},
"suggest": {
"ext-mbstring": "For best performance",
"symfony/polyfill-mbstring": "If you can't install ext-mbstring"
},
"type": "library",
"autoload": {
"psr-4": {
"Html2Text\\": [
"src/",
"test/"
]
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"GPL-2.0-or-later"
],
"description": "Converts HTML to formatted plain text",
"support": {
"issues": "https://github.com/mtibben/html2text/issues",
"source": "https://github.com/mtibben/html2text/tree/4.3.1"
},
"time": "2020-04-16T23:44:31+00:00"
},
{ {
"name": "htmlawed/htmlawed", "name": "htmlawed/htmlawed",
"version": "1.2.5", "version": "1.2.5",
......
...@@ -502,10 +502,6 @@ li.auto_comp { ...@@ -502,10 +502,6 @@ li.auto_comp {
width:550px; width:550px;
} }
#kbanswer ul {
padding-left: 15px;
}
.tdkb_result { .tdkb_result {
vertical-align:top; vertical-align:top;
text-align:left; text-align:left;
......
...@@ -311,6 +311,53 @@ hr { ...@@ -311,6 +311,53 @@ hr {
/* ################--------------- Rich Text ---------------#################### */ /* ################--------------- Rich Text ---------------#################### */
.rich_text_container { .rich_text_container {
p {
margin: 1.12em 0;
}
h1 {
font-size: 2em;
margin: 0.67em 0;
}
h2 {
font-size: 1.5em;
margin: .75em 0;
font-weight: bolder;
}
h3 {
font-size: 1.17em;
margin: .83em 0;
font-weight: bolder;
}
h4 {
margin: 1.12em 0;
font-weight: bolder;
}
h5 {
font-size: .83em;
margin: 1.5em 0;
font-weight: bolder;
}
h6 {
font-size: .75em;
margin: 1.67em 0;
font-weight: bolder;
}
address {
font-style: italic;
}
pre {
font-family: monospace;
white-space: pre;
}
ul { ul {
list-style-type: disc; list-style-type: disc;
margin: 1em 0; margin: 1em 0;
...@@ -347,6 +394,10 @@ hr { ...@@ -347,6 +394,10 @@ hr {
} }
} }
img {
max-width: 100%;
}
.user-mention, [data-user-mention="true"] { .user-mention, [data-user-mention="true"] {
border-radius: 3px; border-radius: 3px;
padding: 5px; padding: 5px;
...@@ -1804,7 +1855,13 @@ td, th { ...@@ -1804,7 +1855,13 @@ td, th {
/* ################--------------- User Picture ---------------#################### */ /* ################--------------- User Picture ---------------#################### */
.qtip { .qtip {
max-width: 380px !important; max-width: none;
.qtip-content {
max-height: 250px;
max-width: 400px;
overflow: auto;
}
} }
.tooltip { .tooltip {
...@@ -2010,54 +2067,6 @@ img.picture_square { ...@@ -2010,54 +2067,6 @@ img.picture_square {
} }
#kbanswer { #kbanswer {
ul {
padding-left: 15px;
list-style-type: circle;
}
ol {
padding-left: 15px;
list-style-type: decimal;
}
p {
margin: 1.12em 0;
}
h1 {
font-size: 2em;
margin: 0.67em 0;
}
h2 {
font-size: 1.5em;
margin: .75em 0;
font-weight: bolder;
}
h3 {
font-size: 1.17em;
margin: .83em 0;
font-weight: bolder;
}
h4 {
margin: 1.12em 0;
font-weight: bolder;
}
h5 {
font-size: .83em;
margin: 1.5em 0;
font-weight: bolder;
}
h6 {
font-size: .75em;
margin: 1.67em 0;
font-weight: bolder;
}
h1:target, h2:target, h3:target, h4:target, h5:target, h6:target { h1:target, h2:target, h3:target, h4:target, h5:target, h6:target {
background-color: #fff2a8; background-color: #fff2a8;
} }
...@@ -2075,15 +2084,6 @@ img.picture_square { ...@@ -2075,15 +2084,6 @@ img.picture_square {
h1:hover svg, h2:hover svg, h3:hover svg, h4:hover svg, h5:hover svg, h6:hover svg { h1:hover svg, h2:hover svg, h3:hover svg, h4:hover svg, h5:hover svg, h6:hover svg {
visibility: visible; visibility: visible;
} }
address {
font-style: italic;
}
pre {
font-family: monospace;
white-space: pre;
}
} }
.tdkb_result { .tdkb_result {
...@@ -7133,11 +7133,6 @@ div.progress { ...@@ -7133,11 +7133,6 @@ div.progress {
} }
/** /style for relations **/ /** /style for relations **/
// Fix line breaks in pasted rich text
.rich_text_container pre, .rich_text_container span {
white-space: pre-wrap;
}
.log-toolbar { .log-toolbar {
padding-right: 8px !important; padding-right: 8px !important;
......
...@@ -266,7 +266,7 @@ abstract class API { ...@@ -266,7 +266,7 @@ abstract class API {
// login on glpi // login on glpi
if (!$auth->login($params['login'], $params['password'], $noAuto, false, $params['auth'])) { if (!$auth->login($params['login'], $params['password'], $noAuto, false, $params['auth'])) {
$err = Html::clean($auth->getErr()); $err = Toolbox::stripTags($auth->getErr());
if (isset($params['user_token']) if (isset($params['user_token'])
&& !empty($params['user_token'])) { && !empty($params['user_token'])) {
return $this->returnError(__("parameter user_token seems invalid"), 401, "ERROR_GLPI_LOGIN_USER_TOKEN", false); return $this->returnError(__("parameter user_token seems invalid"), 401, "ERROR_GLPI_LOGIN_USER_TOKEN", false);
...@@ -2192,7 +2192,7 @@ abstract class API { ...@@ -2192,7 +2192,7 @@ abstract class API {
// clean html // clean html
foreach ($messages_after_redirect as $messages) { foreach ($messages_after_redirect as $messages) {
foreach ($messages as $message) { foreach ($messages as $message) {
$all_messages[] = Html::clean($message); $all_messages[] = Toolbox::stripTags($message);
} }
} }
...@@ -2341,11 +2341,7 @@ abstract class API { ...@@ -2341,11 +2341,7 @@ abstract class API {
// expand dropdown // expand dropdown
if ($params['expand_dropdowns']) { if ($params['expand_dropdowns']) {
$value = Dropdown::getDropdownName($tablename, $value); $value = Dropdown::getDropdownName($tablename, $value, false, true, false, '');
// fix value for inexistent items
if ($value == " ") {
$value = "";
}
} }
} }
} }
......
...@@ -323,8 +323,7 @@ class Calendar extends AbstractBackend { ...@@ -323,8 +323,7 @@ class Calendar extends AbstractBackend {
$input['uuid'] = Uuid::uuid4(); $input['uuid'] = Uuid::uuid4();
} }
$input = \Html::entities_deep($input); $input = \Toolbox::sanitize($input);
$input = \Toolbox::addslashes_deep($input);
if ($item->isNewItem()) { if ($item->isNewItem()) {
// Auto set entities_id if exists and not set // Auto set entities_id if exists and not set
......
...@@ -32,6 +32,7 @@ ...@@ -32,6 +32,7 @@
namespace Glpi\CalDAV\Traits; namespace Glpi\CalDAV\Traits;
use Glpi\Toolbox\RichText;
use RRule\RRule; use RRule\RRule;
use Sabre\VObject\Component; use Sabre\VObject\Component;
use Sabre\VObject\Component\VCalendar; use Sabre\VObject\Component\VCalendar;
...@@ -95,7 +96,7 @@ trait VobjectConverterTrait { ...@@ -95,7 +96,7 @@ trait VobjectConverterTrait {
$vcomp = $vcalendar->add($component_type); $vcomp = $vcalendar->add($component_type);
} }
$fields = \Html::entity_decode_deep($item->fields); $fields = \Toolbox::unclean_cross_side_scripting_deep($item->fields);
$utc_tz = new \DateTimeZone('UTC'); $utc_tz = new \DateTimeZone('UTC');
if (array_key_exists('uuid', $fields)) { if (array_key_exists('uuid', $fields)) {
...@@ -117,10 +118,15 @@ trait VobjectConverterTrait { ...@@ -117,10 +118,15 @@ trait VobjectConverterTrait {
$vcomp->SUMMARY = $fields['name']; $vcomp->SUMMARY = $fields['name'];
} }
$description = null;
if (array_key_exists('content', $fields)) { if (array_key_exists('content', $fields)) {
$vcomp->DESCRIPTION = $fields['content']; $description = $fields['content'];
} else if (array_key_exists('text', $fields)) { } else if (array_key_exists('text', $fields)) {
$vcomp->DESCRIPTION = $fields['text']; $description = $fields['text'];
}
if ($description !== null) {
// Transform HTML text to plain text
$vcomp->DESCRIPTION = RichText::getTextFromHtml($description, true, false);
} }
$vcomp->URL = $CFG_GLPI['url_base'] . $this->getFormURLWithID($fields['id'], false); $vcomp->URL = $CFG_GLPI['url_base'] . $this->getFormURLWithID($fields['id'], false);
...@@ -243,6 +249,9 @@ trait VobjectConverterTrait { ...@@ -243,6 +249,9 @@ trait VobjectConverterTrait {
} }
$input['rrule'] = $this->getRRuleInputFromVComponent($vcomponent); $input['rrule'] = $this->getRRuleInputFromVComponent($vcomponent);
if ($input['rrule'] === null) {
$input['rrule'] = 'NULL'; // Ensure rrule is set to null on update.
}
$state = $this->getStateInputFromVComponent($vcomponent); $state = $this->getStateInputFromVComponent($vcomponent);
if ($state !== null) { if ($state !== null) {
...@@ -267,8 +276,11 @@ trait VobjectConverterTrait { ...@@ -267,8 +276,11 @@ trait VobjectConverterTrait {
} }
$content = $vcomponent->DESCRIPTION->getValue(); $content = $vcomponent->DESCRIPTION->getValue();
$content = nl2br($content);
$content = str_replace(["\n", "\r"], ['', ''], $content); // Content is handled as plain text in CalDAV client and will be handled as rich text on GLPI side,
// so special chars have to be encoded in html entities.
$content = \Html::entities_deep($content);
return $content; return $content;
} }
......
...@@ -521,7 +521,7 @@ class Certificate extends CommonDBTM { ...@@ -521,7 +521,7 @@ class Certificate extends CommonDBTM {
echo "<tr class='tab_bg_1'>"; echo "<tr class='tab_bg_1'>";
echo "<td>" . __('Expiration date'); echo "<td>" . __('Expiration date');
echo "&nbsp;"; echo "&nbsp;";
Html::showToolTip(nl2br(__('Empty for infinite'))); Html::showToolTip(__('Empty for infinite'));
echo "&nbsp;</td>"; echo "&nbsp;</td>";
echo "<td>"; echo "<td>";
Html::showDateField('date_expiration', ['value' => $this->fields["date_expiration"]]); Html::showDateField('date_expiration', ['value' => $this->fields["date_expiration"]]);
......
...@@ -34,6 +34,8 @@ if (!defined('GLPI_ROOT')) { ...@@ -34,6 +34,8 @@ if (!defined('GLPI_ROOT')) {
die("Sorry. You can't access this file directly"); die("Sorry. You can't access this file directly");
} }
use Glpi\Toolbox\RichText;
/** /**
* Change Class * Change Class
**/ **/
...@@ -1174,8 +1176,6 @@ class Change extends CommonITILObject { ...@@ -1174,8 +1176,6 @@ class Change extends CommonITILObject {
$content = Html::cleanPostForTextArea($content); $content = Html::cleanPostForTextArea($content);
} }