Subversion Repositories cheapmusic

Rev

Details | Last modification | View Log | RSS feed

Rev Author Line No. Line
25 - 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
 */
24
namespace Facebook;
25
 
26
use ArrayIterator;
27
use IteratorAggregate;
28
use ArrayAccess;
29
use Facebook\Authentication\AccessToken;
30
use Facebook\Exceptions\FacebookSDKException;
31
 
32
/**
33
 * Class BatchRequest
34
 *
35
 * @package Facebook
36
 */
37
class FacebookBatchRequest extends FacebookRequest implements IteratorAggregate, ArrayAccess
38
{
39
    /**
40
     * @var array An array of FacebookRequest entities to send.
41
     */
42
    protected $requests;
43
 
44
    /**
45
     * @var array An array of files to upload.
46
     */
47
    protected $attachedFiles;
48
 
49
    /**
50
     * Creates a new Request entity.
51
     *
52
     * @param FacebookApp|null        $app
53
     * @param array                   $requests
54
     * @param AccessToken|string|null $accessToken
55
     * @param string|null             $graphVersion
56
     */
57
    public function __construct(FacebookApp $app = null, array $requests = [], $accessToken = null, $graphVersion = null)
58
    {
59
        parent::__construct($app, $accessToken, 'POST', '', [], null, $graphVersion);
60
 
61
        $this->add($requests);
62
    }
63
 
64
    /**
65
     * Adds a new request to the array.
66
     *
67
     * @param FacebookRequest|array $request
68
     * @param string|null|array     $options Array of batch request options e.g. 'name', 'omit_response_on_success'.
69
     *                                       If a string is given, it is the value of the 'name' option.
70
     *
71
     * @return FacebookBatchRequest
72
     *
73
     * @throws \InvalidArgumentException
74
     */
75
    public function add($request, $options = null)
76
    {
77
        if (is_array($request)) {
78
            foreach ($request as $key => $req) {
79
                $this->add($req, $key);
80
            }
81
 
82
            return $this;
83
        }
84
 
85
        if (!$request instanceof FacebookRequest) {
86
            throw new \InvalidArgumentException('Argument for add() must be of type array or FacebookRequest.');
87
        }
88
 
89
        if (null === $options) {
90
            $options = [];
91
        } elseif (!is_array($options)) {
92
            $options = ['name' => $options];
93
        }
94
 
95
        $this->addFallbackDefaults($request);
96
 
97
        // File uploads
98
        $attachedFiles = $this->extractFileAttachments($request);
99
 
100
        $name = isset($options['name']) ? $options['name'] : null;
101
 
102
        unset($options['name']);
103
 
104
        $requestToAdd = [
105
            'name' => $name,
106
            'request' => $request,
107
            'options' => $options,
108
            'attached_files' => $attachedFiles,
109
        ];
110
 
111
        $this->requests[] = $requestToAdd;
112
 
113
        return $this;
114
    }
115
 
116
    /**
117
     * Ensures that the FacebookApp and access token fall back when missing.
118
     *
119
     * @param FacebookRequest $request
120
     *
121
     * @throws FacebookSDKException
122
     */
123
    public function addFallbackDefaults(FacebookRequest $request)
124
    {
125
        if (!$request->getApp()) {
126
            $app = $this->getApp();
127
            if (!$app) {
128
                throw new FacebookSDKException('Missing FacebookApp on FacebookRequest and no fallback detected on FacebookBatchRequest.');
129
            }
130
            $request->setApp($app);
131
        }
132
 
133
        if (!$request->getAccessToken()) {
134
            $accessToken = $this->getAccessToken();
135
            if (!$accessToken) {
136
                throw new FacebookSDKException('Missing access token on FacebookRequest and no fallback detected on FacebookBatchRequest.');
137
            }
138
            $request->setAccessToken($accessToken);
139
        }
140
    }
141
 
142
    /**
143
     * Extracts the files from a request.
144
     *
145
     * @param FacebookRequest $request
146
     *
147
     * @return string|null
148
     *
149
     * @throws FacebookSDKException
150
     */
151
    public function extractFileAttachments(FacebookRequest $request)
152
    {
153
        if (!$request->containsFileUploads()) {
154
            return null;
155
        }
156
 
157
        $files = $request->getFiles();
158
        $fileNames = [];
159
        foreach ($files as $file) {
160
            $fileName = uniqid();
161
            $this->addFile($fileName, $file);
162
            $fileNames[] = $fileName;
163
        }
164
 
165
        $request->resetFiles();
166
 
167
        // @TODO Does Graph support multiple uploads on one endpoint?
168
        return implode(',', $fileNames);
169
    }
170
 
171
    /**
172
     * Return the FacebookRequest entities.
173
     *
174
     * @return array
175
     */
176
    public function getRequests()
177
    {
178
        return $this->requests;
179
    }
180
 
181
    /**
182
     * Prepares the requests to be sent as a batch request.
183
     */
184
    public function prepareRequestsForBatch()
185
    {
186
        $this->validateBatchRequestCount();
187
 
188
        $params = [
189
            'batch' => $this->convertRequestsToJson(),
190
            'include_headers' => true,
191
        ];
192
        $this->setParams($params);
193
    }
194
 
195
    /**
196
     * Converts the requests into a JSON(P) string.
197
     *
198
     * @return string
199
     */
200
    public function convertRequestsToJson()
201
    {
202
        $requests = [];
203
        foreach ($this->requests as $request) {
204
            $options = [];
205
 
206
            if (null !== $request['name']) {
207
                $options['name'] = $request['name'];
208
            }
209
 
210
            $options += $request['options'];
211
 
212
            $requests[] = $this->requestEntityToBatchArray($request['request'], $options, $request['attached_files']);
213
        }
214
 
215
        return json_encode($requests);
216
    }
217
 
218
    /**
219
     * Validate the request count before sending them as a batch.
220
     *
221
     * @throws FacebookSDKException
222
     */
223
    public function validateBatchRequestCount()
224
    {
225
        $batchCount = count($this->requests);
226
        if ($batchCount === 0) {
227
            throw new FacebookSDKException('There are no batch requests to send.');
228
        } elseif ($batchCount > 50) {
229
            // Per: https://developers.facebook.com/docs/graph-api/making-multiple-requests#limits
230
            throw new FacebookSDKException('You cannot send more than 50 batch requests at a time.');
231
        }
232
    }
233
 
234
    /**
235
     * Converts a Request entity into an array that is batch-friendly.
236
     *
237
     * @param FacebookRequest   $request       The request entity to convert.
238
     * @param string|null|array $options       Array of batch request options e.g. 'name', 'omit_response_on_success'.
239
     *                                         If a string is given, it is the value of the 'name' option.
240
     * @param string|null       $attachedFiles Names of files associated with the request.
241
     *
242
     * @return array
243
     */
244
    public function requestEntityToBatchArray(FacebookRequest $request, $options = null, $attachedFiles = null)
245
    {
246
 
247
        if (null === $options) {
248
            $options = [];
249
        } elseif (!is_array($options)) {
250
            $options = ['name' => $options];
251
        }
252
 
253
        $compiledHeaders = [];
254
        $headers = $request->getHeaders();
255
        foreach ($headers as $name => $value) {
256
            $compiledHeaders[] = $name . ': ' . $value;
257
        }
258
 
259
        $batch = [
260
            'headers' => $compiledHeaders,
261
            'method' => $request->getMethod(),
262
            'relative_url' => $request->getUrl(),
263
        ];
264
 
265
        // Since file uploads are moved to the root request of a batch request,
266
        // the child requests will always be URL-encoded.
267
        $body = $request->getUrlEncodedBody()->getBody();
268
        if ($body) {
269
            $batch['body'] = $body;
270
        }
271
 
272
        $batch += $options;
273
 
274
        if (null !== $attachedFiles) {
275
            $batch['attached_files'] = $attachedFiles;
276
        }
277
 
278
        return $batch;
279
    }
280
 
281
    /**
282
     * Get an iterator for the items.
283
     *
284
     * @return ArrayIterator
285
     */
286
    public function getIterator()
287
    {
288
        return new ArrayIterator($this->requests);
289
    }
290
 
291
    /**
292
     * @inheritdoc
293
     */
294
    public function offsetSet($offset, $value)
295
    {
296
        $this->add($value, $offset);
297
    }
298
 
299
    /**
300
     * @inheritdoc
301
     */
302
    public function offsetExists($offset)
303
    {
304
        return isset($this->requests[$offset]);
305
    }
306
 
307
    /**
308
     * @inheritdoc
309
     */
310
    public function offsetUnset($offset)
311
    {
312
        unset($this->requests[$offset]);
313
    }
314
 
315
    /**
316
     * @inheritdoc
317
     */
318
    public function offsetGet($offset)
319
    {
320
        return isset($this->requests[$offset]) ? $this->requests[$offset] : null;
321
    }
322
}