1<?php
2/**
3 * Copyright 2017 Facebook, Inc.
4 *
5 * You are hereby granted a non-exclusive, worldwide, royalty-free license to
6 * use, copy, modify, and distribute this software in source code or binary
7 * form for use in connection with the web services and APIs provided by
8 * Facebook.
9 *
10 * As with any software that integrates with the Facebook platform, your use
11 * of this software is subject to the Facebook Developer Principles and
12 * Policies [http://developers.facebook.com/policy/]. This copyright notice
13 * shall be included in all copies or substantial portions of the software.
14 *
15 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
18 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
20 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
21 * DEALINGS IN THE SOFTWARE.
22 *
23 */
24namespace Facebook;
25
26use Facebook\GraphNodes\GraphNodeFactory;
27use Facebook\Exceptions\FacebookResponseException;
28use Facebook\Exceptions\FacebookSDKException;
29
30/**
31 * Class FacebookResponse
32 *
33 * @package Facebook
34 */
35class FacebookResponse
36{
37    /**
38     * @var int The HTTP status code response from Graph.
39     */
40    protected $httpStatusCode;
41
42    /**
43     * @var array The headers returned from Graph.
44     */
45    protected $headers;
46
47    /**
48     * @var string The raw body of the response from Graph.
49     */
50    protected $body;
51
52    /**
53     * @var array The decoded body of the Graph response.
54     */
55    protected $decodedBody = [];
56
57    /**
58     * @var FacebookRequest The original request that returned this response.
59     */
60    protected $request;
61
62    /**
63     * @var FacebookSDKException The exception thrown by this request.
64     */
65    protected $thrownException;
66
67    /**
68     * Creates a new Response entity.
69     *
70     * @param FacebookRequest $request
71     * @param string|null     $body
72     * @param int|null        $httpStatusCode
73     * @param array|null      $headers
74     */
75    public function __construct(FacebookRequest $request, $body = null, $httpStatusCode = null, array $headers = [])
76    {
77        $this->request = $request;
78        $this->body = $body;
79        $this->httpStatusCode = $httpStatusCode;
80        $this->headers = $headers;
81
82        $this->decodeBody();
83    }
84
85    /**
86     * Return the original request that returned this response.
87     *
88     * @return FacebookRequest
89     */
90    public function getRequest()
91    {
92        return $this->request;
93    }
94
95    /**
96     * Return the FacebookApp entity used for this response.
97     *
98     * @return FacebookApp
99     */
100    public function getApp()
101    {
102        return $this->request->getApp();
103    }
104
105    /**
106     * Return the access token that was used for this response.
107     *
108     * @return string|null
109     */
110    public function getAccessToken()
111    {
112        return $this->request->getAccessToken();
113    }
114
115    /**
116     * Return the HTTP status code for this response.
117     *
118     * @return int
119     */
120    public function getHttpStatusCode()
121    {
122        return $this->httpStatusCode;
123    }
124
125    /**
126     * Return the HTTP headers for this response.
127     *
128     * @return array
129     */
130    public function getHeaders()
131    {
132        return $this->headers;
133    }
134
135    /**
136     * Return the raw body response.
137     *
138     * @return string
139     */
140    public function getBody()
141    {
142        return $this->body;
143    }
144
145    /**
146     * Return the decoded body response.
147     *
148     * @return array
149     */
150    public function getDecodedBody()
151    {
152        return $this->decodedBody;
153    }
154
155    /**
156     * Get the app secret proof that was used for this response.
157     *
158     * @return string|null
159     */
160    public function getAppSecretProof()
161    {
162        return $this->request->getAppSecretProof();
163    }
164
165    /**
166     * Get the ETag associated with the response.
167     *
168     * @return string|null
169     */
170    public function getETag()
171    {
172        return isset($this->headers['ETag']) ? $this->headers['ETag'] : null;
173    }
174
175    /**
176     * Get the version of Graph that returned this response.
177     *
178     * @return string|null
179     */
180    public function getGraphVersion()
181    {
182        return isset($this->headers['Facebook-API-Version']) ? $this->headers['Facebook-API-Version'] : null;
183    }
184
185    /**
186     * Returns true if Graph returned an error message.
187     *
188     * @return boolean
189     */
190    public function isError()
191    {
192        return isset($this->decodedBody['error']);
193    }
194
195    /**
196     * Throws the exception.
197     *
198     * @throws FacebookSDKException
199     */
200    public function throwException()
201    {
202        throw $this->thrownException;
203    }
204
205    /**
206     * Instantiates an exception to be thrown later.
207     */
208    public function makeException()
209    {
210        $this->thrownException = FacebookResponseException::create($this);
211    }
212
213    /**
214     * Returns the exception that was thrown for this request.
215     *
216     * @return FacebookResponseException|null
217     */
218    public function getThrownException()
219    {
220        return $this->thrownException;
221    }
222
223    /**
224     * Convert the raw response into an array if possible.
225     *
226     * Graph will return 2 types of responses:
227     * - JSON(P)
228     *    Most responses from Graph are JSON(P)
229     * - application/x-www-form-urlencoded key/value pairs
230     *    Happens on the `/oauth/access_token` endpoint when exchanging
231     *    a short-lived access token for a long-lived access token
232     * - And sometimes nothing :/ but that'd be a bug.
233     */
234    public function decodeBody()
235    {
236        $this->decodedBody = json_decode($this->body, true);
237
238        if ($this->decodedBody === null) {
239            $this->decodedBody = [];
240            parse_str($this->body, $this->decodedBody);
241        } elseif (is_bool($this->decodedBody)) {
242            // Backwards compatibility for Graph < 2.1.
243            // Mimics 2.1 responses.
244            // @TODO Remove this after Graph 2.0 is no longer supported
245            $this->decodedBody = ['success' => $this->decodedBody];
246        } elseif (is_numeric($this->decodedBody)) {
247            $this->decodedBody = ['id' => $this->decodedBody];
248        }
249
250        if (!is_array($this->decodedBody)) {
251            $this->decodedBody = [];
252        }
253
254        if ($this->isError()) {
255            $this->makeException();
256        }
257    }
258
259    /**
260     * Instantiate a new GraphObject from response.
261     *
262     * @param string|null $subclassName The GraphNode subclass to cast to.
263     *
264     * @return \Facebook\GraphNodes\GraphObject
265     *
266     * @throws FacebookSDKException
267     *
268     * @deprecated 5.0.0 getGraphObject() has been renamed to getGraphNode()
269     * @todo v6: Remove this method
270     */
271    public function getGraphObject($subclassName = null)
272    {
273        return $this->getGraphNode($subclassName);
274    }
275
276    /**
277     * Instantiate a new GraphNode from response.
278     *
279     * @param string|null $subclassName The GraphNode subclass to cast to.
280     *
281     * @return \Facebook\GraphNodes\GraphNode
282     *
283     * @throws FacebookSDKException
284     */
285    public function getGraphNode($subclassName = null)
286    {
287        $factory = new GraphNodeFactory($this);
288
289        return $factory->makeGraphNode($subclassName);
290    }
291
292    /**
293     * Convenience method for creating a GraphAlbum collection.
294     *
295     * @return \Facebook\GraphNodes\GraphAlbum
296     *
297     * @throws FacebookSDKException
298     */
299    public function getGraphAlbum()
300    {
301        $factory = new GraphNodeFactory($this);
302
303        return $factory->makeGraphAlbum();
304    }
305
306    /**
307     * Convenience method for creating a GraphPage collection.
308     *
309     * @return \Facebook\GraphNodes\GraphPage
310     *
311     * @throws FacebookSDKException
312     */
313    public function getGraphPage()
314    {
315        $factory = new GraphNodeFactory($this);
316
317        return $factory->makeGraphPage();
318    }
319
320    /**
321     * Convenience method for creating a GraphSessionInfo collection.
322     *
323     * @return \Facebook\GraphNodes\GraphSessionInfo
324     *
325     * @throws FacebookSDKException
326     */
327    public function getGraphSessionInfo()
328    {
329        $factory = new GraphNodeFactory($this);
330
331        return $factory->makeGraphSessionInfo();
332    }
333
334    /**
335     * Convenience method for creating a GraphUser collection.
336     *
337     * @return \Facebook\GraphNodes\GraphUser
338     *
339     * @throws FacebookSDKException
340     */
341    public function getGraphUser()
342    {
343        $factory = new GraphNodeFactory($this);
344
345        return $factory->makeGraphUser();
346    }
347
348    /**
349     * Convenience method for creating a GraphEvent collection.
350     *
351     * @return \Facebook\GraphNodes\GraphEvent
352     *
353     * @throws FacebookSDKException
354     */
355    public function getGraphEvent()
356    {
357        $factory = new GraphNodeFactory($this);
358
359        return $factory->makeGraphEvent();
360    }
361
362    /**
363     * Convenience method for creating a GraphGroup collection.
364     *
365     * @return \Facebook\GraphNodes\GraphGroup
366     *
367     * @throws FacebookSDKException
368     */
369    public function getGraphGroup()
370    {
371        $factory = new GraphNodeFactory($this);
372
373        return $factory->makeGraphGroup();
374    }
375
376    /**
377     * Instantiate a new GraphList from response.
378     *
379     * @param string|null $subclassName The GraphNode subclass to cast list items to.
380     * @param boolean     $auto_prefix  Toggle to auto-prefix the subclass name.
381     *
382     * @return \Facebook\GraphNodes\GraphList
383     *
384     * @throws FacebookSDKException
385     *
386     * @deprecated 5.0.0 getGraphList() has been renamed to getGraphEdge()
387     * @todo v6: Remove this method
388     */
389    public function getGraphList($subclassName = null, $auto_prefix = true)
390    {
391        return $this->getGraphEdge($subclassName, $auto_prefix);
392    }
393
394    /**
395     * Instantiate a new GraphEdge from response.
396     *
397     * @param string|null $subclassName The GraphNode subclass to cast list items to.
398     * @param boolean     $auto_prefix  Toggle to auto-prefix the subclass name.
399     *
400     * @return \Facebook\GraphNodes\GraphEdge
401     *
402     * @throws FacebookSDKException
403     */
404    public function getGraphEdge($subclassName = null, $auto_prefix = true)
405    {
406        $factory = new GraphNodeFactory($this);
407
408        return $factory->makeGraphEdge($subclassName, $auto_prefix);
409    }
410}
411