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\GraphNodes;
25
26use Facebook\FacebookRequest;
27use Facebook\Url\FacebookUrlManipulator;
28use Facebook\Exceptions\FacebookSDKException;
29
30/**
31 * Class GraphEdge
32 *
33 * @package Facebook
34 */
35class GraphEdge extends Collection
36{
37    /**
38     * @var FacebookRequest The original request that generated this data.
39     */
40    protected $request;
41
42    /**
43     * @var array An array of Graph meta data like pagination, etc.
44     */
45    protected $metaData = [];
46
47    /**
48     * @var string|null The parent Graph edge endpoint that generated the list.
49     */
50    protected $parentEdgeEndpoint;
51
52    /**
53     * @var string|null The subclass of the child GraphNode's.
54     */
55    protected $subclassName;
56
57    /**
58     * Init this collection of GraphNode's.
59     *
60     * @param FacebookRequest $request            The original request that generated this data.
61     * @param array           $data               An array of GraphNode's.
62     * @param array           $metaData           An array of Graph meta data like pagination, etc.
63     * @param string|null     $parentEdgeEndpoint The parent Graph edge endpoint that generated the list.
64     * @param string|null     $subclassName       The subclass of the child GraphNode's.
65     */
66    public function __construct(FacebookRequest $request, array $data = [], array $metaData = [], $parentEdgeEndpoint = null, $subclassName = null)
67    {
68        $this->request = $request;
69        $this->metaData = $metaData;
70        $this->parentEdgeEndpoint = $parentEdgeEndpoint;
71        $this->subclassName = $subclassName;
72
73        parent::__construct($data);
74    }
75
76    /**
77     * Gets the parent Graph edge endpoint that generated the list.
78     *
79     * @return string|null
80     */
81    public function getParentGraphEdge()
82    {
83        return $this->parentEdgeEndpoint;
84    }
85
86    /**
87     * Gets the subclass name that the child GraphNode's are cast as.
88     *
89     * @return string|null
90     */
91    public function getSubClassName()
92    {
93        return $this->subclassName;
94    }
95
96    /**
97     * Returns the raw meta data associated with this GraphEdge.
98     *
99     * @return array
100     */
101    public function getMetaData()
102    {
103        return $this->metaData;
104    }
105
106    /**
107     * Returns the next cursor if it exists.
108     *
109     * @return string|null
110     */
111    public function getNextCursor()
112    {
113        return $this->getCursor('after');
114    }
115
116    /**
117     * Returns the previous cursor if it exists.
118     *
119     * @return string|null
120     */
121    public function getPreviousCursor()
122    {
123        return $this->getCursor('before');
124    }
125
126    /**
127     * Returns the cursor for a specific direction if it exists.
128     *
129     * @param string $direction The direction of the page: after|before
130     *
131     * @return string|null
132     */
133    public function getCursor($direction)
134    {
135        if (isset($this->metaData['paging']['cursors'][$direction])) {
136            return $this->metaData['paging']['cursors'][$direction];
137        }
138
139        return null;
140    }
141
142    /**
143     * Generates a pagination URL based on a cursor.
144     *
145     * @param string $direction The direction of the page: next|previous
146     *
147     * @return string|null
148     *
149     * @throws FacebookSDKException
150     */
151    public function getPaginationUrl($direction)
152    {
153        $this->validateForPagination();
154
155        // Do we have a paging URL?
156        if (!isset($this->metaData['paging'][$direction])) {
157            return null;
158        }
159
160        $pageUrl = $this->metaData['paging'][$direction];
161
162        return FacebookUrlManipulator::baseGraphUrlEndpoint($pageUrl);
163    }
164
165    /**
166     * Validates whether or not we can paginate on this request.
167     *
168     * @throws FacebookSDKException
169     */
170    public function validateForPagination()
171    {
172        if ($this->request->getMethod() !== 'GET') {
173            throw new FacebookSDKException('You can only paginate on a GET request.', 720);
174        }
175    }
176
177    /**
178     * Gets the request object needed to make a next|previous page request.
179     *
180     * @param string $direction The direction of the page: next|previous
181     *
182     * @return FacebookRequest|null
183     *
184     * @throws FacebookSDKException
185     */
186    public function getPaginationRequest($direction)
187    {
188        $pageUrl = $this->getPaginationUrl($direction);
189        if (!$pageUrl) {
190            return null;
191        }
192
193        $newRequest = clone $this->request;
194        $newRequest->setEndpoint($pageUrl);
195
196        return $newRequest;
197    }
198
199    /**
200     * Gets the request object needed to make a "next" page request.
201     *
202     * @return FacebookRequest|null
203     *
204     * @throws FacebookSDKException
205     */
206    public function getNextPageRequest()
207    {
208        return $this->getPaginationRequest('next');
209    }
210
211    /**
212     * Gets the request object needed to make a "previous" page request.
213     *
214     * @return FacebookRequest|null
215     *
216     * @throws FacebookSDKException
217     */
218    public function getPreviousPageRequest()
219    {
220        return $this->getPaginationRequest('previous');
221    }
222
223    /**
224     * The total number of results according to Graph if it exists.
225     *
226     * This will be returned if the summary=true modifier is present in the request.
227     *
228     * @return int|null
229     */
230    public function getTotalCount()
231    {
232        if (isset($this->metaData['summary']['total_count'])) {
233            return $this->metaData['summary']['total_count'];
234        }
235
236        return null;
237    }
238
239    /**
240     * @inheritDoc
241     */
242    public function map(\Closure $callback)
243    {
244        return new static(
245            $this->request,
246            array_map($callback, $this->items, array_keys($this->items)),
247            $this->metaData,
248            $this->parentEdgeEndpoint,
249            $this->subclassName
250        );
251    }
252}
253