1: <?php
2: /**
3: * Horde_Service_Facebook class abstracts communication with Facebook's
4: * rest interface.
5: *
6: * This code was originally a Hordified version of Facebook's official PHP
7: * client. However, since that client was very buggy and incomplete, very little
8: * of the original code or design is left. I left the original copyright notice
9: * intact below.
10: *
11: * Copyright 2009-2012 Horde LLC (http://www.horde.org/)
12: *
13: * @author Michael J. Rubinsky <mrubinsk@horde.org>
14: * @category Horde
15: * @package Service_Facebook
16: */
17:
18: /**
19: * Facebook Platform PHP5 client
20: *
21: * Copyright 2004-2009 Facebook. All Rights Reserved.
22: *
23: * Copyright (c) 2007 Facebook, Inc.
24: * All rights reserved.
25: *
26: * Redistribution and use in source and binary forms, with or without
27: * modification, are permitted provided that the following conditions
28: * are met:
29: *
30: * 1. Redistributions of source code must retain the above copyright
31: * notice, this list of conditions and the following disclaimer.
32: * 2. Redistributions in binary form must reproduce the above copyright
33: * notice, this list of conditions and the following disclaimer in the
34: * documentation and/or other materials provided with the distribution.
35: *
36: * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
37: * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
38: * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
39: * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
40: * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
41: * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
42: * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
43: * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
44: * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
45: * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
46: *
47: * For help with this library, contact developers-help@facebook.com
48: */
49: class Horde_Service_Facebook
50: {
51: /**
52: * Use only ssl resource flag
53: *
54: * @var boolean
55: */
56: public $useSslResources = false;
57:
58: /**
59: * The API Secret Key
60: *
61: * @var string
62: */
63: protected $_secret;
64:
65: /**
66: * Holds the batch object when building a batch request.
67: *
68: * @var Horde_Service_Facebook_Batch
69: */
70: protected $_batchRequest;
71:
72: /**
73: * Holds an optional logger object
74: *
75: * @var Horde_Log_Logger
76: */
77: protected $_logger;
78:
79: /**
80: *
81: * @var Horde_Http_Client
82: */
83: protected $_http;
84:
85: /**
86: * Return format
87: *
88: * @var Horde_Service_Facebook::DATA_FORMAT_* constant
89: */
90: public $dataFormat = self::DATA_FORMAT_ARRAY;
91:
92: /**
93: * Cache for the various objects we lazy load in __get()
94: *
95: * @var hash of Horde_Service_Facebook_* objects
96: */
97: protected $_objCache = array();
98:
99:
100: const API_VALIDATION_ERROR = 1;
101: const REST_SERVER_ADDR = 'https://api.facebook.com/method/';
102: const GRAPH_SERVER_ADDR = 'https://graph.facebook.com';
103:
104: /**
105: * Data format returned to client code.
106: */
107: const DATA_FORMAT_JSON = 'json';
108: const DATA_FORMAT_XML = 'xml';
109: const DATA_FORMAT_ARRAY = 'array';
110:
111: /**
112: * Const'r
113: *
114: * @param string $appId Application ID.
115: * @param string $secret Developer API secret.
116: * @param array $context Array of context information containing:
117: * <pre>
118: * http_client - required
119: * logger
120: * use_ssl
121: * </pre>
122: */
123: public function __construct($appId, $secret, $context)
124: {
125: // We require a http client object.
126: if (empty($context['http_client'])) {
127: throw new InvalidArgumentException('A http client object is required');
128: } else {
129: $this->_http = $context['http_client'];
130: }
131:
132: // Optional Horde_Log_Logger
133: if (!empty($context['logger'])) {
134: $this->_logger = $context['logger'];
135: } else {
136: $this->_logger = new Horde_Support_Stub();
137: }
138:
139: $this->_logger->debug('Initializing Horde_Service_Facebook');
140:
141: $this->_appId = $appId;
142: $this->secret = $secret;
143:
144: if (!empty($context['use_ssl'])) {
145: $this->useSslResources = true;
146: }
147: }
148:
149: /**
150: * Lazy load the facebook classes.
151: *
152: * @param string $value The lowercase representation of the subclass.
153: *
154: * @return mixed
155: * @throws Horde_Service_Facebook_Exception
156: */
157: public function __get($value)
158: {
159: // First, see if it's an allowed protected value.
160: switch ($value) {
161: case 'internalFormat':
162: return self::DATA_FORMAT_JSON;
163: case 'appId':
164: return $this->_appId;
165: case 'secret':
166: return $this->_secret;
167: case 'http':
168: return $this->_http;
169: case 'logger':
170: return $this->_logger;
171: }
172:
173: // If not, assume it's a method/action class...
174: $class = 'Horde_Service_Facebook_' . ucfirst($value);
175: if (!class_exists($class)) {
176: throw new Horde_Service_Facebook_Exception(sprintf("%s class not found", $class));
177: }
178:
179: if (empty($this->_objCache[$class])) {
180: $this->_objCache[$class] = new $class($this);
181: }
182:
183: return $this->_objCache[$class];
184: }
185:
186: /**
187: * Helper function to get the appropriate facebook url
188: *
189: * @param string $subdomain The subdomain to use (www).
190: *
191: * @return string
192: */
193: public static function getFacebookUrl($subdomain = 'www')
194: {
195: return 'https://' . $subdomain . '.facebook.com';
196: }
197:
198: /**
199: * Start a batch operation.
200: */
201: public function batchBegin()
202: {
203: $this->_logger->debug('Starting batch operation');
204: if ($this->_batchRequest !== null) {
205: $code = Horde_Service_Facebook_ErrorCodes::API_EC_BATCH_ALREADY_STARTED;
206: $description = Horde_Service_Facebook_ErrorCodes::$api_error_descriptions[$code];
207: $this->_logger->err($description);
208: throw new Horde_Service_Facebook_Exception($description, $code);
209: }
210:
211: $this->_batchRequest = new Horde_Service_Facebook_BatchRequest($this);
212: }
213:
214: /**
215: * End current batch operation
216: */
217: public function batchEnd()
218: {
219: $this->_logger->debug('Ending batch operation');
220: if ($this->_batchRequest === null) {
221: $code = Horde_Service_Facebook_ErrorCodes::API_EC_BATCH_NOT_STARTED;
222: $description = Horde_Service_Facebook_ErrorCodes::$api_error_descriptions[$code];
223: $this->_logger->err($description);
224: throw new Horde_Service_Facebook_Exception($description, $code);
225: }
226:
227: $this->_batchRequest->run();
228: $this->_batchRequest = null;
229: }
230:
231: /**
232: * Calls the specified normal POST method with the specified parameters.
233: *
234: * @param string $method Name of the Facebook method to invoke
235: * @param array $params A map of param names => param values
236: *
237: * @return mixed Result of method call; this returns a reference to support
238: * 'delayed returns' when in a batch context.
239: * See: http://wiki.developers.facebook.com/index.php/Using_batching_API
240: */
241: public function &callMethod($method, array $params = array())
242: {
243: $this->_logger->debug(sprintf('Calling method %s with parameters %s', $method, print_r($params, true)));
244: if ($this->_batchRequest === null) {
245: $request = new Horde_Service_Facebook_Request($this, $method, $params);
246: $results = &$request->run();
247: } else {
248: $results = &$this->_batchRequest->add($method, $params);
249: }
250:
251: return $results;
252: }
253:
254: /**
255: * Calls the specified file-upload POST method with the specified parameters
256: *
257: * @param string $method Name of the Facebook method to invoke
258: * @param array $params A map of param names => param values
259: * @param string $file A path to the file to upload (required)
260: *
261: * @return array A dictionary representing the response.
262: */
263: public function callUploadMethod($method, $params, $file)
264: {
265: if ($this->_batchRequest === null) {
266: if (!file_exists($file)) {
267: $code = Horde_Service_Facebook_ErrorCodes::API_EC_PARAM;
268: $description = Horde_Service_Facebook_ErrorCodes::$api_error_descriptions[$code];
269: throw new Horde_Service_Facebook_Exception($description, $code);
270: }
271: } else {
272: $code = Horde_Service_Facebook_ErrorCodes::API_EC_BATCH_METHOD_NOT_ALLOWED_IN_BATCH_MODE;
273: $description = Horde_Service_Facebook_ErrorCodes::$api_error_descriptions[$code];
274: throw new Horde_Service_Facebook_Exception($description, $code);
275: }
276: $request = new Horde_Service_Facebook_UploadRequest($this, $method, $file, $params);
277: $result = $request->run();
278: $result = json_decode($result, true);
279: if (is_array($result) && isset($result['error_code'])) {
280: throw new Horde_Service_Facebook_Exception($result['error_msg'], $result['error_code']);
281: }
282:
283: return $result;
284: }
285:
286: }