N°2875 - Extract method to find mentioned objects into utils so it can used by extensions, not only the trigger

This commit is contained in:
Molkobain
2021-04-23 17:01:04 +02:00
parent a478294211
commit 7aecdd0dc7
3 changed files with 148 additions and 30 deletions

View File

@@ -90,12 +90,31 @@ class utils
* @since 3.0.0
*/
public const ENUM_SANITIZATION_FILTER_VARIABLE_NAME = 'variable_name';
/**
* @var string
* @since 3.0.0
*/
public const ENUM_SANITIZATION_FILTER_RAW_DATA = 'raw_data';
/**
* @var string
* @since 3.0.0
* @used-by static::GetMentionedObjectsFromText
*/
public const ENUM_TEXT_FORMAT_PLAIN = 'text';
/**
* @var string
* @since 3.0.0
* @used-by static::GetMentionedObjectsFromText
*/
public const ENUM_TEXT_FORMAT_HTML = 'html';
/**
* @var string
* @since 3.0.0
* @used-by static::GetMentionedObjectsFromText
*/
public const ENUM_TEXT_FORMAT_MARKDOWN = 'markdown';
/**
* @var string
* @since 3.0.0
@@ -2867,4 +2886,56 @@ HTML;
return $sAcronym;
}
//----------------------------------------------
// Text manipulation
//----------------------------------------------
/**
* @param string $sText Text containing the mentioned objects to be found
* @param string $sFormat {@uses static::ENUM_TEXT_FORMAT_HTML, ...}
*
* @return array Array of object classes / IDs for the ones found in $sText
* @throws \Exception
* @since 3.0.0
*/
public static function GetMentionedObjectsFromText(string $sText, string $sFormat = self::ENUM_TEXT_FORMAT_HTML): array
{
// First transform text so it can be parsed
switch ($sFormat) {
case static::ENUM_TEXT_FORMAT_HTML:
$sText = static::HtmlToText($sText);
break;
default:
// Don't transform it
break;
}
// Then parse text to find objects
$aMentionedObjects = array();
$aMentionMatches = array();
// Note: As the sanitizer (or CKEditor autocomplete plugin? 🤔) removes data-* attributes from the hyperlink, we can't use the following (simpler) regexp: '/<a\s*([^>]*)data-object-class="([^"]*)"\s*data-object-id="([^"]*)">/i'
// If we change the sanitizer, we might want to use this regexp as it's easier to read
// Note 2: This is only working for backoffice URLs...
$sAppRootUrlForRegExp = addcslashes(utils::GetAbsoluteUrlAppRoot(), '/&');
preg_match_all("/\[([^\]]*)\]\({$sAppRootUrlForRegExp}[^\)]*\&class=([^\)\&]*)\&id=([\d]*)[^\)]*\)/i", $sText, $aMentionMatches);
foreach ($aMentionMatches[0] as $iMatchIdx => $sCompleteMatch) {
$sMatchedClass = $aMentionMatches[2][$iMatchIdx];
$sMatchedId = $aMentionMatches[3][$iMatchIdx];
// Prepare array for matched class if not already present
if (!array_key_exists($sMatchedClass, $aMentionedObjects)) {
$aMentionedObjects[$sMatchedClass] = array();
}
// Add matched ID if not already there
if (!in_array($sMatchedId, $aMentionedObjects[$sMatchedClass])) {
$aMentionedObjects[$sMatchedClass][] = $sMatchedId;
}
}
return $aMentionedObjects;
}
}

View File

