Merge pull request #22540 from civicrm/5.46
[civicrm-core.git] / CRM / Mailing / BAO / TrackableURL.php
1 <?php
2 /*
3 +--------------------------------------------------------------------+
4 | Copyright CiviCRM LLC. All rights reserved. |
5 | |
6 | This work is published under the GNU AGPLv3 license with some |
7 | permitted exceptions and without any warranty. For full license |
8 | and copyright information, see https://civicrm.org/licensing |
9 +--------------------------------------------------------------------+
10 */
11
12 /**
13 *
14 * @package CRM
15 * @copyright CiviCRM LLC https://civicrm.org/licensing
16 */
17 class CRM_Mailing_BAO_TrackableURL extends CRM_Mailing_DAO_TrackableURL {
18
19 /**
20 * Given a url, mailing id and queue event id, find or construct a
21 * trackable url and redirect url.
22 *
23 * @param string $url
24 * The target url to track.
25 * @param int $mailing_id
26 * The id of the mailing.
27 * @param int $queue_id
28 * The queue event id (contact clicking through).
29 *
30 * @return string
31 * The redirect/tracking url
32 */
33 public static function getTrackerURL($url, $mailing_id, $queue_id) {
34 if (strpos($url, '{') !== FALSE) {
35 return self::getTrackerURLForUrlWithTokens($url, $mailing_id, $queue_id);
36 }
37 else {
38 return self::getBasicTrackerURL($url, $mailing_id, $queue_id);
39 }
40 }
41
42 private static function getBasicTrackerURL($url, $mailing_id, $queue_id) {
43 static $urlCache = [];
44
45 if (array_key_exists($mailing_id . $url, $urlCache)) {
46 return $urlCache[$mailing_id . $url] . "&qid=$queue_id";
47 }
48
49 // hack for basic CRM-1014 and CRM-1151 and CRM-3492 compliance:
50 // let's not replace possible image URLs, CiviMail URLs or internal anchor URLs
51 if (preg_match('/\.(png|jpg|jpeg|gif|css)[\'"]?$/i', $url)
52 or substr_count($url, 'civicrm/extern/')
53 or substr_count($url, 'civicrm/mailing/')
54 or ($url[0] === '#')
55 ) {
56 // let's not cache these, so they don't get &qid= appended to them
57 return $url;
58 }
59 else {
60
61 $hrefExists = FALSE;
62
63 $tracker = new CRM_Mailing_BAO_TrackableURL();
64 if (preg_match('/^href/i', $url)) {
65 $url = preg_replace('/^href[ ]*=[ ]*[\'"](.*?)[\'"]$/i', '$1', $url);
66 $hrefExists = TRUE;
67 }
68
69 $tracker->url = $url;
70 $tracker->mailing_id = $mailing_id;
71
72 if (!$tracker->find(TRUE)) {
73 $tracker->save();
74 }
75 $id = $tracker->id;
76
77 $redirect = CRM_Utils_System::externUrl('extern/url', "u=$id");
78 $urlCache[$mailing_id . $url] = $redirect;
79 }
80
81 // This looks silly - calling the hook twice. This smells like an accident. Restoring old cache-based lookup.
82 // $returnUrl = CRM_Utils_System::externUrl('extern/url', "u=$id&qid=$queue_id");
83 $returnUrl = "{$urlCache[$mailing_id . $url]}&qid={$queue_id}";
84
85 if ($hrefExists) {
86 $returnUrl = "href='{$returnUrl}' rel='nofollow'";
87 }
88
89 return $returnUrl;
90 }
91
92 /**
93 * Create a trackable URL for a URL with tokens.
94 *
95 * @param string $url
96 * @param int $mailing_id
97 * @param int|string $queue_id
98 *
99 * @return string
100 */
101 private static function getTrackerURLForUrlWithTokens($url, $mailing_id, $queue_id) {
102
103 // Parse the URL.
104 // (not using parse_url because it's messy to reassemble)
105 if (!preg_match('/^([^?#]+)([?][^#]*)?(#.*)?$/', $url, $parsed)) {
106 // Failed to parse it, give up and don't track it.
107 return $url;
108 }
109
110 // If we have a token in the URL + path section, we can't tokenise.
111 if (strpos($parsed[1], '{') !== FALSE) {
112 return $url;
113 }
114
115 $trackable_url = $parsed[1];
116
117 // Process the query parameters, if there are any.
118 $tokenised_params = [];
119 $static_params = [];
120 if (!empty($parsed[2])) {
121 $query_key_value_pairs = explode('&', substr($parsed[2], 1));
122
123 // Separate the tokenised from the static parts.
124 foreach ($query_key_value_pairs as $_) {
125 if (strpos($_, '{') === FALSE) {
126 $static_params[] = $_;
127 }
128 else {
129 $tokenised_params[] = $_;
130 }
131 }
132 // Add the static params to the trackable part.
133 if ($static_params) {
134 $trackable_url .= '?' . implode('&', $static_params);
135 }
136 }
137
138 // Get trackable URL.
139 $data = self::getBasicTrackerURL($trackable_url, $mailing_id, $queue_id);
140
141 // Append the tokenised bits and the fragment.
142 if ($tokenised_params) {
143 // We know the URL will already have the '?'
144 $data .= '&' . implode('&', $tokenised_params);
145 }
146 if (!empty($parsed[3])) {
147 $data .= $parsed[3];
148 }
149 return $data;
150 }
151
152 /**
153 * @param $url
154 * @param $mailing_id
155 *
156 * @return int
157 * Url id of the given url and mail
158 */
159 public static function getTrackerURLId($url, $mailing_id) {
160 $tracker = new CRM_Mailing_BAO_TrackableURL();
161 $tracker->url = $url;
162 $tracker->mailing_id = $mailing_id;
163 if ($tracker->find(TRUE)) {
164 return $tracker->id;
165 }
166
167 return NULL;
168 }
169
170 }