Add location header to --dry-run fake response
[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->parseHeaderLine('Location: http://example.org/fake-response');
103             $res->appendBody('Fake --dry-run response');
104             return $res;
105         }
106
107         $res = $this->req->send();
108
109         if (intval($res->getStatus() / 100) != 2) {
110             $this->displayErrorResponse($res);
111         }
112         return $res;
113     }
114
115     protected function displayErrorResponse($res)
116     {
117         Log::err(
118             'Server returned an error status code ' . $res->getStatus()
119         );
120
121         $shown = false;
122         if (Util::getMimeType($res) == 'application/json') {
123             $errData = json_decode($res->getBody());
124             if (!isset($errData->error)) {
125                 Log::err('Error response does not contain "error" property');
126             } else if (isset($errData->error_description)) {
127                 Log::err($errData->error . ': ' . $errData->error_description);
128                 $shown = true;
129             } else {
130                 Log::err($errData->error);
131                 $shown = true;
132             }
133         }
134
135         if (!$shown) {
136             Log::err($res->getBody());
137         }
138         exit(11);
139     }
140
141     public function setAction($action)
142     {
143         $this->action = $action;
144     }
145
146     public function setType($type)
147     {
148         $this->type = $type;
149     }
150
151     public function setUrl($url)
152     {
153         $this->url = $url;
154     }
155
156     /**
157      * Add file upload
158      *
159      * @param string $fieldName name of file-upload field
160      * @param array  $fileNames list of local file paths
161      *
162      * @return void
163      */
164     public function addUpload($fieldName, $fileNames)
165     {
166         if ($this->directUpload && $this->sendAsJson) {
167             throw new \Exception(
168                 'Cannot do direct upload with JSON requests'
169             );
170         }
171
172         if ($this->host->endpoints->media === null
173             || $this->directUpload
174         ) {
175             if ($this->sendAsJson) {
176                 throw new \Exception(
177                     'No media endpoint available, which is required for JSON'
178                 );
179             }
180             if (count($fileNames) == 1) {
181                 $fileNames = reset($fileNames);
182             }
183             $this->uploadsInfo[$fieldName] = $fileNames;
184             return $this->req->addUpload($fieldName, $fileNames);
185         }
186
187         $urls = [];
188         foreach ($fileNames as $fileName) {
189             $urls[] = $this->uploadToMediaEndpoint($fileName);
190         }
191         if (count($urls) == 1) {
192             $urls = reset($urls);
193         }
194         $this->addProperty($fieldName, $urls);
195     }
196
197     /**
198      * Execute the file upload
199      *
200      * @param string $fileName File path
201      *
202      * @return string URL at media endpoint
203      */
204     public function uploadToMediaEndpoint($fileName)
205     {
206         $httpReq = $this->getHttpRequest(
207             $this->host->endpoints->media, $this->host->token
208         );
209         $httpReq->addUpload('file', $fileName);
210
211         if ($this->cfg->debug) {
212             $cp = new CurlPrinter();
213             $cp->show($httpReq, ['file' => $fileName]);
214         }
215         $res = $httpReq->send();
216         if (intval($res->getStatus() / 100) != 2) {
217             Log::err(
218                 'Media endpoint returned an error status code '
219                 . $res->getStatus()
220             );
221             Log::err($res->getBody());
222             exit(11);
223         }
224
225         $location = $res->getHeader('location');
226         if ($location === null) {
227             Log::err('Media endpoint did not return a URL');
228             exit(11);
229         }
230
231         $base = new \Net_URL2($this->host->endpoints->media);
232         return (string) $base->resolve($location);
233     }
234
235     public function addContent($text, $isHtml)
236     {
237         if ($isHtml) {
238             $this->addProperty(
239                 'content', [['html' => $text]]
240             );
241         } else {
242             $this->addProperty('content', $text);
243         }
244
245     }
246
247     /**
248      * Adds a micropub property to the request.
249      *
250      * @param string       $key    Parameter name
251      * @param string|array $values One or multiple values
252      *
253      * @return void
254      */
255     public function addProperty($key, $values)
256     {
257         $this->properties[$key] = (array) $values;
258     }
259
260     public function setSendAsJson($json)
261     {
262         $this->sendAsJson = $json;
263     }
264
265     public function setDirectUpload($directUpload)
266     {
267         $this->directUpload = $directUpload;
268     }
269 }