Merge pull request #19714 from civicrm/5.35
[civicrm-core.git] / CRM / Utils / GuzzleMiddleware.php
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 }