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\Exceptions;
25
26use Facebook\FacebookResponse;
27
28/**
29 * Class FacebookResponseException
30 *
31 * @package Facebook
32 */
33class FacebookResponseException extends FacebookSDKException
34{
35    /**
36     * @var FacebookResponse The response that threw the exception.
37     */
38    protected $response;
39
40    /**
41     * @var array Decoded response.
42     */
43    protected $responseData;
44
45    /**
46     * Creates a FacebookResponseException.
47     *
48     * @param FacebookResponse     $response          The response that threw the exception.
49     * @param FacebookSDKException $previousException The more detailed exception.
50     */
51    public function __construct(FacebookResponse $response, FacebookSDKException $previousException = null)
52    {
53        $this->response = $response;
54        $this->responseData = $response->getDecodedBody();
55
56        $errorMessage = $this->get('message', 'Unknown error from Graph.');
57        $errorCode = $this->get('code', -1);
58
59        parent::__construct($errorMessage, $errorCode, $previousException);
60    }
61
62    /**
63     * A factory for creating the appropriate exception based on the response from Graph.
64     *
65     * @param FacebookResponse $response The response that threw the exception.
66     *
67     * @return FacebookResponseException
68     */
69    public static function create(FacebookResponse $response)
70    {
71        $data = $response->getDecodedBody();
72
73        if (!isset($data['error']['code']) && isset($data['code'])) {
74            $data = ['error' => $data];
75        }
76
77        $code = isset($data['error']['code']) ? $data['error']['code'] : null;
78        $message = isset($data['error']['message']) ? $data['error']['message'] : 'Unknown error from Graph.';
79
80        if (isset($data['error']['error_subcode'])) {
81            switch ($data['error']['error_subcode']) {
82                // Other authentication issues
83                case 458:
84                case 459:
85                case 460:
86                case 463:
87                case 464:
88                case 467:
89                    return new static($response, new FacebookAuthenticationException($message, $code));
90                // Video upload resumable error
91                case 1363030:
92                case 1363019:
93                case 1363037:
94                case 1363033:
95                case 1363021:
96                case 1363041:
97                    return new static($response, new FacebookResumableUploadException($message, $code));
98            }
99        }
100
101        switch ($code) {
102            // Login status or token expired, revoked, or invalid
103            case 100:
104            case 102:
105            case 190:
106                return new static($response, new FacebookAuthenticationException($message, $code));
107
108            // Server issue, possible downtime
109            case 1:
110            case 2:
111                return new static($response, new FacebookServerException($message, $code));
112
113            // API Throttling
114            case 4:
115            case 17:
116            case 32:
117            case 341:
118            case 613:
119                return new static($response, new FacebookThrottleException($message, $code));
120
121            // Duplicate Post
122            case 506:
123                return new static($response, new FacebookClientException($message, $code));
124        }
125
126        // Missing Permissions
127        if ($code == 10 || ($code >= 200 && $code <= 299)) {
128            return new static($response, new FacebookAuthorizationException($message, $code));
129        }
130
131        // OAuth authentication error
132        if (isset($data['error']['type']) && $data['error']['type'] === 'OAuthException') {
133            return new static($response, new FacebookAuthenticationException($message, $code));
134        }
135
136        // All others
137        return new static($response, new FacebookOtherException($message, $code));
138    }
139
140    /**
141     * Checks isset and returns that or a default value.
142     *
143     * @param string $key
144     * @param mixed  $default
145     *
146     * @return mixed
147     */
148    private function get($key, $default = null)
149    {
150        if (isset($this->responseData['error'][$key])) {
151            return $this->responseData['error'][$key];
152        }
153
154        return $default;
155    }
156
157    /**
158     * Returns the HTTP status code
159     *
160     * @return int
161     */
162    public function getHttpStatusCode()
163    {
164        return $this->response->getHttpStatusCode();
165    }
166
167    /**
168     * Returns the sub-error code
169     *
170     * @return int
171     */
172    public function getSubErrorCode()
173    {
174        return $this->get('error_subcode', -1);
175    }
176
177    /**
178     * Returns the error type
179     *
180     * @return string
181     */
182    public function getErrorType()
183    {
184        return $this->get('type', '');
185    }
186
187    /**
188     * Returns the raw response used to create the exception.
189     *
190     * @return string
191     */
192    public function getRawResponse()
193    {
194        return $this->response->getBody();
195    }
196
197    /**
198     * Returns the decoded response used to create the exception.
199     *
200     * @return array
201     */
202    public function getResponseData()
203    {
204        return $this->responseData;
205    }
206
207    /**
208     * Returns the response entity used to create the exception.
209     *
210     * @return FacebookResponse
211     */
212    public function getResponse()
213    {
214        return $this->response;
215    }
216}
217