@@ -3136,39 +3136,15 @@ abstract class DBObject implements iDisplay
}
// 2 - Find mentioned objects
$aMentionedObjects = array();
foreach($aUpdatedLogAttCodes as $sAttCode)
{
foreach ($aUpdatedLogAttCodes as $sAttCode) {
/** @var \ormCaseLog $oUpdatedCaseLog */
$oUpdatedCaseLog = $this->Get($sAttCode);
$aMentionMatches = array();
// Note: As the sanitizer (or CKEditor autocomplete plugin? 🤔) removes data-* attributes from the hyperlink, we can't use the following (simpler) regexp: '/<a\s*([^>]*)data-object-class="([^"]*)"\s*data-object-id="([^"]*)">/i'
// If we change the sanitizer, we might want to use this regexp as it's easier to read
// Note 2: This is only working for backoffice URLs...
$sAppRootUrlForRegExp = addcslashes(utils::GetAbsoluteUrlAppRoot(), '/&');
preg_match_all("/\[([^\]]*)\]\({$sAppRootUrlForRegExp}[^\)]*\&class=([^\)\&]*)\&id=([\d]*)[^\)]*\)/i", $oUpdatedCaseLog->GetModifiedEntry(), $aMentionMatches);
foreach($aMentionMatches[0] as $iMatchIdx => $sCompleteMatch)
{
$sMatchedClass = $aMentionMatches[2][$iMatchIdx];
$sMatchedId = $aMentionMatches[3][$iMatchIdx];
// Prepare array for matched class if not already present
if(!array_key_exists($sMatchedClass, $aMentionedObjects))
{
$aMentionedObjects[$sMatchedClass] = array();
}
// Add matched ID if not already there
if(!in_array($sMatchedId, $aMentionedObjects[$sMatchedClass]))
{
$aMentionedObjects[$sMatchedClass][] = $sMatchedId;
}
}
$aMentionedObjects = array_merge_recursive($aMentionedObjects, utils::GetMentionedObjectsFromText($oUpdatedCaseLog->GetModifiedEntry()));
}
// 3 - Trigger for those objects
foreach($aMentionedObjects as $sMentionedClass => $aMentionedIds)
{
foreach($aMentionedIds as $sMentionedId)
{
// TODO: This should be refactored and moved into the caselogs loop, otherwise, we won't be able to know which case log triggered the action.
foreach ($aMentionedObjects as $sMentionedClass => $aMentionedIds) {
foreach ($aMentionedIds as $sMentionedId) {
/** @var \DBObject $oMentionedObject */
$oMentionedObject = MetaModel::GetObject($sMentionedClass, $sMentionedId);
// Important: Here the "$this->object()$" placeholder is actually the mentioned object and not the current object. The current object can be used through the $source->object()$ placeholder.

View File

@@ -440,6 +440,9 @@ class UtilsTest extends \Combodo\iTop\Test\UnitTest\ItopTestCase
$this->assertEquals($sTestedAcronym, $sExceptedAcronym, "Acronym for '$sInput' doesn't match. Got '$sTestedAcronym', expected '$sExceptedAcronym'.");
}
/**
* @since 3.0.0
*/
public function ToAcronymProvider()
{
return [
@@ -481,4 +484,72 @@ class UtilsTest extends \Combodo\iTop\Test\UnitTest\ItopTestCase
],
];
}
/**
* @dataProvider GetMentionedObjectsFromTextProvider
* @covers utils::GetMentionedObjectsFromText
*
* @param string $sInput
* @param string $sFormat
* @param array $aExceptedMentionedObjects
*
* @throws \Exception
*/
public function testGetMentionedObjectsFromText(string $sInput, string $sFormat, array $aExceptedMentionedObjects)
{
$aTestedMentionedObjects = utils::GetMentionedObjectsFromText($sInput, $sFormat);
$sExpectedAsString = print_r($aExceptedMentionedObjects, true);
$sTestedAsString = print_r($aTestedMentionedObjects, true);
$this->assertEquals($sTestedAsString, $sExpectedAsString, "Found mentioned objects don't match. Got: $sTestedAsString, expected $sExpectedAsString");
}
/**
* @since 3.0.0
*/
public function GetMentionedObjectsFromTextProvider(): array
{
$sAbsUrlAppRoot = utils::GetAbsoluteUrlAppRoot();
return [
'No object' => [
"Begining
Second line
End",
utils::ENUM_TEXT_FORMAT_HTML,
[],
],
'1 UserRequest' => [
"Begining
Before link <a href=\"$sAbsUrlAppRoot/pages/UI.php&operation=details&class=UserRequest&id=12345&foo=bar\">R-012345</a> After link
End",
utils::ENUM_TEXT_FORMAT_HTML,
[
'UserRequest' => ['12345'],
],
],
'2 UserRequests' => [
"Begining
Before link <a href=\"$sAbsUrlAppRoot/pages/UI.php&operation=details&class=UserRequest&id=12345&foo=bar\">R-012345</a> After link
And <a href=\"$sAbsUrlAppRoot/pages/UI.php&operation=details&class=UserRequest&id=987654&foo=bar\">R-987654</a>
End",
utils::ENUM_TEXT_FORMAT_HTML,
[
'UserRequest' => ['12345', '987654'],
],
],
'1 UserRequest, 1 Person' => [
"Begining
Before link <a href=\"$sAbsUrlAppRoot/pages/UI.php&operation=details&class=UserRequest&id=12345&foo=bar\">R-012345</a> After link
And <a href=\"$sAbsUrlAppRoot/pages/UI.php&operation=details&class=Person&id=3&foo=bar\">Claude Monet</a>
End",
utils::ENUM_TEXT_FORMAT_HTML,
[
'UserRequest' => ['12345'],
'Person' => ['3'],
],
],
];
}
}