From: Christian Wach Date: Thu, 15 Apr 2021 00:24:09 +0000 (+0100) Subject: Fix PayPal IPN URL and WordPress URLs when Permalinks are set to "Plain" X-Git-Url: https://vcs.fsf.org/?a=commitdiff_plain;h=cd95cfbed228236bfab7d60f39986bb809db760a;p=civicrm-core.git Fix PayPal IPN URL and WordPress URLs when Permalinks are set to "Plain" --- diff --git a/CRM/Core/Payment.php b/CRM/Core/Payment.php index 433c68457c..4f76aff6ee 100644 --- a/CRM/Core/Payment.php +++ b/CRM/Core/Payment.php @@ -1275,7 +1275,7 @@ abstract class CRM_Core_Payment { * URL to notify outcome of transaction. */ protected function getNotifyUrl() { - $url = CRM_Utils_System::url( + $url = CRM_Utils_System::getNotifyUrl( 'civicrm/payment/ipn/' . $this->_paymentProcessor['id'], [], TRUE, diff --git a/CRM/Utils/System.php b/CRM/Utils/System.php index 4be0a95888..0b6dbb8768 100644 --- a/CRM/Utils/System.php +++ b/CRM/Utils/System.php @@ -281,6 +281,43 @@ class CRM_Utils_System { return $url; } + /** + * Return the Notification URL for Payments. + * + * @param string $path + * The path being linked to, such as "civicrm/add". + * @param array|string $query + * A query string to append to the link, or an array of key-value pairs. + * @param bool $absolute + * Whether to force the output to be an absolute link (beginning with a + * URI-scheme such as 'http:'). Useful for links that will be displayed + * outside the site, such as in an RSS feed. + * @param string $fragment + * A fragment identifier (named anchor) to append to the link. + * @param bool $htmlize + * Whether to encode special html characters such as &. + * @param bool $frontend + * This link should be to the CMS front end (applies to WP & Joomla). + * @param bool $forceBackend + * This link should be to the CMS back end (applies to WP & Joomla). + * + * @return string + * The Notification URL. + */ + public static function getNotifyUrl( + $path = NULL, + $query = NULL, + $absolute = FALSE, + $fragment = NULL, + $htmlize = TRUE, + $frontend = FALSE, + $forceBackend = FALSE + ) { + $config = CRM_Core_Config::singleton(); + $query = self::makeQueryString($query); + return $config->userSystem->getNotifyUrl($path, $query, $absolute, $fragment, $frontend, $forceBackend, $htmlize); + } + /** * Generates an extern url. * diff --git a/CRM/Utils/System/Base.php b/CRM/Utils/System/Base.php index fd5ff6432e..80ffa437e2 100644 --- a/CRM/Utils/System/Base.php +++ b/CRM/Utils/System/Base.php @@ -141,6 +141,45 @@ abstract class CRM_Utils_System_Base { return NULL; } + /** + * Return the Notification URL for Payments. + * + * The default is to pass the params through to `url()`. However the WordPress + * CMS class overrides this method because Notification URLs must always target + * the Base Page to avoid IPN failures when Forms are embedded in pages that + * require authentication. + * + * @param string $path + * The path being linked to, such as "civicrm/add". + * @param string $query + * A query string to append to the link. + * @param bool $absolute + * Whether to force the output to be an absolute link (beginning with http). + * Useful for links that will be displayed outside the site, such as in an RSS feed. + * @param string $fragment + * A fragment identifier (named anchor) to append to the link. + * @param bool $frontend + * This link should be to the CMS front end (applies to WP & Joomla). + * @param bool $forceBackend + * This link should be to the CMS back end (applies to WP & Joomla). + * @param bool $htmlize + * Whether to encode special html characters such as &. + * + * @return string + * The Notification URL. + */ + public function getNotifyUrl( + $path = NULL, + $query = NULL, + $absolute = FALSE, + $fragment = NULL, + $frontend = FALSE, + $forceBackend = FALSE, + $htmlize = TRUE + ) { + return $this->url($path, $query, $absolute, $fragment, $frontend, $forceBackend, $htmlize); + } + /** * Authenticate the user against the CMS db. * diff --git a/CRM/Utils/System/WordPress.php b/CRM/Utils/System/WordPress.php index 5d173234b2..fc243b0c4c 100644 --- a/CRM/Utils/System/WordPress.php +++ b/CRM/Utils/System/WordPress.php @@ -311,72 +311,63 @@ class CRM_Utils_System_WordPress extends CRM_Utils_System_Base { $config = CRM_Core_Config::singleton(); $script = ''; $separator = '&'; - $wpPageParam = ''; $fragment = isset($fragment) ? ('#' . $fragment) : ''; - $path = CRM_Utils_String::stripPathChars($path); $basepage = FALSE; - //this means wp function we are trying to use is not available, - //so load bootStrap - // FIXME: Why bootstrap in url()? Generally want to define 1-2 strategic places to put bootstrap + // FIXME: Why bootstrap in url()? + // Generally want to define 1-2 strategic places to put bootstrap. if (!function_exists('get_option')) { $this->loadBootStrap(); } + // When on the front-end. if ($config->userFrameworkFrontend) { + + // Try and find the "calling" page/post. global $post; - if (get_option('permalink_structure') != '') { - $script = $post ? get_permalink($post->ID) : ""; - } - if ($post && $config->wpBasePage == $post->post_name) { - $basepage = TRUE; - } - // when shortcode is included in page - // also make sure we have valid query object - // FIXME: $wpPageParam has no effect and is only set on the *basepage* - global $wp_query; - if (get_option('permalink_structure') == '' && method_exists($wp_query, 'get')) { - if (get_query_var('page_id')) { - $wpPageParam = "page_id=" . get_query_var('page_id'); - } - elseif (get_query_var('p')) { - // when shortcode is inserted in post - $wpPageParam = "p=" . get_query_var('p'); + if ($post) { + $script = get_permalink($post->ID); + if ($config->wpBasePage == $post->post_name) { + $basepage = TRUE; } } + } + else { - $base = $this->getBaseUrl($absolute, $frontend, $forceBackend); + // Get the Base Page URL for building front-end URLs. + if ($frontend && !$forceBackend) { + $script = $this->getBasePageUrl(); + $basepage = TRUE; + } - if (!isset($path) && !isset($query)) { - // FIXME: This short-circuited codepath is the same as the general one below, except - // in that it ignores "permlink_structure" / $wpPageParam / $script . I don't know - // why it's different (and I can only find two obvious use-cases for this codepath, - // of which at least one looks gratuitous). A more ambitious person would simply remove - // this code. - return $base . $fragment; } - if (!$forceBackend && get_option('permalink_structure') != '' && ($wpPageParam || $script != '')) { + // Get either the relative Base Page URL or the relative Admin Page URL. + $base = $this->getBaseUrl($absolute, $frontend, $forceBackend); + + // Overwrite base URL if we already have a front-end URL. + if (!$forceBackend && $script != '') { $base = $script; } $queryParts = []; + $admin_request = ((is_admin() && !$frontend) || $forceBackend); if ( - // not using clean URLs + // If not using Clean URLs. !$config->cleanURL - // requesting an admin URL - || ((is_admin() && !$frontend) || $forceBackend) - // is shortcode + // Or requesting an admin URL. + || $admin_request + // Or this is a Shortcode. || (!$basepage && $script != '') ) { - // pre-existing logic - if (isset($path)) { + // Build URL according to pre-existing logic. + if (!empty($path)) { // Admin URLs still need "page=CiviCRM", front-end URLs do not. - if ((is_admin() && !$frontend) || $forceBackend) { + if ($admin_request) { $queryParts[] = 'page=CiviCRM'; } else { @@ -384,23 +375,26 @@ class CRM_Utils_System_WordPress extends CRM_Utils_System_Base { } $queryParts[] = 'q=' . rawurlencode($path); } - if ($wpPageParam) { - $queryParts[] = $wpPageParam; - } if (!empty($query)) { $queryParts[] = $query; } - $final = $base . '?' . implode($separator, $queryParts) . $fragment; + // Append our query parts, taking Permlink Structure into account. + if (get_option('permalink_structure') == '' && !$admin_request) { + $final = $base . $separator . implode($separator, $queryParts) . $fragment; + } + else { + $final = $base . '?' . implode($separator, $queryParts) . $fragment; + } } else { - // clean URLs - if (isset($path)) { + // Build Clean URL. + if (!empty($path)) { $base = trailingslashit($base) . str_replace('civicrm/', '', $path) . '/'; } - if (isset($query)) { + if (!empty($query)) { $query = ltrim($query, '=?&'); $queryParts[] = $query; } @@ -418,19 +412,18 @@ class CRM_Utils_System_WordPress extends CRM_Utils_System_Base { } /** - * 27-09-2016 - * CRM-16421 CRM-17633 WIP Changes to support WP in it's own directory - * https://wiki.civicrm.org/confluence/display/CRM/WordPress+installed+in+its+own+directory+issues - * For now leave hard coded wp-admin references. - * TODO: remove wp-admin references and replace with admin_url() in the future. Look at best way to get path to admin_url + * Get either the relative Base Page URL or the relative Admin Page URL. * - * @param $absolute - * @param $frontend - * @param $forceBackend + * @param bool $absolute + * Whether to force the output to be an absolute link beginning with http(s). + * @param bool $frontend + * True if this link should be to the CMS front end. + * @param bool $forceBackend + * True if this link should be to the CMS back end. * * @return mixed|null|string */ - private function getBaseUrl($absolute, $frontend, $forceBackend) { + public function getBaseUrl($absolute, $frontend, $forceBackend) { $config = CRM_Core_Config::singleton(); if ((is_admin() && !$frontend) || $forceBackend) { return Civi::paths()->getUrl('[wp.backend]/.', $absolute ? 'absolute' : 'relative'); @@ -440,6 +433,111 @@ class CRM_Utils_System_WordPress extends CRM_Utils_System_Base { } } + /** + * Get the URL of the WordPress Base Page. + * + * @return string|bool + * The Base Page URL, or false on failure. + */ + public function getBasePageUrl() { + static $basepage_url = ''; + if ($basepage_url === '') { + + // Get the Base Page config setting. + $config = CRM_Core_Config::singleton(); + $basepage_slug = $config->wpBasePage; + + // Did we get a value? + if (!empty($basepage_slug)) { + + // Query for our Base Page. + $pages = get_posts([ + 'post_type' => 'page', + 'name' => strtolower($basepage_slug), + 'post_status' => 'publish', + 'posts_per_page' => 1, + ]); + + // Find the Base Page object and set the URL. + if (!empty($pages) && is_array($pages)) { + $basepage = array_pop($pages); + if ($basepage instanceof WP_Post) { + $basepage_url = get_permalink($basepage->ID); + } + } + + } + + } + + return $basepage_url; + } + + /** + * @inheritDoc + */ + public function getNotifyUrl( + $path = NULL, + $query = NULL, + $absolute = FALSE, + $fragment = NULL, + $frontend = FALSE, + $forceBackend = FALSE, + $htmlize = TRUE + ) { + $config = CRM_Core_Config::singleton(); + $separator = '&'; + $fragment = isset($fragment) ? ('#' . $fragment) : ''; + $path = CRM_Utils_String::stripPathChars($path); + $queryParts = []; + + // Get the Base Page URL. + $base = $this->getBasePageUrl(); + + // If not using Clean URLs. + if (!$config->cleanURL) { + + // Build URL according to pre-existing logic. + if (!empty($path)) { + $queryParts[] = 'civiwp=CiviCRM'; + $queryParts[] = 'q=' . rawurlencode($path); + } + if (!empty($query)) { + $queryParts[] = $query; + } + + // Append our query parts, taking Permlink Structure into account. + if (get_option('permalink_structure') == '') { + $final = $base . $separator . implode($separator, $queryParts) . $fragment; + } + else { + $final = $base . '?' . implode($separator, $queryParts) . $fragment; + } + + } + else { + + // Build Clean URL. + if (!empty($path)) { + $base = trailingslashit($base) . str_replace('civicrm/', '', $path) . '/'; + } + if (!empty($query)) { + $query = ltrim($query, '=?&'); + $queryParts[] = $query; + } + + if (!empty($queryParts)) { + $final = $base . '?' . implode($separator, $queryParts) . $fragment; + } + else { + $final = $base . $fragment; + } + + } + + return $final; + } + /** * @inheritDoc */