*/
class CRM_Activity_Form_ActivityViewTest extends CiviUnitTestCase {
+ /**
+ * @var int
+ */
+ protected $source_contact_id;
+
+ /**
+ * @var int
+ */
+ protected $target_contact_id;
+
+ /**
+ * @var int
+ */
+ protected $mailing_id;
+
+ /**
+ * @var int
+ */
+ protected $case_id;
+
+ public function setUp(): void {
+ parent::setUp();
+ $this->source_contact_id = $this->createLoggedInUser();
+ $this->target_contact_id = $this->individualCreate([], 0, TRUE);
+ $this->mailing_id = $this->callAPISuccess('Mailing', 'create', [
+ 'subject' => 'A Mailing Subject',
+ // We need the newlines to be early since this text gets truncated
+ 'body_text' => "This\naddress\n\nis not ours: {domain.address}.\n\nDo not click this link {action.optOutUrl}.",
+ 'name' => 'amailing',
+ 'created_id' => $this->source_contact_id,
+ ]);
+ CRM_Core_BAO_ConfigSetting::enableComponent('CiviCase');
+ $this->case_id = $this->createCase($this->target_contact_id, $this->source_contact_id)->id;
+ }
+
/**
* Cleanup after class.
*
*/
public function tearDown(): void {
$tablesToTruncate = [
+ 'civicrm_case_activity',
+ 'civicrm_case_contact',
'civicrm_activity',
'civicrm_activity_contact',
+ 'civicrm_case',
+ 'civicrm_contact',
];
$this->quickCleanup($tablesToTruncate);
+ CRM_Core_BAO_ConfigSetting::disableComponent('CiviCase');
parent::tearDown();
}
$this->assertEquals($expected, $templateVar);
}
+ /**
+ * Test that the text is neither squished nor double-spaced.
+ * @dataProvider activityTypesProvider
+ * @dataProvider caseActivityTypesProvider
+ * @param array $input
+ * @param string $expected
+ */
+ public function testNewlinesLookRight(array $input, string $expected) {
+ if (!empty($input['skip'])) {
+ $this->markTestSkipped('This test is boring.');
+ return;
+ }
+
+ $activity = $this->activityCreate([
+ 'source_contact_id' => $this->source_contact_id,
+ 'target_contact_id' => $this->target_contact_id,
+ 'details' => $input['details'],
+ 'activity_type_id' => $input['activity_type'],
+ 'source_record_id' => ($input['activity_type'] === 'Bulk Email' ? $this->mailing_id : NULL),
+ 'case_id' => (strpos($input['url'], 'caseid') === FALSE ? NULL : $this->case_id),
+ ]);
+
+ // We have to replace these at runtime because dataproviders are
+ // evaluated even before setUp() runs.
+ $input['url'] = str_replace('%%id%%', $activity['id'], $input['url']);
+ $input['url'] = str_replace('%%aid%%', $activity['id'], $input['url']);
+ $input['url'] = str_replace('%%cid%%', $this->source_contact_id, $input['url']);
+ $input['url'] = str_replace('%%atype%%', CRM_Core_PseudoConstant::getKey('CRM_Activity_BAO_Activity', 'activity_type_id', $input['activity_type']), $input['url']);
+ $input['url'] = str_replace('%%caseid%%', $this->case_id, $input['url']);
+
+ $this->setRequestVars($input['url']);
+
+ $item = CRM_Core_Invoke::getItem([$_GET['q']]);
+ ob_start();
+ CRM_Core_Invoke::runItem($item);
+ $contents = ob_get_clean();
+
+ $this->unsetRequestVars($input['url']);
+
+ // See note in activityTypesProvider why we do this
+ $contents = str_replace(["\r", "\n"], '', $contents);
+
+ $this->assertStringContainsString($expected, $contents);
+ }
+
+ /**
+ * data provider for testNewlinesLookRight() for non-case activities
+ *
+ * Unfortunately there seem to be differences in the output of `purify` on
+ * unix vs windows, and also on unix it seems to even work differently in
+ * tests than when run normally! So we strip all chr(10) and chr(13) when
+ * comparing the result.
+ *
+ * @return array
+ */
+ public function activityTypesProvider(): array {
+ $data = [
+ 'meeting-text' => [
+ [
+ 'activity_type' => 'Meeting',
+ 'url' => 'civicrm/activity?atype=%%atype%%&action=view&reset=1&id=%%id%%&cid=%%cid%%&context=activity',
+ 'details' => <<<'ENDDETAILS'
+This is text only
+and has two consecutive lines.
+
+And one blank line.
+ENDDETAILS
+ ],
+ '<span class="crm-frozen-field">This is text only<br />and has two consecutive lines.<br /><br />And one blank line.</span>',
+ ],
+
+ 'bulkemail-text' => [
+ [
+ 'activity_type' => 'Bulk Email',
+ 'url' => 'civicrm/activity/view?atype=%%atype%%&action=view&reset=1&id=%%id%%&cid=%%cid%%&context=activity',
+ 'details' => <<<'ENDDETAILS'
+This is text only
+and has two consecutive lines.
+
+And one blank line.
+ENDDETAILS
+ ],
+ // Note this is the summary of the actual bulk mailing text. The above details are a bit irrelevant for what we're testing here.
+ '<td class="label nowrap">Text Message</td><td>This<br />address<br /><br />is not ours:...<br />',
+ ],
+
+ 'email-text' => [
+ [
+ 'activity_type' => 'Email',
+ 'url' => 'civicrm/activity/view?atype=%%atype%%&action=view&reset=1&id=%%id%%&cid=%%cid%%&context=activity',
+ 'details' => <<<'ENDDETAILS'
+This is text only
+and has two consecutive lines.
+
+And one blank line.
+ENDDETAILS
+ ],
+ '<td class="view-value report"> This is text only<br />and has two consecutive lines.<br /><br />And one blank line.',
+ ],
+
+ 'inbound-text' => [
+ [
+ 'activity_type' => 'Inbound Email',
+ 'url' => 'civicrm/contact/view/activity?atype=%%atype%%&action=view&reset=1&id=%%id%%&cid=%%cid%%&context=activity',
+ 'details' => <<<'ENDDETAILS'
+This is text only
+and has two consecutive lines.
+
+And one blank line.
+ENDDETAILS
+ ],
+ '<span class="crm-frozen-field">This is text only<br />and has two consecutive lines.<br /><br />And one blank line.</span>',
+ ],
+
+ // Now html only
+ 'meeting-html' => [
+ [
+ 'activity_type' => 'Meeting',
+ 'url' => 'civicrm/activity?atype=%%atype%%&action=view&reset=1&id=%%id%%&cid=%%cid%%&context=activity',
+ 'details' => <<<'ENDDETAILS'
+<p>This is html only</p>
+
+<p>And it usually looks like this.</p>
+
+<p>With p's and newlines between the p's.</p>
+ENDDETAILS
+ ],
+ '<span class="crm-frozen-field"><p>This is html only</p><p>And it usually looks like this.</p><p>With p\'s and newlines between the p\'s.</p></span>',
+ ],
+
+ 'bulkemail-html' => [
+ [
+ 'activity_type' => 'Bulk Email',
+ 'url' => 'civicrm/activity/view?atype=%%atype%%&action=view&reset=1&id=%%id%%&cid=%%cid%%&context=activity',
+ 'details' => <<<'ENDDETAILS'
+<p>This is html only</p>
+
+<p>And it usually looks like this.</p>
+
+<p>With p's and newlines between the p's.</p>
+ENDDETAILS
+ ],
+ // Note this is the summary of the actual bulk mailing text. The above details are a bit irrelevant for what we're testing here.
+ '<td class="label nowrap">Text Message</td><td>This<br />address<br /><br />is not ours:...<br />',
+ ],
+
+ 'email-html' => [
+ [
+ 'activity_type' => 'Email',
+ 'url' => 'civicrm/activity/view?atype=%%atype%%&action=view&reset=1&id=%%id%%&cid=%%cid%%&context=activity',
+ 'details' => <<<'ENDDETAILS'
+<p>This is html only</p>
+
+<p>And it usually looks like this.</p>
+
+<p>With p's and newlines between the p's.</p>
+ENDDETAILS
+ ],
+ '<td class="view-value report"> <p>This is html only</p><p>And it usually looks like this.</p><p>With p\'s and newlines between the p\'s.</p>',
+ ],
+
+ 'inbound-html' => [
+ [
+ 'activity_type' => 'Inbound Email',
+ 'url' => 'civicrm/contact/view/activity?atype=%%atype%%&action=view&reset=1&id=%%id%%&cid=%%cid%%&context=activity',
+ // This is probably unusual in real life. It would almost always be mixed. But there's nothing to stop custom code from creating these.
+ 'details' => <<<'ENDDETAILS'
+<p>This is html only</p>
+
+<p>And it usually looks like this.</p>
+
+<p>With p's and newlines between the p's.</p>
+ENDDETAILS
+ ],
+ '<span class="crm-frozen-field"><p>This is html only</p><p>And it usually looks like this.</p><p>With p\'s and newlines between the p\'s.</p></span>',
+ ],
+
+ // Now mixed with text first
+ 'meeting-mixed-text' => [
+ [
+ 'activity_type' => 'Meeting',
+ 'url' => 'civicrm/activity?atype=%%atype%%&action=view&reset=1&id=%%id%%&cid=%%cid%%&context=activity',
+ 'details' => <<<'ENDDETAILS'
+-ALTERNATIVE ITEM 0-
+This is mixed
+and has two consecutive lines.
+
+And one blank line.
+-ALTERNATIVE ITEM 1-
+<p>This is mixed<br />
+and has two consecutive lines.</p>
+
+<p>And one blank line.</p>
+-ALTERNATIVE END-
+ENDDETAILS
+ ],
+ 'This is mixed<br />and has two consecutive lines.<br /><br />And one blank line.',
+ ],
+
+ 'bulkemail-mixed-text' => [
+ [
+ 'activity_type' => 'Bulk Email',
+ 'url' => 'civicrm/activity/view?atype=%%atype%%&action=view&reset=1&id=%%id%%&cid=%%cid%%&context=activity',
+ 'details' => <<<'ENDDETAILS'
+-ALTERNATIVE ITEM 0-
+This is mixed
+and has two consecutive lines.
+
+And one blank line.
+-ALTERNATIVE ITEM 1-
+<p>This is mixed<br />
+and has two consecutive lines.</p>
+
+<p>And one blank line.</p>
+-ALTERNATIVE END-
+ENDDETAILS
+ ],
+ // Note this is the summary of the actual bulk mailing text. The above details are a bit irrelevant for what we're testing here.
+ '<td class="label nowrap">Text Message</td><td>This<br />address<br /><br />is not ours:...<br />',
+ ],
+
+ 'email-mixed-text' => [
+ [
+ 'activity_type' => 'Email',
+ 'url' => 'civicrm/activity/view?atype=%%atype%%&action=view&reset=1&id=%%id%%&cid=%%cid%%&context=activity',
+ 'details' => <<<'ENDDETAILS'
+-ALTERNATIVE ITEM 0-
+This is mixed
+and has two consecutive lines.
+
+And one blank line.
+-ALTERNATIVE ITEM 1-
+<p>This is mixed<br />
+and has two consecutive lines.</p>
+
+<p>And one blank line.</p>
+-ALTERNATIVE END-
+ENDDETAILS
+ ],
+ '<td class="view-value report"> <br />This is mixed<br />and has two consecutive lines.<br /><br />And one blank line.<br />',
+ ],
+
+ 'inbound-mixed-text' => [
+ [
+ 'activity_type' => 'Inbound Email',
+ 'url' => 'civicrm/contact/view/activity?atype=%%atype%%&action=view&reset=1&id=%%id%%&cid=%%cid%%&context=activity',
+ 'details' => <<<'ENDDETAILS'
+-ALTERNATIVE ITEM 0-
+This is mixed
+and has two consecutive lines.
+
+And one blank line.
+-ALTERNATIVE ITEM 1-
+<p>This is mixed<br />
+and has two consecutive lines.</p>
+
+<p>And one blank line.</p>
+-ALTERNATIVE END-
+ENDDETAILS
+ ],
+ '<td class="view-value"> <br />This is mixed<br />and has two consecutive lines.<br /><br />And one blank line.<br />',
+ ],
+
+ // Now mixed with html first
+ 'meeting-mixed-html' => [
+ [
+ 'activity_type' => 'Meeting',
+ 'url' => 'civicrm/activity?atype=%%atype%%&action=view&reset=1&id=%%id%%&cid=%%cid%%&context=activity',
+ 'details' => <<<'ENDDETAILS'
+-ALTERNATIVE ITEM 0-
+<p>This is mixed<br />
+and has two consecutive lines.</p>
+
+<p>And one blank line.</p>
+-ALTERNATIVE ITEM 1-
+This is mixed
+and has two consecutive lines.
+
+And one blank line.
+-ALTERNATIVE END-
+ENDDETAILS
+ ],
+ '<td class="view-value"> <p>This is mixed<br />and has two consecutive lines.</p><p>And one blank line.</p>',
+ ],
+
+ 'bulkemail-mixed-html' => [
+ [
+ 'activity_type' => 'Bulk Email',
+ 'url' => 'civicrm/activity/view?atype=%%atype%%&action=view&reset=1&id=%%id%%&cid=%%cid%%&context=activity',
+ 'details' => <<<'ENDDETAILS'
+-ALTERNATIVE ITEM 0-
+<p>This is mixed<br />
+and has two consecutive lines.</p>
+
+<p>And one blank line.</p>
+-ALTERNATIVE ITEM 1-
+This is mixed
+and has two consecutive lines.
+
+And one blank line.
+-ALTERNATIVE END-
+ENDDETAILS
+ ],
+ // Note this is the summary of the actual bulk mailing text. The above details are a bit irrelevant for what we're testing here.
+ '<td class="label nowrap">Text Message</td><td>This<br />address<br /><br />is not ours:...<br />',
+ ],
+
+ 'email-mixed-html' => [
+ [
+ 'activity_type' => 'Email',
+ 'url' => 'civicrm/activity/view?atype=%%atype%%&action=view&reset=1&id=%%id%%&cid=%%cid%%&context=activity',
+ 'details' => <<<'ENDDETAILS'
+-ALTERNATIVE ITEM 0-
+<p>This is mixed<br />
+and has two consecutive lines.</p>
+
+<p>And one blank line.</p>
+-ALTERNATIVE ITEM 1-
+This is mixed
+and has two consecutive lines.
+
+And one blank line.
+-ALTERNATIVE END-
+ENDDETAILS
+ ],
+ '<p>This is mixed<br />and has two consecutive lines.</p><p>And one blank line.</p>',
+ ],
+
+ 'inbound-mixed-html' => [
+ [
+ 'activity_type' => 'Inbound Email',
+ 'url' => 'civicrm/contact/view/activity?atype=%%atype%%&action=view&reset=1&id=%%id%%&cid=%%cid%%&context=activity',
+ 'details' => <<<'ENDDETAILS'
+-ALTERNATIVE ITEM 0-
+<p>This is mixed<br />
+and has two consecutive lines.</p>
+
+<p>And one blank line.</p>
+-ALTERNATIVE ITEM 1-
+This is mixed
+and has two consecutive lines.
+
+And one blank line.
+-ALTERNATIVE END-
+ENDDETAILS
+ ],
+ '<p>This is mixed<br />and has two consecutive lines.</p><p>And one blank line.</p>',
+ ],
+ ];
+
+ // The output of these is wrong, but they are very rare and it has always
+ // been wrong, so just skip these tests. But we want them to be present in
+ // the array because they are used in caseActivityTypesProvider correctly.
+ $data['meeting-text'][0]['skip'] = TRUE;
+ $data['meeting-mixed-text'][0]['skip'] = TRUE;
+ return $data;
+ }
+
+ /**
+ * data provider for testNewlinesLookRight() for case activities
+ * @return array
+ */
+ public function caseActivityTypesProvider(): array {
+ // We want the same set as non-case, but the url is different, and expected results might change.
+ $data = $this->activityTypesProvider();
+ $newData = [];
+ foreach ($data as $key => $value) {
+ $newData['case-' . $key] = $data[$key];
+ // The url is always the same for case, despite there being places in
+ // civi that give a non-case link to the activity, but those are bugs
+ // IMO since can lead to data loss.
+ $newData['case-' . $key][0]['url'] = 'civicrm/case/activity/view?reset=1&aid=%%id%%&cid=%%cid%%&caseid=%%caseid%%';
+ }
+ $newData['case-meeting-text'][0]['skip'] = FALSE;
+ $newData['case-meeting-text'][1] = 'This is text only<br />and has two consecutive lines.<br /><br />And one blank line.';
+ $newData['case-bulkemail-text'][1] = 'This is text only<br />and has two consecutive lines.<br /><br />And one blank line.';
+ $newData['case-email-text'][1] = 'This is text only<br />and has two consecutive lines.<br /><br />And one blank line.';
+ $newData['case-inbound-text'][1] = 'This is text only<br />and has two consecutive lines.<br /><br />And one blank line.';
+
+ $newData['case-meeting-html'][1] = "<p>This is html only</p><p>And it usually looks like this.</p><p>With p's and newlines between the p's.</p>";
+ $newData['case-bulkemail-html'][1] = "<p>This is html only</p><p>And it usually looks like this.</p><p>With p's and newlines between the p's.</p>";
+ $newData['case-email-html'][1] = "<p>This is html only</p><p>And it usually looks like this.</p><p>With p's and newlines between the p's.</p>";
+ $newData['case-inbound-html'][1] = "<p>This is html only</p><p>And it usually looks like this.</p><p>With p's and newlines between the p's.</p>";
+
+ $newData['case-meeting-mixed-text'][0]['skip'] = FALSE;
+ $newData['case-meeting-mixed-text'][1] = 'This is mixed<br />and has two consecutive lines.<br /><br />And one blank line.';
+ $newData['case-bulkemail-mixed-text'][1] = 'This is mixed<br />and has two consecutive lines.<br /><br />And one blank line.';
+ $newData['case-email-mixed-text'][1] = 'This is mixed<br />and has two consecutive lines.<br /><br />And one blank line.';
+ $newData['case-inbound-mixed-text'][1] = 'This is mixed<br />and has two consecutive lines.<br /><br />And one blank line.';
+
+ $newData['case-meeting-mixed-html'][1] = '<p>This is mixed<br />and has two consecutive lines.</p><p>And one blank line.</p>';
+ $newData['case-bulkemail-mixed-html'][1] = '<p>This is mixed<br />and has two consecutive lines.</p><p>And one blank line.</p>';
+ $newData['case-email-mixed-html'][1] = '<p>This is mixed<br />and has two consecutive lines.</p><p>And one blank line.</p>';
+ $newData['case-inbound-mixed-html'][1] = '<p>This is mixed<br />and has two consecutive lines.</p><p>And one blank line.</p>';
+
+ return $newData;
+ }
+
+ /**
+ * Invoke and preProcess often need these set.
+ * @param string $url
+ */
+ private function setRequestVars(string $url): void {
+ $_SERVER['REQUEST_URI'] = $url;
+ $urlParts = explode('?', $url);
+ $_GET['q'] = $urlParts[0];
+
+ $parsed = [];
+ parse_str($urlParts[1], $parsed);
+ foreach ($parsed as $param => $value) {
+ $_GET[$param] = $value;
+ $_REQUEST[$param] = $value;
+ }
+ }
+
+ /**
+ * @param string $url
+ */
+ private function unsetRequestVars(string $url): void {
+ unset($_GET['q']);
+ $urlParts = explode('?', $url);
+ $parsed = [];
+ parse_str($urlParts[1], $parsed);
+ foreach ($parsed as $param => $value) {
+ unset($_GET[$param], $_REQUEST[$param]);
+ }
+ }
+
}