| 1 | <?php |
| 2 | |
| 3 | use Psr\Http\Message\ResponseInterface; |
| 4 | use Psr\Http\Message\RequestInterface; |
| 5 | |
| 6 | /** |
| 7 | * Additional helpers/utilities for use as Guzzle middleware. |
| 8 | */ |
| 9 | class CRM_Utils_GuzzleMiddleware { |
| 10 | |
| 11 | /** |
| 12 | * Add this as a Guzzle handler/middleware if you wish to simplify |
| 13 | * the construction of Civi-related URLs. It enables URL schemes for: |
| 14 | * |
| 15 | * - route://ROUTE_NAME (aka) route:ROUTE_NAME |
| 16 | * - backend://ROUTE_NAME (aka) backend:ROUTE_NAME |
| 17 | * - frontend://ROUTE_NAME (aka) frontend:ROUTE_NAME |
| 18 | * - var://PATH_EXPRESSION (aka) var:PATH_EXPRESSION |
| 19 | * - ext://EXTENSION/FILE (aka) ext:EXTENSION/FILE |
| 20 | * - assetBuilder://ASSET_NAME?PARAMS (aka) assetBuilder:ASSET_NAME?PARAMS |
| 21 | * |
| 22 | * Compare: |
| 23 | * |
| 24 | * $http->get(CRM_Utils_System::url('civicrm/dashboard', NULL, TRUE, NULL, FALSE, ??)) |
| 25 | * $http->get('route://civicrm/dashboard') |
| 26 | * $http->get('frontend://civicrm/dashboard') |
| 27 | * $http->get('backend://civicrm/dashboard') |
| 28 | * |
| 29 | * $http->get(Civi::paths()->getUrl('[civicrm.files]/foo.txt')) |
| 30 | * $http->get('var:[civicrm.files]/foo.txt') |
| 31 | * |
| 32 | * $http->get(Civi::resources()->getUrl('my.other.ext', 'foo.js')) |
| 33 | * $http->get('ext:my.other.ext/foo.js') |
| 34 | * |
| 35 | * $http->get(Civi::service('asset_builder')->getUrl('my-asset.css', ['a'=>1, 'b'=>2])) |
| 36 | * $http->get('assetBuilder:my-asset.css?a=1&b=2') |
| 37 | * |
| 38 | * Note: To further simplify URL expressions, Guzzle allows you to set a 'base_uri' |
| 39 | * option (which is applied as a prefix to any relative URLs). Consider using |
| 40 | * `base_uri=auto:`. This allows you to implicitly use the most common types |
| 41 | * (routes+variables): |
| 42 | * |
| 43 | * $http->get('civicrm/dashboard') |
| 44 | * $http->get('[civicrm.files]/foo.txt') |
| 45 | * |
| 46 | * @return \Closure |
| 47 | */ |
| 48 | public static function url() { |
| 49 | return function(callable $handler) { |
| 50 | return function (RequestInterface $request, array $options) use ($handler) { |
| 51 | $newUri = self::filterUri($request->getUri()); |
| 52 | if ($newUri !== NULL) { |
| 53 | $request = $request->withUri(\CRM_Utils_Url::parseUrl($newUri)); |
| 54 | } |
| 55 | |
| 56 | return $handler($request, $options); |
| 57 | }; |
| 58 | }; |
| 59 | } |
| 60 | |
| 61 | /** |
| 62 | * @param \Psr\Http\Message\UriInterface $oldUri |
| 63 | * |
| 64 | * @return string|null |
| 65 | * The string formation of the new URL, or NULL for unchanged URLs. |
| 66 | */ |
| 67 | protected static function filterUri(\Psr\Http\Message\UriInterface $oldUri) { |
| 68 | // Copy the old ?query-params and #fragment-params on top of $newBase. |
| 69 | $copyParams = function ($newBase) use ($oldUri) { |
| 70 | if ($oldUri->getQuery()) { |
| 71 | $newBase .= strpos($newBase, '?') !== FALSE ? '&' : '?'; |
| 72 | $newBase .= $oldUri->getQuery(); |
| 73 | } |
| 74 | if ($oldUri->getFragment()) { |
| 75 | $newBase .= '#' . $oldUri->getFragment(); |
| 76 | } |
| 77 | return $newBase; |
| 78 | }; |
| 79 | |
| 80 | $hostPath = urldecode($oldUri->getHost() . $oldUri->getPath()); |
| 81 | $scheme = $oldUri->getScheme(); |
| 82 | if ($scheme === 'auto') { |
| 83 | // Ex: 'auto:civicrm/my-page' ==> Router |
| 84 | // Ex: 'auto:[civicrm.root]/js/foo.js' ==> Resource file |
| 85 | $scheme = ($hostPath[0] === '[') ? 'var' : 'route'; |
| 86 | } |
| 87 | |
| 88 | if ($scheme === 'route') { |
| 89 | $menu = CRM_Core_Menu::get($hostPath); |
| 90 | $scheme = ($menu && !empty($menu['is_public'])) ? 'frontend' : 'backend'; |
| 91 | } |
| 92 | |
| 93 | switch ($scheme) { |
| 94 | case 'assetBuilder': |
| 95 | // Ex: 'assetBuilder:dynamic.css' or 'assetBuilder://dynamic.css?foo=bar' |
| 96 | // Note: It's more useful to pass params to the asset-builder than to the final HTTP request. |
| 97 | $assetParams = []; |
| 98 | parse_str('' . $oldUri->getQuery(), $assetParams); |
| 99 | return \Civi::service('asset_builder')->getUrl($hostPath, $assetParams); |
| 100 | |
| 101 | case 'ext': |
| 102 | // Ex: 'ext:other.ext.name/file.js' or 'ext://other.ext.name/file.js' |
| 103 | [$ext, $file] = explode('/', $hostPath, 2); |
| 104 | return $copyParams(\Civi::resources()->getUrl($ext, $file)); |
| 105 | |
| 106 | case 'var': |
| 107 | // Ex: 'var:[civicrm.files]/foo.txt' or 'var://[civicrm.files]/foo.txt' |
| 108 | return $copyParams(\Civi::paths()->getUrl($hostPath, 'absolute')); |
| 109 | |
| 110 | case 'backend': |
| 111 | // Ex: 'backend:civicrm/my-page' or 'backend://civicrm/my-page' |
| 112 | return $copyParams(\CRM_Utils_System::url($hostPath, NULL, TRUE, NULL, FALSE)); |
| 113 | |
| 114 | case 'frontend': |
| 115 | // Ex: 'frontend:civicrm/my-page' or 'frontend://civicrm/my-page' |
| 116 | return $copyParams(\CRM_Utils_System::url($hostPath, NULL, TRUE, NULL, FALSE, TRUE)); |
| 117 | |
| 118 | default: |
| 119 | return NULL; |
| 120 | } |
| 121 | } |
| 122 | |
| 123 | /** |
| 124 | * This logs the list of outgoing requests in curl format. |
| 125 | */ |
| 126 | public static function curlLog(\Psr\Log\LoggerInterface $logger) { |
| 127 | |
| 128 | $curlFmt = new class() extends \GuzzleHttp\MessageFormatter { |
| 129 | |
| 130 | public function format(RequestInterface $request, ResponseInterface $response = NULL, \Exception $error = NULL) { |
| 131 | $cmd = '$ curl'; |
| 132 | if ($request->getMethod() !== 'GET') { |
| 133 | $cmd .= ' -X ' . escapeshellarg($request->getMethod()); |
| 134 | } |
| 135 | foreach ($request->getHeaders() as $header => $lines) { |
| 136 | foreach ($lines as $line) { |
| 137 | $cmd .= ' -H ' . escapeshellarg("$header: $line"); |
| 138 | } |
| 139 | } |
| 140 | $body = (string) $request->getBody(); |
| 141 | if ($body !== '') { |
| 142 | $cmd .= ' -d ' . escapeshellarg($body); |
| 143 | } |
| 144 | $cmd .= ' ' . escapeshellarg((string) $request->getUri()); |
| 145 | return $cmd; |
| 146 | } |
| 147 | |
| 148 | }; |
| 149 | |
| 150 | return \GuzzleHttp\Middleware::log($logger, $curlFmt); |
| 151 | } |
| 152 | |
| 153 | } |