| 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 | |
| 18 | /** |
| 19 | * Redirects a user to the full url from a mailing url. |
| 20 | * |
| 21 | * General Usage: civicrm/mailing/url?qid={event_queue_id}&u={url_id} |
| 22 | * |
| 23 | * Additional arguments may be handled by extractPassthroughParameters(). |
| 24 | */ |
| 25 | class CRM_Mailing_Page_Url extends CRM_Core_Page { |
| 26 | |
| 27 | /** |
| 28 | * Redirect the user to the specified url. |
| 29 | * |
| 30 | * @throws \CRM_Core_Exception |
| 31 | */ |
| 32 | public function run() { |
| 33 | $queue_id = CRM_Utils_Request::retrieveValue('qid', 'Integer'); |
| 34 | $url_id = CRM_Utils_Request::retrieveValue('u', 'Integer', NULL, TRUE); |
| 35 | $url = CRM_Mailing_Event_BAO_TrackableURLOpen::track($queue_id, $url_id); |
| 36 | $query_string = $this->extractPassthroughParameters(); |
| 37 | |
| 38 | if (strlen($query_string) > 0) { |
| 39 | // Parse the url to preserve the fragment. |
| 40 | $pieces = parse_url($url); |
| 41 | |
| 42 | if (isset($pieces['fragment'])) { |
| 43 | $url = str_replace('#' . $pieces['fragment'], '', $url); |
| 44 | } |
| 45 | |
| 46 | // Handle additional query string params. |
| 47 | if ($query_string) { |
| 48 | if (stristr($url, '?')) { |
| 49 | $url .= '&' . $query_string; |
| 50 | } |
| 51 | else { |
| 52 | $url .= '?' . $query_string; |
| 53 | } |
| 54 | } |
| 55 | |
| 56 | // slap the fragment onto the end per URL spec |
| 57 | if (isset($pieces['fragment'])) { |
| 58 | $url .= '#' . $pieces['fragment']; |
| 59 | } |
| 60 | } |
| 61 | CRM_Utils_System::redirect($url, [ |
| 62 | 'for' => 'civicrm/mailing/url', |
| 63 | 'queue_id' => $queue_id, |
| 64 | 'url_id' => $url_id, |
| 65 | ]); |
| 66 | } |
| 67 | |
| 68 | /** |
| 69 | * Determine if this request has any valid pass-through parameters. |
| 70 | * |
| 71 | * Under CRM-7103 (v3.3), all unrecognized query-parameters (besides qid/u) are passed |
| 72 | * through as part of the redirect. This mechanism is relevant to certain |
| 73 | * customizations (eg using `hook_alterMailParams` to append extra URL args) |
| 74 | * but does not matter for normal URLs. |
| 75 | * |
| 76 | * The functionality seems vaguely problematic (IMHO) - especially now that |
| 77 | * 'extern/url.php' is moving into the CMS/Civi router ('civicrm/mailing/url'). |
| 78 | * But it's the current protocol. |
| 79 | * |
| 80 | * A better design might be to support `hook_alterRedirect` in the CiviMail |
| 81 | * click-through tracking. Then you don't have to take any untrusted inputs |
| 82 | * and you can fix URL mistakes in realtime. |
| 83 | * |
| 84 | * @return string |
| 85 | * @link https://issues.civicrm.org/jira/browse/CRM-7103 |
| 86 | */ |
| 87 | protected function extractPassthroughParameters():string { |
| 88 | $config = CRM_Core_Config::singleton(); |
| 89 | |
| 90 | $query_param = $_GET; |
| 91 | unset($query_param['qid']); |
| 92 | unset($query_param['u']); |
| 93 | unset($query_param[$config->userFrameworkURLVar]); |
| 94 | if ($config->userFramework === 'WordPress') { |
| 95 | // Ugh |
| 96 | unset($query_param['page']); |
| 97 | unset($query_param['noheader']); |
| 98 | } |
| 99 | |
| 100 | $query_string = http_build_query($query_param); |
| 101 | return $query_string; |
| 102 | } |
| 103 | |
| 104 | } |