Add --dry-run option for getting curl commands
[shpub.git] / src / shpub / Request.php
1 <?php
2 namespace shpub;
3
4 class Request
5 {
6     public $req;
7     public $cfg;
8     public $host;
9
10     protected $sendAsJson = false;
11     protected $directUpload = false;
12     protected $uploadsInfo = [];
13     protected $dedicatedBody = false;
14
15     protected $properties = [];
16     protected $type = null;
17     protected $action = null;
18     protected $url = null;
19
20     public function __construct($host, $cfg)
21     {
22         $this->cfg  = $cfg;
23         $this->host = $host;
24         $this->req = $this->getHttpRequest(
25             $this->host->endpoints->micropub, $this->host->token
26         );
27     }
28
29     protected function getHttpRequest($url, $accessToken)
30     {
31         $req = new MyHttpRequest2($url, 'POST');
32         $req->setHeader('User-Agent: shpub');
33         if (version_compare(PHP_VERSION, '5.6.0', '<')) {
34             //correct ssl validation on php 5.5 is a pain, so disable
35             $req->setConfig('ssl_verify_host', false);
36             $req->setConfig('ssl_verify_peer', false);
37         }
38         $req->setHeader('Content-type', 'application/x-www-form-urlencoded');
39         $req->setHeader('authorization', 'Bearer ' . $accessToken);
40         return $req;
41     }
42
43     public function send($body = null)
44     {
45         if ($this->sendAsJson) {
46             //application/json
47             if ($body !== null) {
48                 throw new \Exception('body already defined');
49             }
50             $this->req->setHeader('Content-Type: application/json');
51             $data = [];
52             if ($this->action !== null) {
53                 $data['action'] = $this->action;
54             }
55             if ($this->url !== null) {
56                 $data['url'] = $this->url;
57             }
58             if ($this->type !== null) {
59                 $data['type'] = array('h-' . $this->type);
60             }
61             if (count($this->properties)) {
62                 $data['properties'] = $this->properties;
63             }
64             $body = json_encode($data);
65         } else {
66             //form-encoded
67             if ($this->type !== null) {
68                 $this->req->addPostParameter('h', $this->type);
69             }
70             if ($this->action !== null) {
71                 $this->req->addPostParameter('action', $this->action);
72             }
73             if ($this->url !== null) {
74                 $this->req->addPostParameter('url', $this->url);
75             }
76             foreach ($this->properties as $propkey => $propval) {
77                 if (isset($propval['html'])) {
78                     //workaround for content[html]
79                     $propkey = $propkey . '[html]';
80                     $propval = $propval['html'];
81                 } else if (count($propval) == 1) {
82                     $propval = reset($propval);
83                 }
84                 $this->req->addPostParameter($propkey, $propval);
85             }
86         }
87
88         if ($body !== null) {
89             $this->dedicatedBody = true;
90             $this->req->setBody($body);
91         }
92         if ($this->cfg->debug) {
93             $cp = new CurlPrinter();
94             $cp->show($this->req, $this->uploadsInfo, $this->dedicatedBody);
95         }
96
97         if ($this->cfg->dryRun && $this->req->getMethod() != 'GET') {
98             //do not run any modifying queries
99             //fake a successful response
100             $res = new \HTTP_Request2_Response('HTTP/1.1 200 OK', false);
101             $res->parseHeaderLine('Content-type: text/plain');
102             $res->appendBody('Fake --dry-run response');
103             return $res;
104         }
105
106         $res = $this->req->send();
107
108         if (intval($res->getStatus() / 100) != 2) {
109             $this->displayErrorResponse($res);
110         }
111         return $res;
112     }
113
114     protected function displayErrorResponse($res)
115     {
116         Log::err(
117             'Server returned an error status code ' . $res->getStatus()
118         );
119
120         $shown = false;
121         if (Util::getMimeType($res) == 'application/json') {
122             $errData = json_decode($res->getBody());
123             if (!isset($errData->error)) {
124                 Log::err('Error response does not contain "error" property');
125             } else if (isset($errData->error_description)) {
126                 Log::err($errData->error . ': ' . $errData->error_description);
127                 $shown = true;
128             } else {
129                 Log::err($errData->error);
130                 $shown = true;
131             }
132         }
133
134         if (!$shown) {
135             Log::err($res->getBody());
136         }
137         exit(11);
138     }
139
140     public function setAction($action)
141     {
142         $this->action = $action;
143     }
144
145     public function setType($type)
146     {
147         $this->type = $type;
148     }
149
150     public function setUrl($url)
151     {
152         $this->url = $url;
153     }
154
155     /**
156      * Add file upload
157      *
158      * @param string $fieldName name of file-upload field
159      * @param array  $fileNames list of local file paths
160      *
161      * @return void
162      */
163     public function addUpload($fieldName, $fileNames)
164     {
165         if ($this->directUpload && $this->sendAsJson) {
166             throw new \Exception(
167                 'Cannot do direct upload with JSON requests'
168             );
169         }
170
171         if ($this->host->endpoints->media === null
172             || $this->directUpload
173         ) {
174             if ($this->sendAsJson) {
175                 throw new \Exception(
176                     'No media endpoint available, which is required for JSON'
177                 );
178             }
179             if (count($fileNames) == 1) {
180                 $fileNames = reset($fileNames);
181             }
182             $this->uploadsInfo[$fieldName] = $fileNames;
183             return $this->req->addUpload($fieldName, $fileNames);
184         }
185
186         $urls = [];
187         foreach ($fileNames as $fileName) {
188             $urls[] = $this->uploadToMediaEndpoint($fileName);
189         }
190         if (count($urls) == 1) {
191             $urls = reset($urls);
192         }
193         $this->addProperty($fieldName, $urls);
194     }
195
196     /**
197      * Execute the file upload
198      *
199      * @param string $fileName File path
200      *
201      * @return string URL at media endpoint
202      */
203     public function uploadToMediaEndpoint($fileName)
204     {
205         $httpReq = $this->getHttpRequest(
206             $this->host->endpoints->media, $this->host->token
207         );
208         $httpReq->addUpload('file', $fileName);
209
210         if ($this->cfg->debug) {
211             $cp = new CurlPrinter();
212             $cp->show($httpReq, ['file' => $fileName]);
213         }
214         $res = $httpReq->send();
215         if (intval($res->getStatus() / 100) != 2) {
216             Log::err(
217                 'Media endpoint returned an error status code '
218                 . $res->getStatus()
219             );
220             Log::err($res->getBody());
221             exit(11);
222         }
223
224         $location = $res->getHeader('location');
225         if ($location === null) {
226             Log::err('Media endpoint did not return a URL');
227             exit(11);
228         }
229
230         $base = new \Net_URL2($this->host->endpoints->media);
231         return (string) $base->resolve($location);
232     }
233
234     public function addContent($text, $isHtml)
235     {
236         if ($isHtml) {
237             $this->addProperty(
238                 'content', [['html' => $text]]
239             );
240         } else {
241             $this->addProperty('content', $text);
242         }
243
244     }
245
246     /**
247      * Adds a micropub property to the request.
248      *
249      * @param string       $key    Parameter name
250      * @param string|array $values One or multiple values
251      *
252      * @return void
253      */
254     public function addProperty($key, $values)
255     {
256         $this->properties[$key] = (array) $values;
257     }
258
259     public function setSendAsJson($json)
260     {
261         $this->sendAsJson = $json;
262     }
263
264     public function setDirectUpload($directUpload)
265     {
266         $this->directUpload = $directUpload;
267     }
268 }