Recent Facebook Posts - Version 1.7.3

Version Description

  • October 28, 2013 =
  • Added: rfbp_read_more filter.
  • Added: rfbp_content filter.
  • Added: option to unhook wpautop from rfbp_content filter.
Download this release

Release Info

Developer DvanKooten
Plugin Icon wp plugin Recent Facebook Posts
Version 1.7.3
Comparing to
See all releases

Code changes from version 1.6 to 1.7.3

assets/css/admin.css CHANGED
@@ -5,7 +5,7 @@
5
  .rfbp-box{ margin:0 0 24px;}
6
  .rfbp-well { background:#222; color:#eee; padding:10px 20px; margin-top:10px; }
7
 
8
- .error{ color:red; }
9
  p.status{ font-weight:bold;}
10
  span.connected, span.disconnected{ font-weight:bold; display:inline-block; color:white; padding:3px; }
11
  span.connected{ background:green; }
5
  .rfbp-box{ margin:0 0 24px;}
6
  .rfbp-well { background:#222; color:#eee; padding:10px 20px; margin-top:10px; }
7
 
8
+ .rfbp-row-error label{ color:red; }
9
  p.status{ font-weight:bold;}
10
  span.connected, span.disconnected{ font-weight:bold; display:inline-block; color:white; padding:3px; }
11
  span.connected{ background:green; }
assets/css/default.css CHANGED
@@ -2,7 +2,7 @@
2
 
3
  .rfbp-post { border-bottom:1px solid #E9E9E9; padding-bottom:15px; margin-bottom:15px; margin-top:0; padding-top:0; }
4
  .rfbp-post-link-wrap { margin-bottom:0 !important; padding-bottom:0; }
5
- .rfbp-post-link{ display:block; font-weight:normal; background:none; border:0; padding:1px 0; margin:0; cursor:pointer; text-decoration:none !important; color:#3B5998;; font-size:11px; line-height:15px; height:15px; }
6
  .rfbp-post-link:hover{ text-decoration:none !important; color:#3B5998; }
7
 
8
  .rfbp-timestamp{ color:#999;}
2
 
3
  .rfbp-post { border-bottom:1px solid #E9E9E9; padding-bottom:15px; margin-bottom:15px; margin-top:0; padding-top:0; }
4
  .rfbp-post-link-wrap { margin-bottom:0 !important; padding-bottom:0; }
5
+ .rfbp-post-link{ display:block; font-weight:normal; background:none; border:0; padding:1px 0; margin:0; cursor:pointer; text-decoration:none !important; color:#3B5998;; font-size:11px; line-height:15px !important; height:15px; }
6
  .rfbp-post-link:hover{ text-decoration:none !important; color:#3B5998; }
7
 
8
  .rfbp-timestamp{ color:#999;}
includes/RFBP.php CHANGED
@@ -32,10 +32,12 @@ class RFBP {
32
  // only on frontend
33
  if(!is_admin()) {
34
 
 
 
35
  include_once RFBP_PLUGIN_DIR . 'includes/helper-functions.php';
36
  include_once RFBP_PLUGIN_DIR . 'includes/template-functions.php';
37
 
38
- add_shortcode('rfbp', array($this, 'output'));
39
  add_shortcode('recent-facebook-posts', array($this, 'output'));
40
 
41
  if($opts['load_css']) {
@@ -47,6 +49,9 @@ class RFBP {
47
  include_once RFBP_PLUGIN_DIR . 'includes/RFBP_Admin.php';
48
  new RFBP_Admin();
49
  }
 
 
 
50
  }
51
 
52
  public function register_widget()
@@ -90,105 +95,97 @@ class RFBP {
90
  static public function api()
91
  {
92
  if(!self::$api) {
93
-
94
- if(!class_exists("Facebook")) {
95
- require_once RFBP_PLUGIN_DIR . 'includes/facebook-php-sdk/facebook.php';
96
- }
97
-
98
  $opts = RFBP::instance()->get_settings();
99
- self::$api = new Facebook(array(
100
- 'appId' => trim($opts['app_id']),
101
- 'secret' => trim($opts['app_secret']),
102
- ));
103
  }
104
 
105
  return self::$api;
106
  }
107
 
 
 
 
 
 
 
 
 
 
 
 
108
  public function get_posts() {
109
 
110
- $opts = $this->get_settings();
111
-
112
  // try to get posts from cache
113
- if(($posts = $this->get_cached_posts())) {
114
  return $posts;
115
  }
116
 
117
- if(empty($opts['app_id']) || empty($opts['fb_id'])) { return array(); }
118
-
119
- $accessToken = get_option('rfb_access_token');
120
-
121
- // if no access token has been stored, we can't make the API call.
122
- if(!$accessToken) { return array(); }
123
-
124
- $fb = self::api();
125
- $fb->setAccessToken($accessToken);
126
 
127
- // check if Facebook API has an identified user
128
- // if not, API is not connected
129
- if(!$fb->getUser()) { return array(); }
130
 
131
- $apiResult = $fb->api(trim($opts['fb_id']) . '/posts?fields=id,picture,type,from,message,status_type,object_id,link,created_time,comments.limit(1).summary(true),likes.limit(1).summary(true)');
132
-
133
- if(!$apiResult or !is_array($apiResult) or !isset($apiResult['data']) or !is_array($apiResult['data'])) { return array(); }
 
134
 
135
  $posts = array();
136
- foreach($apiResult['data'] as $p) {
137
 
138
  // skip this "post" if it is not of one of the following types
139
- if(!in_array($p['type'], array('status', 'photo', 'video', 'link'))) {
140
  continue;
141
  }
142
 
143
- // skip empty status updates
144
- if($p['type'] == 'status' && (!isset($p['message']) || empty($p['message']))) { continue; }
145
- if($p['type'] == 'status' && $p['status_type'] == 'approved_friend') { continue; }
146
-
147
- //var_dump($p); echo '<br /><br />';
148
 
149
  //split user and post ID (userID_postID)
150
- $idArray = explode("_", $p['id']);
151
 
152
  $post = array();
153
- $post['type'] = $p['type'];
154
- $post['author'] = $p['from'];
155
- $post['content'] = isset($p['message']) ? $p['message'] : '';
156
  $post['image'] = null;
157
 
158
  // set post content and image
159
- if($p['type'] == 'photo') {
160
 
161
- $image = "//graph.facebook.com/". $p['object_id'] . '/picture?type=' . $opts['img_size'];
162
  $post['image'] = $image;
163
 
164
- } elseif($p['type'] == 'video') {
165
 
166
- $image = $p['picture'];
167
 
 
168
  if($opts['img_size'] == 'normal') {
169
  $image = str_replace(array("_s.jpg", "_s.png"), array("_n.jpg", "_n.png"), $image);
170
  }
171
 
172
  $post['image'] = $image;
173
 
174
- } elseif($p['type'] == 'link') {
175
- $post['content'] .= "\n\n" . $p['link'];
176
  }
177
 
178
  // calculate post like and comment counts
179
- if(isset($p['likes']['summary']['total_count'])) {
180
- $like_count = $p['likes']['summary']['total_count'];
181
  } else {
182
  $like_count = 0;
183
  }
184
 
185
- if(isset($p['comments']['summary']['total_count'])) {
186
- $comment_count = $p['comments']['summary']['total_count'];
187
  } else {
188
  $comment_count = 0;
189
  }
190
 
191
- $post['timestamp'] = strtotime($p['created_time']);
192
  $post['like_count'] = $like_count;
193
  $post['comment_count'] = $comment_count;
194
  $post['link'] = "http://www.facebook.com/".$opts['fb_id']."/posts/".$idArray[1];
@@ -197,47 +194,13 @@ class RFBP {
197
  }
198
 
199
  // store results in cache for later use
200
- $this->set_cached_posts($posts);
201
-
202
- return $posts;
203
- }
204
-
205
- public function invalidate_cache()
206
- {
207
- $opts = $this->get_settings();
208
- $cache_file = WP_CONTENT_DIR . '/recent-facebook-posts.cache';
209
- $time = time() - ($opts['cache_time'] * 2);
210
- return touch($cache_file, $time);
211
- }
212
-
213
- private function get_cached_posts()
214
- {
215
- $opts = $this->get_settings();
216
- $cache_file = WP_CONTENT_DIR . '/recent-facebook-posts.cache';
217
- if(!file_exists($cache_file) || ($this->get_time_of_last_file_change($cache_file) < (time() - $opts['cache_time']))) {
218
- return false;
219
  }
220
-
221
- // by now, cache file exists.
222
- $posts = json_decode(file_get_contents($cache_file), true);
223
- return $posts;
224
-
225
- }
226
-
227
- private function set_cached_posts($posts)
228
- {
229
- $data = json_encode($posts);
230
- $cache_dir = WP_CONTENT_DIR . '/';
231
- $cache_file = WP_CONTENT_DIR . '/recent-facebook-posts.cache';
232
 
233
- // abandon if cache folder is not writable
234
- if(!is_writable(WP_CONTENT_DIR)) {
235
- return false;
236
- }
237
-
238
- file_put_contents($cache_file, $data);
239
- $this->cache_renewed = true;
240
- return true;
241
  }
242
 
243
  public function output($atts = array())
@@ -263,25 +226,37 @@ class RFBP {
263
 
264
  if($posts && !empty($posts)) {
265
 
 
 
266
  $posts = array_slice($posts, 0, $number);
267
  $link_target = ($opts['link_new_window']) ? "_blank" : '';
268
 
269
  foreach($posts as $p) {
270
- $content = $p['content'];
 
 
271
  $shortened = false;
272
 
273
  if(strlen($content) > $excerpt_length) {
274
- $limit = strpos($p['content'], ' ',$excerpt_length);
275
  if($limit) {
276
- $content = substr($p['content'], 0, $limit);
277
  $shortened = true;
278
  }
279
  }
280
  ?>
281
 
282
-
283
  <<?php echo $el; ?> class="rfbp-post">
284
- <p class="rfbp-text"><?php echo nl2br(rfbp_make_clickable($content, $link_target)); if($shortened) { echo '..'; } ?></p>
 
 
 
 
 
 
 
 
 
285
  <?php if($opts['img_size'] != 'dont_show' && isset($p['image']) && !empty($p['image'])) { ?>
286
  <p class="rfbp-image-wrap">
287
  <a class="rfbp-image-link" target="<?php echo $link_target; ?>" href="<?php echo $p['link']; ?>" rel="nofollow">
@@ -302,10 +277,13 @@ class RFBP {
302
  <?php
303
 
304
  } // end foreach $posts
 
 
 
305
  } else {
306
  ?><p>No recent Facebook posts to show.</p><?php
307
  if(current_user_can('manage_options')) {
308
- ?><p><strong>Admins only notice:</strong> Did you <a href="<?php echo admin_url('options-general.php?page=rfb-settings'); ?>">configure the plugin</a> properly?</p><?php
309
  }
310
  } ?>
311
 
@@ -322,26 +300,4 @@ class RFBP {
322
  return $output;
323
  }
324
 
325
- private function get_time_of_last_file_change($filePath)
326
- {
327
- clearstatcache();
328
- $time = filemtime($filePath);
329
-
330
- $isDST = (date('I', $time) == 1);
331
- $systemDST = (date('I') == 1);
332
-
333
- $adjustment = 0;
334
-
335
- if($isDST == false && $systemDST == true)
336
- $adjustment = 3600;
337
-
338
- else if($isDST == true && $systemDST == false)
339
- $adjustment = -3600;
340
-
341
- else
342
- $adjustment = 0;
343
-
344
- return ($time + $adjustment);
345
- }
346
-
347
  }
32
  // only on frontend
33
  if(!is_admin()) {
34
 
35
+ add_filter('rfbp_content', 'wpautop');
36
+
37
  include_once RFBP_PLUGIN_DIR . 'includes/helper-functions.php';
38
  include_once RFBP_PLUGIN_DIR . 'includes/template-functions.php';
39
 
40
+ add_shortcode('recent_facebook_posts', array($this, 'output'));
41
  add_shortcode('recent-facebook-posts', array($this, 'output'));
42
 
43
  if($opts['load_css']) {
49
  include_once RFBP_PLUGIN_DIR . 'includes/RFBP_Admin.php';
50
  new RFBP_Admin();
51
  }
52
+
53
+ add_action('rfbp_renew_cache', array($this, 'delete_transients'), 10);
54
+ add_action('rfbp_renew_cache', array($this, 'get_posts'), 11);
55
  }
56
 
57
  public function register_widget()
95
  static public function api()
96
  {
97
  if(!self::$api) {
98
+ require_once RFBP_PLUGIN_DIR . 'includes/RFBP_API.php';
 
 
 
 
99
  $opts = RFBP::instance()->get_settings();
100
+ self::$api = new RFBP_API($opts['app_id'], $opts['app_secret'], $opts['fb_id']);
 
 
 
101
  }
102
 
103
  return self::$api;
104
  }
105
 
106
+ public function get_fallback_posts()
107
+ {
108
+ $posts = get_transient('rfbp_posts_fallback');
109
+
110
+ if(!$posts) {
111
+ return array();
112
+ }
113
+
114
+ return $posts;
115
+ }
116
+
117
  public function get_posts() {
118
 
 
 
119
  // try to get posts from cache
120
+ if(($posts = get_transient('rfbp_posts'))) {
121
  return $posts;
122
  }
123
 
124
+ $opts = $this->get_settings();
 
 
 
 
 
 
 
 
125
 
126
+ $api = self::api();
127
+ $data = $api->get_posts();
 
128
 
129
+ // did api call succeed?
130
+ if(!$data) {
131
+ return $this->get_fallback_posts();
132
+ }
133
 
134
  $posts = array();
135
+ foreach($data as $p) {
136
 
137
  // skip this "post" if it is not of one of the following types
138
+ if(!in_array($p->type, array('status', 'photo', 'video', 'link'))) {
139
  continue;
140
  }
141
 
142
+ // skip empty status updates and friend approvals
143
+ if($p->type == 'status' && (!isset($p->message) || empty($p->message))) { continue; }
144
+ if($p->type == 'status' && $p->status_type == 'approved_friend') { continue; }
 
 
145
 
146
  //split user and post ID (userID_postID)
147
+ $idArray = explode("_", $p->id);
148
 
149
  $post = array();
150
+ $post['type'] = $p->type;
151
+ $post['content'] = isset($p->message) ? utf8_encode($p->message) : '';
 
152
  $post['image'] = null;
153
 
154
  // set post content and image
155
+ if($p->type == 'photo') {
156
 
157
+ $image = "//graph.facebook.com/". $p->object_id . '/picture?type=' . $opts['img_size'];
158
  $post['image'] = $image;
159
 
160
+ } elseif($p->type == 'video') {
161
 
162
+ $image = $p->picture;
163
 
164
+ // hacky
165
  if($opts['img_size'] == 'normal') {
166
  $image = str_replace(array("_s.jpg", "_s.png"), array("_n.jpg", "_n.png"), $image);
167
  }
168
 
169
  $post['image'] = $image;
170
 
171
+ } elseif($p->type == 'link' && !stristr($post['content'], $p->link)) {
172
+ $post['content'] .= (empty($post['content'])) ? $p->link : "\n\n" . $p->link;
173
  }
174
 
175
  // calculate post like and comment counts
176
+ if(isset($p->likes->summary->total_count)) {
177
+ $like_count = $p->likes->summary->total_count;
178
  } else {
179
  $like_count = 0;
180
  }
181
 
182
+ if(isset($p->comments->summary->total_count)) {
183
+ $comment_count = $p->comments->summary->total_count;
184
  } else {
185
  $comment_count = 0;
186
  }
187
 
188
+ $post['timestamp'] = strtotime($p->created_time);
189
  $post['like_count'] = $like_count;
190
  $post['comment_count'] = $comment_count;
191
  $post['link'] = "http://www.facebook.com/".$opts['fb_id']."/posts/".$idArray[1];
194
  }
195
 
196
  // store results in cache for later use
197
+ if($posts) {
198
+ set_transient('rfbp_posts', $posts, $opts['cache_time']); // user set cache time
199
+ set_transient('rfbp_posts_fallback', $posts, 2629744); // 1 month
200
+ $this->cache_renewed = true;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
201
  }
 
 
 
 
 
 
 
 
 
 
 
 
202
 
203
+ return $posts;
 
 
 
 
 
 
 
204
  }
205
 
206
  public function output($atts = array())
226
 
227
  if($posts && !empty($posts)) {
228
 
229
+ if($el == 'li') { echo '<ul class="rfbp-posts-wrap">'; }
230
+
231
  $posts = array_slice($posts, 0, $number);
232
  $link_target = ($opts['link_new_window']) ? "_blank" : '';
233
 
234
  foreach($posts as $p) {
235
+
236
+ $content = convert_smilies(utf8_decode($p['content']));
237
+
238
  $shortened = false;
239
 
240
  if(strlen($content) > $excerpt_length) {
241
+ $limit = strpos($content, ' ',$excerpt_length);
242
  if($limit) {
243
+ $content = substr($content, 0, $limit);
244
  $shortened = true;
245
  }
246
  }
247
  ?>
248
 
 
249
  <<?php echo $el; ?> class="rfbp-post">
250
+ <div class="rfbp-text">
251
+
252
+ <?php
253
+ $content = make_clickable($content, $link_target);
254
+ $content = ($shortened) ? $content . apply_filters('rfbp_read_more', '..', $p['link']) : $content;
255
+ $content = apply_filters('rfbp_content', $content, $p['link']);
256
+
257
+ echo $content; ?>
258
+
259
+ </div>
260
  <?php if($opts['img_size'] != 'dont_show' && isset($p['image']) && !empty($p['image'])) { ?>
261
  <p class="rfbp-image-wrap">
262
  <a class="rfbp-image-link" target="<?php echo $link_target; ?>" href="<?php echo $p['link']; ?>" rel="nofollow">
277
  <?php
278
 
279
  } // end foreach $posts
280
+
281
+ if($el == 'li') { echo '</ul>'; }
282
+
283
  } else {
284
  ?><p>No recent Facebook posts to show.</p><?php
285
  if(current_user_can('manage_options')) {
286
+ ?><p><strong>Admins only notice:</strong> Did you <a href="<?php echo admin_url('options-general.php?page=rfbp'); ?>">configure the plugin</a> properly?</p><?php
287
  }
288
  } ?>
289
 
300
  return $output;
301
  }
302
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
303
  }
includes/RFBP_API.php ADDED
@@ -0,0 +1,71 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ class RFBP_API {
4
+
5
+ private $app_id = '';
6
+ private $app_secret = '';
7
+ private $error;
8
+
9
+ public function __construct($app_id, $app_secret, $fb_id = '')
10
+ {
11
+ $this->app_id = $app_id;
12
+ $this->app_secret = $app_secret;
13
+ $this->fb_id = $fb_id;
14
+ }
15
+
16
+ public function get_posts()
17
+ {
18
+ $result = $this->call("{$this->fb_id}/posts", array(
19
+ 'fields' => 'id,picture,type,from,message,status_type,object_id,link,created_time,comments.limit(1).summary(true),likes.limit(1).summary(true)',
20
+ 'access_token' => "{$this->app_id}|{$this->app_secret}"
21
+ ));
22
+
23
+ if($result) {
24
+ if(isset($result->data)) {
25
+ return $result->data;
26
+ } else {
27
+ $this->error = $result->error;
28
+ return false;
29
+ }
30
+ }
31
+
32
+ return false;
33
+ }
34
+
35
+ private function call($endpoint, array $data = array())
36
+ {
37
+ if(empty($this->app_id) || empty($this->app_secret)) { return false; }
38
+
39
+ $url = "https://graph.facebook.com/{$endpoint}";
40
+
41
+ $url = add_query_arg($data, $url);
42
+
43
+ $response = wp_remote_get($url, array(
44
+ 'timeout' => 10,
45
+ 'headers' => array('Accept-Encoding' => ''),
46
+ 'sslverify' => false
47
+ )
48
+ );
49
+
50
+ if(is_wp_error($response)) {
51
+ $this->error = $response->get_error_message();
52
+ return false;
53
+ } else {
54
+ $body = wp_remote_retrieve_body($response);
55
+ return json_decode($body);
56
+ }
57
+ }
58
+
59
+ public function has_error() {
60
+ return (!empty($this->error));
61
+ }
62
+
63
+ public function get_error_message()
64
+ {
65
+ if(is_object($this->error)) {
66
+ return $this->error->message;
67
+ }
68
+
69
+ return $this->error;
70
+ }
71
+ }
includes/RFBP_Admin.php CHANGED
@@ -1,171 +1,98 @@
1
  <?php
2
 
3
  class RFBP_Admin {
4
-
5
  public function __construct() {
6
 
7
  global $pagenow;
8
 
9
- add_action('admin_init', array($this, 'register_settings'));
10
- add_action('admin_menu', array($this, 'build_menu'));
11
-
12
- add_filter("plugin_action_links_recent-facebook-posts/recent-facebook-posts.php", array($this, 'add_settings_link'));
13
 
14
- // check expiry date of access token
15
- $expiryDate = get_option('rfb_access_token_expiry_date');
16
- if($expiryDate && (date('Ymd', strtotime("+14 days")) >= $expiryDate)) {
17
- // access token expires in less than 14 days, show admin notice
18
- add_action( 'admin_notices', array($this, 'show_admin_notice') );
19
- }
20
 
21
  // handle requests early, but only on rfb settings page
22
- if(isset($_GET['page']) && $_GET['page'] == 'rfb-settings' ) {
23
 
24
  // load css
25
- add_action('admin_enqueue_scripts', array($this, 'load_css') );
26
-
27
- // maybe renew cache file
28
- if(isset($_POST['renew_cache'])) {
29
- add_action('init', array(RFBP::instance(), 'invalidate_cache'));
30
- add_action('init', array(RFBP::instance(), 'get_posts'));
31
- }
32
-
33
- // maybe to facebook
34
- if(isset($_GET['login_to_fb'])) {
35
- $this->redirect_to_facebook();
36
- }
37
  }
38
  }
39
 
40
- private function redirect_to_facebook()
41
- {
42
- $fb = RFBP::api();
43
- $loginUrl = $fb->getLoginUrl(array('scope' => array('read_stream'), 'redirect_uri' => get_admin_url(null, 'options-general.php?page=rfb-settings&logged_in=1')));
44
-
45
- // check if headers have beent sent, otherwise redirect via JS
46
- if(!headers_sent()) {
47
- header("Location: {$loginUrl}");
48
- exit;
49
- } else {
50
- ?>
51
- <script type="text/javascript">
52
- window.location.href = "<?php echo $loginUrl; ?>";
53
- </script>
54
- <noscript>
55
- <meta http-equiv="refresh" content="0;url=<?php echo $loginUrl; ?>" />
56
- </noscript>
57
- <?php
58
  }
59
  }
60
 
61
- public function get_settings()
62
- {
63
  return RFBP::instance()->get_settings();
64
  }
65
 
66
  public function register_settings() {
67
- register_setting('rfb_settings_group', 'rfb_settings', array($this, 'sanitize_settings'));
68
  }
69
 
70
- public function sanitize_settings($opts)
71
- {
72
- $oldOptions = $this->get_settings();
 
 
73
 
74
- // check to see if page ID has changed
75
- // if so, invalidate cache
76
- if($oldOptions['fb_id'] != $opts['fb_id'] || $opts['img_size'] != $oldOptions['img_size'] || $opts['app_id'] != $oldOptions['app_id'] || $opts['app_secret'] != $oldOptions['app_secret']) {
77
- RFBP::instance()->invalidate_cache();
78
- add_settings_error('rfb_settings', 'cache_invalidated', "Some settings have been changed which invalidated Recent Facebook Posts' cache file. The cache will automatically be updated or you can do it manually." . '<form action="'.admin_url('options-general.php?page=rfb-settings') . '" method="post"><input type="hidden" name="renew_cache" value="1" /><input type="submit" class="button-primary" value="Renew cache file" /></form>', 'updated');
79
  }
80
 
81
- $opts['cache_time'] = (int) $opts['cache_time'];
82
- $opts['img_height'] = (!empty($opts['img_height'])) ? (int) $opts['img_height'] : '';
83
- $opts['img_width'] = (!empty($opts['img_width'])) ? (int) $opts['img_width'] : '';
84
- $opts['load_css'] = (isset($opts['load_css'])) ? 1 : 0;
 
 
 
 
85
  return $opts;
86
  }
87
 
88
  public function build_menu() {
89
- $page = add_options_page('Recent Facebook Posts - Settings','Recent Facebook Posts','manage_options','rfb-settings', array($this, 'settings_page'));
90
  }
91
 
92
  public function load_css() {
93
- wp_enqueue_style( 'rfb_admin_css', plugins_url('recent-facebook-posts/assets/css/admin.css') );
94
- wp_enqueue_script( 'rfb_admin_js', plugins_url('recent-facebook-posts/assets/js/admin.js'), array('jquery'), null, true);
95
  }
96
 
97
- public function settings_page () {
98
 
99
  $opts = $this->get_settings();
100
- $curl = extension_loaded('curl');
101
- $connected = false;
102
-
103
- // only try to connect when curl is installed and app_id is given
104
- if($curl && !empty($opts['app_id'])) {
105
-
106
- $fb = RFBP::api();
107
- $connected = $fb->getUser();
108
 
109
- if($connected) {
110
- try {
111
- $try = $fb->api('/me');
112
- } catch(Exception $e) {
113
- $connected = false;
114
- $apiError = $e;
115
- }
116
- }
117
-
118
- }
119
 
120
  // show user-friendly error message
121
- if(!$curl) { $errorMessage = "This plugin needs the PHP cURL extension installed on your server. Please ask your webhost to enable the php_curl extension."; }
122
- elseif(empty($opts['app_id'])) { $errorMessage = "This plugin needs a valid Application ID to work. Please fill it in below."; }
123
- elseif(empty($opts['app_secret'])) { $errorMessage = "This plugin needs a valid Application Secret to work. Please fill it in below."; }
124
- elseif(!$connected) {
125
- $errorMessage = "The plugin is not connected to Facebook. Please <a class=\"button-primary\" href=\"". admin_url('options-general.php?page=rfb-settings&login_to_fb') ."\">connect</a>.";
126
- } else {
127
- // everything is fine!
128
- $accessToken = $fb->getAccessToken();
129
- update_option('rfb_access_token', $accessToken);
130
-
131
- if(isset($_GET['logged_in'])) {
132
- update_option('rfb_access_token_expiry_date', date("Ymd", strtotime("+60 days")));
133
- $notice = "<strong>Login success!</strong> You succesfully connected the plugin with Facebook.";
134
- } elseif(RFBP::instance()->cache_renewed) { $notice = "<strong>Cache renewed!</strong> You succesfully renewed the cache."; }
135
  }
136
 
137
-
138
- // check if cache directory is writable
139
- $cacheDir = WP_CONTENT_DIR;
140
- $cacheFile = WP_CONTENT_DIR . '/recent-facebook-posts.cache';
141
-
142
- if(!is_writable($cacheDir)) {
143
- $cacheError = 'The wp-content folder (<i>'. WP_CONTENT_DIR .'</i>) is not writable. Please set the folder permissions to 755.';
144
- } elseif(file_exists($cacheFile) && !is_writable($cacheFile)) {
145
- $cacheError = 'The cache file (<i>'. $cacheFile .'</i>) exists but is not writable. Please set the file permissions to 755.';
146
- }
147
-
148
  include_once RFBP_PLUGIN_DIR . 'includes/views/settings_page.html.php';
149
  }
150
 
151
- public function show_admin_notice()
152
- {
153
- ?>
154
- <div class="updated">
155
- <p>Your Facebook access token for <a href="<?php echo admin_url('options-general.php?page-rfb-settings'); ?>">Recent Facebook Posts</a> expires in less than 14 days. Please renew it. <a class="primary-button" href="<?php echo admin_url('options-general.php?page=rfb-settings&login_to_fb'); ?>">Renew token</a></p>
156
- </div>
157
- <?php
 
 
 
 
158
  }
159
-
160
- /**
161
- * Adds the settings link on the plugin's overview page
162
- * @param array $links Array containing all the settings links for the various plugins.
163
- * @return array The new array containing all the settings links
164
- */
165
- function add_settings_link($links) {
166
- $settings_link = '<a href="options-general.php?page=rfb-settings">Settings</a>';
167
- array_unshift($links, $settings_link);
168
-
169
- return $links;
170
- }
171
- }
1
  <?php
2
 
3
  class RFBP_Admin {
4
+
5
  public function __construct() {
6
 
7
  global $pagenow;
8
 
9
+ add_action( 'admin_init', array( $this, 'register_settings' ) );
10
+ add_action( 'admin_menu', array( $this, 'build_menu' ) );
 
 
11
 
12
+ add_filter( "plugin_action_links_recent-facebook-posts/recent-facebook-posts.php", array( $this, 'add_settings_link' ) );
 
 
 
 
 
13
 
14
  // handle requests early, but only on rfb settings page
15
+ if ( isset( $_GET['page'] ) && $_GET['page'] == 'rfbp' ) {
16
 
17
  // load css
18
+ add_action( 'admin_enqueue_scripts', array( $this, 'load_css' ) );
19
+ add_action( 'init', array( $this, 'on_init' ) );
 
 
 
 
 
 
 
 
 
 
20
  }
21
  }
22
 
23
+ public function on_init() {
24
+ // maybe renew cache file
25
+ if ( isset( $_POST['renew_cache'] ) ) {
26
+ delete_transient('rfbp_posts');
27
+ delete_transient('rfbp_posts_fallback');
28
+
29
+ RFBP::instance()->get_posts();
 
 
 
 
 
 
 
 
 
 
 
30
  }
31
  }
32
 
33
+ public function get_settings() {
 
34
  return RFBP::instance()->get_settings();
35
  }
36
 
37
  public function register_settings() {
38
+ register_setting( 'rfb_settings_group', 'rfb_settings', array( $this, 'sanitize_settings' ) );
39
  }
40
 
41
+ public function sanitize_settings( $opts ) {
42
+ $old_opts = $this->get_settings();
43
+ $opts['app_id'] = trim($opts['app_id']);
44
+ $opts['app_secret'] = trim($opts['app_secret']);
45
+ $opts['fb_id'] = trim($opts['fb_id']);
46
 
47
+ if($old_opts['fb_id'] !== $opts['fb_id'] || $old_opts['img_size'] !== $opts['img_size']) {
48
+ add_settings_error('rfbp', 'rfbp-cache-invalid', 'Settings have changed which invalidated the cached Facebook posts. <form action="'. admin_url('options-general.php?page=rfbp') .'" method="post"><input type="hidden" name="renew_cache" value="1" /><input type="submit" class="button-primary" value="Renew Cache" /></form>', "updated");
 
 
 
49
  }
50
 
51
+ if($old_opts['app_id'] !== $opts['app_id'] || !$old_opts['app_secret']) {
52
+ add_settings_error('rfbp', 'rfbp-cache-invalid', 'You changed important settings, please test your new configuration by renewing the Facebook posts cache. <form action="'. admin_url('options-general.php?page=rfbp') .'" method="post"><input type="hidden" name="renew_cache" value="1" /><input type="submit" class="button-primary" value="Renew Cache" /></form>', "updated");
53
+ }
54
+
55
+ $opts['cache_time'] = absint($opts['cache_time']);
56
+ $opts['img_height'] = ( !empty( $opts['img_height'] ) ) ? (int) $opts['img_height'] : '';
57
+ $opts['img_width'] = ( !empty( $opts['img_width'] ) ) ? (int) $opts['img_width'] : '';
58
+ $opts['load_css'] = ( isset( $opts['load_css'] ) ) ? 1 : 0;
59
  return $opts;
60
  }
61
 
62
  public function build_menu() {
63
+ $page = add_options_page( 'Recent Facebook Posts - Settings', 'Recent Facebook Posts', 'manage_options', 'rfbp', array( $this, 'settings_page' ) );
64
  }
65
 
66
  public function load_css() {
67
+ wp_enqueue_style( 'rfb_admin_css', plugins_url( 'recent-facebook-posts/assets/css/admin.css' ) );
68
+ wp_enqueue_script( 'rfb_admin_js', plugins_url( 'recent-facebook-posts/assets/js/admin.js' ), array( 'jquery' ), null, true );
69
  }
70
 
71
+ public function settings_page() {
72
 
73
  $opts = $this->get_settings();
 
 
 
 
 
 
 
 
74
 
75
+ $ready = (!empty($opts['app_id']) && !empty($opts['app_secret']) && !empty($opts['fb_id']));
76
+ $api = RFBP::api();
 
 
 
 
 
 
 
 
77
 
78
  // show user-friendly error message
79
+ if(RFBP::instance()->cache_renewed) {
80
+ $notice = "<strong>Cache renewed!</strong> You succesfully renewed the cache.";
 
 
 
 
 
 
 
 
 
 
 
 
81
  }
82
 
 
 
 
 
 
 
 
 
 
 
 
83
  include_once RFBP_PLUGIN_DIR . 'includes/views/settings_page.html.php';
84
  }
85
 
86
+ /**
87
+ * Adds the settings link on the plugin's overview page
88
+ *
89
+ * @param array $links Array containing all the settings links for the various plugins.
90
+ * @return array The new array containing all the settings links
91
+ */
92
+ function add_settings_link( $links ) {
93
+ $settings_link = '<a href="options-general.php?page=rfbp">Settings</a>';
94
+ array_unshift( $links, $settings_link );
95
+
96
+ return $links;
97
  }
98
+ }
 
 
 
 
 
 
 
 
 
 
 
 
includes/RFBP_Widget.php CHANGED
@@ -27,7 +27,7 @@ class RFBP_Widget extends WP_Widget {
27
  $opts = RFBP::instance()->get_settings();
28
 
29
  if(empty($opts['app_id'])) { ?>
30
- <p style="color:red;">You'll need to <a href="<?php echo admin_url('options-general.php?page=rfb-settings'); ?>">configure Recent Facebook Posts</a> first before this will work.</p>
31
  <?php } ?>
32
  <p>
33
  <label for="<?php echo $this->get_field_id( 'title' ); ?>"><?php _e( 'Title:' ); ?></label>
@@ -59,7 +59,7 @@ class RFBP_Widget extends WP_Widget {
59
  <label for="<?php echo $this->get_field_id( 'show_link' ); ?>"><?php _e( 'Show a link to Facebook page?' ); ?></label>
60
  </p>
61
 
62
- <p style="background: #222; color:#eee; padding:10px; ">I spent countless hours developing (and offering support) for this plugin for FREE. If you like it, consider <a href="http://dannyvankooten.com/donate/">donating $10, $20 or $50</a> as a token of your appreciation.</p>
63
 
64
  <?php
65
  }
27
  $opts = RFBP::instance()->get_settings();
28
 
29
  if(empty($opts['app_id'])) { ?>
30
+ <p style="color:red;">You'll need to <a href="<?php echo admin_url('options-general.php?page=rfbp'); ?>">configure Recent Facebook Posts</a> first before this will work.</p>
31
  <?php } ?>
32
  <p>
33
  <label for="<?php echo $this->get_field_id( 'title' ); ?>"><?php _e( 'Title:' ); ?></label>
59
  <label for="<?php echo $this->get_field_id( 'show_link' ); ?>"><?php _e( 'Show a link to Facebook page?' ); ?></label>
60
  </p>
61
 
62
+ <p style="background: #222; color:#eee; padding:10px; ">If you like this plugin, consider <a href="http://dannyvankooten.com/donate/">donating $10, $20 or $50</a> as a token of your appreciation.</p>
63
 
64
  <?php
65
  }
includes/facebook-php-sdk/base_facebook.php DELETED
@@ -1,1458 +0,0 @@
1
- <?php
2
- /**
3
- * Copyright 2011 Facebook, Inc.
4
- *
5
- * Licensed under the Apache License, Version 2.0 (the "License"); you may
6
- * not use this file except in compliance with the License. You may obtain
7
- * a copy of the License at
8
- *
9
- * http://www.apache.org/licenses/LICENSE-2.0
10
- *
11
- * Unless required by applicable law or agreed to in writing, software
12
- * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
13
- * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
14
- * License for the specific language governing permissions and limitations
15
- * under the License.
16
- */
17
-
18
- if (!function_exists('curl_init')) {
19
- throw new Exception('Facebook needs the CURL PHP extension.');
20
- }
21
- if (!function_exists('json_decode')) {
22
- throw new Exception('Facebook needs the JSON PHP extension.');
23
- }
24
-
25
- /**
26
- * Thrown when an API call returns an exception.
27
- *
28
- * @author Naitik Shah <naitik@facebook.com>
29
- */
30
- class FacebookApiException extends Exception
31
- {
32
- /**
33
- * The result from the API server that represents the exception information.
34
- */
35
- protected $result;
36
-
37
- /**
38
- * Make a new API Exception with the given result.
39
- *
40
- * @param array $result The result from the API server
41
- */
42
- public function __construct($result) {
43
- $this->result = $result;
44
-
45
- $code = isset($result['error_code']) ? $result['error_code'] : 0;
46
-
47
- if (isset($result['error_description'])) {
48
- // OAuth 2.0 Draft 10 style
49
- $msg = $result['error_description'];
50
- } else if (isset($result['error']) && is_array($result['error'])) {
51
- // OAuth 2.0 Draft 00 style
52
- $msg = $result['error']['message'];
53
- } else if (isset($result['error_msg'])) {
54
- // Rest server style
55
- $msg = $result['error_msg'];
56
- } else {
57
- $msg = 'Unknown Error. Check getResult()';
58
- }
59
-
60
- parent::__construct($msg, $code);
61
- }
62
-
63
- /**
64
- * Return the associated result object returned by the API server.
65
- *
66
- * @return array The result from the API server
67
- */
68
- public function getResult() {
69
- return $this->result;
70
- }
71
-
72
- /**
73
- * Returns the associated type for the error. This will default to
74
- * 'Exception' when a type is not available.
75
- *
76
- * @return string
77
- */
78
- public function getType() {
79
- if (isset($this->result['error'])) {
80
- $error = $this->result['error'];
81
- if (is_string($error)) {
82
- // OAuth 2.0 Draft 10 style
83
- return $error;
84
- } else if (is_array($error)) {
85
- // OAuth 2.0 Draft 00 style
86
- if (isset($error['type'])) {
87
- return $error['type'];
88
- }
89
- }
90
- }
91
-
92
- return 'Exception';
93
- }
94
-
95
- /**
96
- * To make debugging easier.
97
- *
98
- * @return string The string representation of the error
99
- */
100
- public function __toString() {
101
- $str = $this->getType() . ': ';
102
- if ($this->code != 0) {
103
- $str .= $this->code . ': ';
104
- }
105
- return $str . $this->message;
106
- }
107
- }
108
-
109
- /**
110
- * Provides access to the Facebook Platform. This class provides
111
- * a majority of the functionality needed, but the class is abstract
112
- * because it is designed to be sub-classed. The subclass must
113
- * implement the four abstract methods listed at the bottom of
114
- * the file.
115
- *
116
- * @author Naitik Shah <naitik@facebook.com>
117
- */
118
- abstract class BaseFacebook
119
- {
120
- /**
121
- * Version.
122
- */
123
- const VERSION = '3.2.2';
124
-
125
- /**
126
- * Signed Request Algorithm.
127
- */
128
- const SIGNED_REQUEST_ALGORITHM = 'HMAC-SHA256';
129
-
130
- /**
131
- * Default options for curl.
132
- */
133
- public static $CURL_OPTS = array(
134
- CURLOPT_CONNECTTIMEOUT => 10,
135
- CURLOPT_RETURNTRANSFER => true,
136
- CURLOPT_TIMEOUT => 60,
137
- CURLOPT_USERAGENT => 'facebook-php-3.2',
138
- );
139
-
140
- /**
141
- * List of query parameters that get automatically dropped when rebuilding
142
- * the current URL.
143
- */
144
- protected static $DROP_QUERY_PARAMS = array(
145
- 'code',
146
- 'state',
147
- 'signed_request',
148
- );
149
-
150
- /**
151
- * Maps aliases to Facebook domains.
152
- */
153
- public static $DOMAIN_MAP = array(
154
- 'api' => 'https://api.facebook.com/',
155
- 'api_video' => 'https://api-video.facebook.com/',
156
- 'api_read' => 'https://api-read.facebook.com/',
157
- 'graph' => 'https://graph.facebook.com/',
158
- 'graph_video' => 'https://graph-video.facebook.com/',
159
- 'www' => 'https://www.facebook.com/',
160
- );
161
-
162
- /**
163
- * The Application ID.
164
- *
165
- * @var string
166
- */
167
- protected $appId;
168
-
169
- /**
170
- * The Application App Secret.
171
- *
172
- * @var string
173
- */
174
- protected $appSecret;
175
-
176
- /**
177
- * The ID of the Facebook user, or 0 if the user is logged out.
178
- *
179
- * @var integer
180
- */
181
- protected $user;
182
-
183
- /**
184
- * The data from the signed_request token.
185
- */
186
- protected $signedRequest;
187
-
188
- /**
189
- * A CSRF state variable to assist in the defense against CSRF attacks.
190
- */
191
- protected $state;
192
-
193
- /**
194
- * The OAuth access token received in exchange for a valid authorization
195
- * code. null means the access token has yet to be determined.
196
- *
197
- * @var string
198
- */
199
- protected $accessToken = null;
200
-
201
- /**
202
- * Indicates if the CURL based @ syntax for file uploads is enabled.
203
- *
204
- * @var boolean
205
- */
206
- protected $fileUploadSupport = false;
207
-
208
- /**
209
- * Indicates if we trust HTTP_X_FORWARDED_* headers.
210
- *
211
- * @var boolean
212
- */
213
- protected $trustForwarded = false;
214
-
215
- /**
216
- * Initialize a Facebook Application.
217
- *
218
- * The configuration:
219
- * - appId: the application ID
220
- * - secret: the application secret
221
- * - fileUpload: (optional) boolean indicating if file uploads are enabled
222
- *
223
- * @param array $config The application configuration
224
- */
225
- public function __construct($config) {
226
- $this->setAppId($config['appId']);
227
- $this->setAppSecret($config['secret']);
228
- if (isset($config['fileUpload'])) {
229
- $this->setFileUploadSupport($config['fileUpload']);
230
- }
231
- if (isset($config['trustForwarded']) && $config['trustForwarded']) {
232
- $this->trustForwarded = true;
233
- }
234
- $state = $this->getPersistentData('state');
235
- if (!empty($state)) {
236
- $this->state = $state;
237
- }
238
- }
239
-
240
- /**
241
- * Set the Application ID.
242
- *
243
- * @param string $appId The Application ID
244
- * @return BaseFacebook
245
- */
246
- public function setAppId($appId) {
247
- $this->appId = $appId;
248
- return $this;
249
- }
250
-
251
- /**
252
- * Get the Application ID.
253
- *
254
- * @return string the Application ID
255
- */
256
- public function getAppId() {
257
- return $this->appId;
258
- }
259
-
260
- /**
261
- * Set the App Secret.
262
- *
263
- * @param string $apiSecret The App Secret
264
- * @return BaseFacebook
265
- * @deprecated
266
- */
267
- public function setApiSecret($apiSecret) {
268
- $this->setAppSecret($apiSecret);
269
- return $this;
270
- }
271
-
272
- /**
273
- * Set the App Secret.
274
- *
275
- * @param string $appSecret The App Secret
276
- * @return BaseFacebook
277
- */
278
- public function setAppSecret($appSecret) {
279
- $this->appSecret = $appSecret;
280
- return $this;
281
- }
282
-
283
- /**
284
- * Get the App Secret.
285
- *
286
- * @return string the App Secret
287
- * @deprecated
288
- */
289
- public function getApiSecret() {
290
- return $this->getAppSecret();
291
- }
292
-
293
- /**
294
- * Get the App Secret.
295
- *
296
- * @return string the App Secret
297
- */
298
- public function getAppSecret() {
299
- return $this->appSecret;
300
- }
301
-
302
- /**
303
- * Set the file upload support status.
304
- *
305
- * @param boolean $fileUploadSupport The file upload support status.
306
- * @return BaseFacebook
307
- */
308
- public function setFileUploadSupport($fileUploadSupport) {
309
- $this->fileUploadSupport = $fileUploadSupport;
310
- return $this;
311
- }
312
-
313
- /**
314
- * Get the file upload support status.
315
- *
316
- * @return boolean true if and only if the server supports file upload.
317
- */
318
- public function getFileUploadSupport() {
319
- return $this->fileUploadSupport;
320
- }
321
-
322
- /**
323
- * DEPRECATED! Please use getFileUploadSupport instead.
324
- *
325
- * Get the file upload support status.
326
- *
327
- * @return boolean true if and only if the server supports file upload.
328
- */
329
- public function useFileUploadSupport() {
330
- return $this->getFileUploadSupport();
331
- }
332
-
333
- /**
334
- * Sets the access token for api calls. Use this if you get
335
- * your access token by other means and just want the SDK
336
- * to use it.
337
- *
338
- * @param string $access_token an access token.
339
- * @return BaseFacebook
340
- */
341
- public function setAccessToken($access_token) {
342
- $this->accessToken = $access_token;
343
- return $this;
344
- }
345
-
346
- /**
347
- * Extend an access token, while removing the short-lived token that might
348
- * have been generated via client-side flow. Thanks to http://bit.ly/b0Pt0H
349
- * for the workaround.
350
- */
351
- public function setExtendedAccessToken() {
352
- try {
353
- // need to circumvent json_decode by calling _oauthRequest
354
- // directly, since response isn't JSON format.
355
- $access_token_response = $this->_oauthRequest(
356
- $this->getUrl('graph', '/oauth/access_token'),
357
- $params = array(
358
- 'client_id' => $this->getAppId(),
359
- 'client_secret' => $this->getAppSecret(),
360
- 'grant_type' => 'fb_exchange_token',
361
- 'fb_exchange_token' => $this->getAccessToken(),
362
- )
363
- );
364
- }
365
- catch (FacebookApiException $e) {
366
- // most likely that user very recently revoked authorization.
367
- // In any event, we don't have an access token, so say so.
368
- return false;
369
- }
370
-
371
- if (empty($access_token_response)) {
372
- return false;
373
- }
374
-
375
- $response_params = array();
376
- parse_str($access_token_response, $response_params);
377
-
378
- if (!isset($response_params['access_token'])) {
379
- return false;
380
- }
381
-
382
- $this->destroySession();
383
-
384
- $this->setPersistentData(
385
- 'access_token', $response_params['access_token']
386
- );
387
- }
388
-
389
- /**
390
- * Determines the access token that should be used for API calls.
391
- * The first time this is called, $this->accessToken is set equal
392
- * to either a valid user access token, or it's set to the application
393
- * access token if a valid user access token wasn't available. Subsequent
394
- * calls return whatever the first call returned.
395
- *
396
- * @return string The access token
397
- */
398
- public function getAccessToken() {
399
- if ($this->accessToken !== null) {
400
- // we've done this already and cached it. Just return.
401
- return $this->accessToken;
402
- }
403
-
404
- // first establish access token to be the application
405
- // access token, in case we navigate to the /oauth/access_token
406
- // endpoint, where SOME access token is required.
407
- $this->setAccessToken($this->getApplicationAccessToken());
408
- $user_access_token = $this->getUserAccessToken();
409
- if ($user_access_token) {
410
- $this->setAccessToken($user_access_token);
411
- }
412
-
413
- return $this->accessToken;
414
- }
415
-
416
- /**
417
- * Determines and returns the user access token, first using
418
- * the signed request if present, and then falling back on
419
- * the authorization code if present. The intent is to
420
- * return a valid user access token, or false if one is determined
421
- * to not be available.
422
- *
423
- * @return string A valid user access token, or false if one
424
- * could not be determined.
425
- */
426
- protected function getUserAccessToken() {
427
- // first, consider a signed request if it's supplied.
428
- // if there is a signed request, then it alone determines
429
- // the access token.
430
- $signed_request = $this->getSignedRequest();
431
- if ($signed_request) {
432
- // apps.facebook.com hands the access_token in the signed_request
433
- if (array_key_exists('oauth_token', $signed_request)) {
434
- $access_token = $signed_request['oauth_token'];
435
- $this->setPersistentData('access_token', $access_token);
436
- return $access_token;
437
- }
438
-
439
- // the JS SDK puts a code in with the redirect_uri of ''
440
- if (array_key_exists('code', $signed_request)) {
441
- $code = $signed_request['code'];
442
- if ($code && $code == $this->getPersistentData('code')) {
443
- // short-circuit if the code we have is the same as the one presented
444
- return $this->getPersistentData('access_token');
445
- }
446
-
447
- $access_token = $this->getAccessTokenFromCode($code, '');
448
- if ($access_token) {
449
- $this->setPersistentData('code', $code);
450
- $this->setPersistentData('access_token', $access_token);
451
- return $access_token;
452
- }
453
- }
454
-
455
- // signed request states there's no access token, so anything
456
- // stored should be cleared.
457
- $this->clearAllPersistentData();
458
- return false; // respect the signed request's data, even
459
- // if there's an authorization code or something else
460
- }
461
-
462
- $code = $this->getCode();
463
- if ($code && $code != $this->getPersistentData('code')) {
464
- $access_token = $this->getAccessTokenFromCode($code);
465
- if ($access_token) {
466
- $this->setPersistentData('code', $code);
467
- $this->setPersistentData('access_token', $access_token);
468
- return $access_token;
469
- }
470
-
471
- // code was bogus, so everything based on it should be invalidated.
472
- $this->clearAllPersistentData();
473
- return false;
474
- }
475
-
476
- // as a fallback, just return whatever is in the persistent
477
- // store, knowing nothing explicit (signed request, authorization
478
- // code, etc.) was present to shadow it (or we saw a code in $_REQUEST,
479
- // but it's the same as what's in the persistent store)
480
- return $this->getPersistentData('access_token');
481
- }
482
-
483
- /**
484
- * Retrieve the signed request, either from a request parameter or,
485
- * if not present, from a cookie.
486
- *
487
- * @return string the signed request, if available, or null otherwise.
488
- */
489
- public function getSignedRequest() {
490
- if (!$this->signedRequest) {
491
- if (!empty($_REQUEST['signed_request'])) {
492
- $this->signedRequest = $this->parseSignedRequest(
493
- $_REQUEST['signed_request']);
494
- } else if (!empty($_COOKIE[$this->getSignedRequestCookieName()])) {
495
- $this->signedRequest = $this->parseSignedRequest(
496
- $_COOKIE[$this->getSignedRequestCookieName()]);
497
- }
498
- }
499
- return $this->signedRequest;
500
- }
501
-
502
- /**
503
- * Get the UID of the connected user, or 0
504
- * if the Facebook user is not connected.
505
- *
506
- * @return string the UID if available.
507
- */
508
- public function getUser() {
509
- if ($this->user !== null) {
510
- // we've already determined this and cached the value.
511
- return $this->user;
512
- }
513
-
514
- return $this->user = $this->getUserFromAvailableData();
515
- }
516
-
517
- /**
518
- * Determines the connected user by first examining any signed
519
- * requests, then considering an authorization code, and then
520
- * falling back to any persistent store storing the user.
521
- *
522
- * @return integer The id of the connected Facebook user,
523
- * or 0 if no such user exists.
524
- */
525
- protected function getUserFromAvailableData() {
526
- // if a signed request is supplied, then it solely determines
527
- // who the user is.
528
- $signed_request = $this->getSignedRequest();
529
- if ($signed_request) {
530
- if (array_key_exists('user_id', $signed_request)) {
531
- $user = $signed_request['user_id'];
532
-
533
- if($user != $this->getPersistentData('user_id')){
534
- $this->clearAllPersistentData();
535
- }
536
-
537
- $this->setPersistentData('user_id', $signed_request['user_id']);
538
- return $user;
539
- }
540
-
541
- // if the signed request didn't present a user id, then invalidate
542
- // all entries in any persistent store.
543
- $this->clearAllPersistentData();
544
- return 0;
545
- }
546
-
547
- $user = $this->getPersistentData('user_id', $default = 0);
548
- $persisted_access_token = $this->getPersistentData('access_token');
549
-
550
- // use access_token to fetch user id if we have a user access_token, or if
551
- // the cached access token has changed.
552
- $access_token = $this->getAccessToken();
553
- if ($access_token &&
554
- $access_token != $this->getApplicationAccessToken() &&
555
- !($user && $persisted_access_token == $access_token)) {
556
- $user = $this->getUserFromAccessToken();
557
- if ($user) {
558
- $this->setPersistentData('user_id', $user);
559
- } else {
560
- $this->clearAllPersistentData();
561
- }
562
- }
563
-
564
- return $user;
565
- }
566
-
567
- /**
568
- * Get a Login URL for use with redirects. By default, full page redirect is
569
- * assumed. If you are using the generated URL with a window.open() call in
570
- * JavaScript, you can pass in display=popup as part of the $params.
571
- *
572
- * The parameters:
573
- * - redirect_uri: the url to go to after a successful login
574
- * - scope: comma separated list of requested extended perms
575
- *
576
- * @param array $params Provide custom parameters
577
- * @return string The URL for the login flow
578
- */
579
- public function getLoginUrl($params=array()) {
580
- $this->establishCSRFTokenState();
581
- $currentUrl = $this->getCurrentUrl();
582
-
583
- // if 'scope' is passed as an array, convert to comma separated list
584
- $scopeParams = isset($params['scope']) ? $params['scope'] : null;
585
- if ($scopeParams && is_array($scopeParams)) {
586
- $params['scope'] = implode(',', $scopeParams);
587
- }
588
-
589
- return $this->getUrl(
590
- 'www',
591
- 'dialog/oauth',
592
- array_merge(array(
593
- 'client_id' => $this->getAppId(),
594
- 'redirect_uri' => $currentUrl, // possibly overwritten
595
- 'state' => $this->state),
596
- $params));
597
- }
598
-
599
- /**
600
- * Get a Logout URL suitable for use with redirects.
601
- *
602
- * The parameters:
603
- * - next: the url to go to after a successful logout
604
- *
605
- * @param array $params Provide custom parameters
606
- * @return string The URL for the logout flow
607
- */
608
- public function getLogoutUrl($params=array()) {
609
- return $this->getUrl(
610
- 'www',
611
- 'logout.php',
612
- array_merge(array(
613
- 'next' => $this->getCurrentUrl(),
614
- 'access_token' => $this->getUserAccessToken(),
615
- ), $params)
616
- );
617
- }
618
-
619
- /**
620
- * Get a login status URL to fetch the status from Facebook.
621
- *
622
- * The parameters:
623
- * - ok_session: the URL to go to if a session is found
624
- * - no_session: the URL to go to if the user is not connected
625
- * - no_user: the URL to go to if the user is not signed into facebook
626
- *
627
- * @param array $params Provide custom parameters
628
- * @return string The URL for the logout flow
629
- */
630
- public function getLoginStatusUrl($params=array()) {
631
- return $this->getUrl(
632
- 'www',
633
- 'extern/login_status.php',
634
- array_merge(array(
635
- 'api_key' => $this->getAppId(),
636
- 'no_session' => $this->getCurrentUrl(),
637
- 'no_user' => $this->getCurrentUrl(),
638
- 'ok_session' => $this->getCurrentUrl(),
639
- 'session_version' => 3,
640
- ), $params)
641
- );
642
- }
643
-
644
- /**
645
- * Make an API call.
646
- *
647
- * @return mixed The decoded response
648
- */
649
- public function api(/* polymorphic */) {
650
- $args = func_get_args();
651
- if (is_array($args[0])) {
652
- return $this->_restserver($args[0]);
653
- } else {
654
- return call_user_func_array(array($this, '_graph'), $args);
655
- }
656
- }
657
-
658
- /**
659
- * Constructs and returns the name of the cookie that
660
- * potentially houses the signed request for the app user.
661
- * The cookie is not set by the BaseFacebook class, but
662
- * it may be set by the JavaScript SDK.
663
- *
664
- * @return string the name of the cookie that would house
665
- * the signed request value.
666
- */
667
- protected function getSignedRequestCookieName() {
668
- return 'fbsr_'.$this->getAppId();
669
- }
670
-
671
- /**
672
- * Constructs and returns the name of the coookie that potentially contain
673
- * metadata. The cookie is not set by the BaseFacebook class, but it may be
674
- * set by the JavaScript SDK.
675
- *
676
- * @return string the name of the cookie that would house metadata.
677
- */
678
- protected function getMetadataCookieName() {
679
- return 'fbm_'.$this->getAppId();
680
- }
681
-
682
- /**
683
- * Get the authorization code from the query parameters, if it exists,
684
- * and otherwise return false to signal no authorization code was
685
- * discoverable.
686
- *
687
- * @return mixed The authorization code, or false if the authorization
688
- * code could not be determined.
689
- */
690
- protected function getCode() {
691
- if (isset($_REQUEST['code'])) {
692
- if ($this->state !== null &&
693
- isset($_REQUEST['state']) &&
694
- $this->state === $_REQUEST['state']) {
695
-
696
- // CSRF state has done its job, so clear it
697
- $this->state = null;
698
- $this->clearPersistentData('state');
699
- return $_REQUEST['code'];
700
- } else {
701
- self::errorLog('CSRF state token does not match one provided.');
702
- return false;
703
- }
704
- }
705
-
706
- return false;
707
- }
708
-
709
- /**
710
- * Retrieves the UID with the understanding that
711
- * $this->accessToken has already been set and is
712
- * seemingly legitimate. It relies on Facebook's Graph API
713
- * to retrieve user information and then extract
714
- * the user ID.
715
- *
716
- * @return integer Returns the UID of the Facebook user, or 0
717
- * if the Facebook user could not be determined.
718
- */
719
- protected function getUserFromAccessToken() {
720
- try {
721
- $user_info = $this->api('/me');
722
- return $user_info['id'];
723
- } catch (FacebookApiException $e) {
724
- return 0;
725
- }
726
- }
727
-
728
- /**
729
- * Returns the access token that should be used for logged out
730
- * users when no authorization code is available.
731
- *
732
- * @return string The application access token, useful for gathering
733
- * public information about users and applications.
734
- */
735
- protected function getApplicationAccessToken() {
736
- return $this->appId.'|'.$this->appSecret;
737
- }
738
-
739
- /**
740
- * Lays down a CSRF state token for this process.
741
- *
742
- * @return void
743
- */
744
- protected function establishCSRFTokenState() {
745
- if ($this->state === null) {
746
- $this->state = md5(uniqid(mt_rand(), true));
747
- $this->setPersistentData('state', $this->state);
748
- }
749
- }
750
-
751
- /**
752
- * Retrieves an access token for the given authorization code
753
- * (previously generated from www.facebook.com on behalf of
754
- * a specific user). The authorization code is sent to graph.facebook.com
755
- * and a legitimate access token is generated provided the access token
756
- * and the user for which it was generated all match, and the user is
757
- * either logged in to Facebook or has granted an offline access permission.
758
- *
759
- * @param string $code An authorization code.
760
- * @return mixed An access token exchanged for the authorization code, or
761
- * false if an access token could not be generated.
762
- */
763
- protected function getAccessTokenFromCode($code, $redirect_uri = null) {
764
- if (empty($code)) {
765
- return false;
766
- }
767
-
768
- if ($redirect_uri === null) {
769
- $redirect_uri = $this->getCurrentUrl();
770
- }
771
-
772
- try {
773
- // need to circumvent json_decode by calling _oauthRequest
774
- // directly, since response isn't JSON format.
775
- $access_token_response =
776
- $this->_oauthRequest(
777
- $this->getUrl('graph', '/oauth/access_token'),
778
- $params = array('client_id' => $this->getAppId(),
779
- 'client_secret' => $this->getAppSecret(),
780
- 'redirect_uri' => $redirect_uri,
781
- 'code' => $code));
782
- } catch (FacebookApiException $e) {
783
- // most likely that user very recently revoked authorization.
784
- // In any event, we don't have an access token, so say so.
785
- return false;
786
- }
787
-
788
- if (empty($access_token_response)) {
789
- return false;
790
- }
791
-
792
- $response_params = array();
793
- parse_str($access_token_response, $response_params);
794
- if (!isset($response_params['access_token'])) {
795
- return false;
796
- }
797
-
798
- return $response_params['access_token'];
799
- }
800
-
801
- /**
802
- * Invoke the old restserver.php endpoint.
803
- *
804
- * @param array $params Method call object
805
- *
806
- * @return mixed The decoded response object
807
- * @throws FacebookApiException
808
- */
809
- protected function _restserver($params) {
810
- // generic application level parameters
811
- $params['api_key'] = $this->getAppId();
812
- $params['format'] = 'json-strings';
813
-
814
- $result = json_decode($this->_oauthRequest(
815
- $this->getApiUrl($params['method']),
816
- $params
817
- ), true);
818
-
819
- // results are returned, errors are thrown
820
- if (is_array($result) && isset($result['error_code'])) {
821
- $this->throwAPIException($result);
822
- // @codeCoverageIgnoreStart
823
- }
824
- // @codeCoverageIgnoreEnd
825
-
826
- $method = strtolower($params['method']);
827
- if ($method === 'auth.expiresession' ||
828
- $method === 'auth.revokeauthorization') {
829
- $this->destroySession();
830
- }
831
-
832
- return $result;
833
- }
834
-
835
- /**
836
- * Return true if this is video post.
837
- *
838
- * @param string $path The path
839
- * @param string $method The http method (default 'GET')
840
- *
841
- * @return boolean true if this is video post
842
- */
843
- protected function isVideoPost($path, $method = 'GET') {
844
- if ($method == 'POST' && preg_match("/^(\/)(.+)(\/)(videos)$/", $path)) {
845
- return true;
846
- }
847
- return false;
848
- }
849
-
850
- /**
851
- * Invoke the Graph API.
852
- *
853
- * @param string $path The path (required)
854
- * @param string $method The http method (default 'GET')
855
- * @param array $params The query/post data
856
- *
857
- * @return mixed The decoded response object
858
- * @throws FacebookApiException
859
- */
860
- protected function _graph($path, $method = 'GET', $params = array()) {
861
- if (is_array($method) && empty($params)) {
862
- $params = $method;
863
- $method = 'GET';
864
- }
865
- $params['method'] = $method; // method override as we always do a POST
866
-
867
- if ($this->isVideoPost($path, $method)) {
868
- $domainKey = 'graph_video';
869
- } else {
870
- $domainKey = 'graph';
871
- }
872
-
873
- $result = json_decode($this->_oauthRequest(
874
- $this->getUrl($domainKey, $path),
875
- $params
876
- ), true);
877
-
878
- // results are returned, errors are thrown
879
- if (is_array($result) && isset($result['error'])) {
880
- $this->throwAPIException($result);
881
- // @codeCoverageIgnoreStart
882
- }
883
- // @codeCoverageIgnoreEnd
884
-
885
- return $result;
886
- }
887
-
888
- /**
889
- * Make a OAuth Request.
890
- *
891
- * @param string $url The path (required)
892
- * @param array $params The query/post data
893
- *
894
- * @return string The decoded response object
895
- * @throws FacebookApiException
896
- */
897
- protected function _oauthRequest($url, $params) {
898
- if (!isset($params['access_token'])) {
899
- $params['access_token'] = $this->getAccessToken();
900
- }
901
-
902
- if (isset($params['access_token'])) {
903
- $params['appsecret_proof'] = $this->getAppSecretProof($params['access_token']);
904
- }
905
-
906
- // json_encode all params values that are not strings
907
- foreach ($params as $key => $value) {
908
- if (!is_string($value)) {
909
- $params[$key] = json_encode($value);
910
- }
911
- }
912
-
913
- return $this->makeRequest($url, $params);
914
- }
915
-
916
- /**
917
- * Generate a proof of App Secret
918
- * This is required for all API calls originating from a server
919
- * It is a sha256 hash of the access_token made using the app secret
920
- *
921
- * @param string $access_token The access_token to be hashed (required)
922
- *
923
- * @return string The sha256 hash of the access_token
924
- */
925
- protected function getAppSecretProof($access_token) {
926
- return hash_hmac('sha256', $access_token, $this->getAppSecret());
927
- }
928
-
929
- /**
930
- * Makes an HTTP request. This method can be overridden by subclasses if
931
- * developers want to do fancier things or use something other than curl to
932
- * make the request.
933
- *
934
- * @param string $url The URL to make the request to
935
- * @param array $params The parameters to use for the POST body
936
- * @param CurlHandler $ch Initialized curl handle
937
- *
938
- * @return string The response text
939
- */
940
- protected function makeRequest($url, $params, $ch=null) {
941
- if (!$ch) {
942
- $ch = curl_init();
943
- }
944
-
945
- $opts = self::$CURL_OPTS;
946
- if ($this->getFileUploadSupport()) {
947
- $opts[CURLOPT_POSTFIELDS] = $params;
948
- } else {
949
- $opts[CURLOPT_POSTFIELDS] = http_build_query($params, null, '&');
950
- }
951
- $opts[CURLOPT_URL] = $url;
952
-
953
- // disable the 'Expect: 100-continue' behaviour. This causes CURL to wait
954
- // for 2 seconds if the server does not support this header.
955
- if (isset($opts[CURLOPT_HTTPHEADER])) {
956
- $existing_headers = $opts[CURLOPT_HTTPHEADER];
957
- $existing_headers[] = 'Expect:';
958
- $opts[CURLOPT_HTTPHEADER] = $existing_headers;
959
- } else {
960
- $opts[CURLOPT_HTTPHEADER] = array('Expect:');
961
- }
962
-
963
- curl_setopt_array($ch, $opts);
964
- $result = curl_exec($ch);
965
-
966
- if (curl_errno($ch) == 60) { // CURLE_SSL_CACERT
967
- self::errorLog('Invalid or no certificate authority found, '.
968
- 'using bundled information');
969
- curl_setopt($ch, CURLOPT_CAINFO,
970
- dirname(__FILE__) . '/fb_ca_chain_bundle.crt');
971
- $result = curl_exec($ch);
972
- }
973
-
974
- // With dual stacked DNS responses, it's possible for a server to
975
- // have IPv6 enabled but not have IPv6 connectivity. If this is
976
- // the case, curl will try IPv4 first and if that fails, then it will
977
- // fall back to IPv6 and the error EHOSTUNREACH is returned by the
978
- // operating system.
979
- if ($result === false && empty($opts[CURLOPT_IPRESOLVE])) {
980
- $matches = array();
981
- $regex = '/Failed to connect to ([^:].*): Network is unreachable/';
982
- if (preg_match($regex, curl_error($ch), $matches)) {
983
- if (strlen(@inet_pton($matches[1])) === 16) {
984
- self::errorLog('Invalid IPv6 configuration on server, '.
985
- 'Please disable or get native IPv6 on your server.');
986
- self::$CURL_OPTS[CURLOPT_IPRESOLVE] = CURL_IPRESOLVE_V4;
987
- curl_setopt($ch, CURLOPT_IPRESOLVE, CURL_IPRESOLVE_V4);
988
- $result = curl_exec($ch);
989
- }
990
- }
991
- }
992
-
993
- if ($result === false) {
994
- $e = new FacebookApiException(array(
995
- 'error_code' => curl_errno($ch),
996
- 'error' => array(
997
- 'message' => curl_error($ch),
998
- 'type' => 'CurlException',
999
- ),
1000
- ));
1001
- curl_close($ch);
1002
- throw $e;
1003
- }
1004
- curl_close($ch);
1005
- return $result;
1006
- }
1007
-
1008
- /**
1009
- * Parses a signed_request and validates the signature.
1010
- *
1011
- * @param string $signed_request A signed token
1012
- * @return array The payload inside it or null if the sig is wrong
1013
- */
1014
- protected function parseSignedRequest($signed_request) {
1015
- list($encoded_sig, $payload) = explode('.', $signed_request, 2);
1016
-
1017
- // decode the data
1018
- $sig = self::base64UrlDecode($encoded_sig);
1019
- $data = json_decode(self::base64UrlDecode($payload), true);
1020
-
1021
- if (strtoupper($data['algorithm']) !== self::SIGNED_REQUEST_ALGORITHM) {
1022
- self::errorLog(
1023
- 'Unknown algorithm. Expected ' . self::SIGNED_REQUEST_ALGORITHM);
1024
- return null;
1025
- }
1026
-
1027
- // check sig
1028
- $expected_sig = hash_hmac('sha256', $payload,
1029
- $this->getAppSecret(), $raw = true);
1030
- if ($sig !== $expected_sig) {
1031
- self::errorLog('Bad Signed JSON signature!');
1032
- return null;
1033
- }
1034
-
1035
- return $data;
1036
- }
1037
-
1038
- /**
1039
- * Makes a signed_request blob using the given data.
1040
- *
1041
- * @param array The data array.
1042
- * @return string The signed request.
1043
- */
1044
- protected function makeSignedRequest($data) {
1045
- if (!is_array($data)) {
1046
- throw new InvalidArgumentException(
1047
- 'makeSignedRequest expects an array. Got: ' . print_r($data, true));
1048
- }
1049
- $data['algorithm'] = self::SIGNED_REQUEST_ALGORITHM;
1050
- $data['issued_at'] = time();
1051
- $json = json_encode($data);
1052
- $b64 = self::base64UrlEncode($json);
1053
- $raw_sig = hash_hmac('sha256', $b64, $this->getAppSecret(), $raw = true);
1054
- $sig = self::base64UrlEncode($raw_sig);
1055
- return $sig.'.'.$b64;
1056
- }
1057
-
1058
- /**
1059
- * Build the URL for api given parameters.
1060
- *
1061
- * @param $method String the method name.
1062
- * @return string The URL for the given parameters
1063
- */
1064
- protected function getApiUrl($method) {
1065
- static $READ_ONLY_CALLS =
1066
- array('admin.getallocation' => 1,
1067
- 'admin.getappproperties' => 1,
1068
- 'admin.getbannedusers' => 1,
1069
- 'admin.getlivestreamvialink' => 1,
1070
- 'admin.getmetrics' => 1,
1071
- 'admin.getrestrictioninfo' => 1,
1072
- 'application.getpublicinfo' => 1,
1073
- 'auth.getapppublickey' => 1,
1074
- 'auth.getsession' => 1,
1075
- 'auth.getsignedpublicsessiondata' => 1,
1076
- 'comments.get' => 1,
1077
- 'connect.getunconnectedfriendscount' => 1,
1078
- 'dashboard.getactivity' => 1,
1079
- 'dashboard.getcount' => 1,
1080
- 'dashboard.getglobalnews' => 1,
1081
- 'dashboard.getnews' => 1,
1082
- 'dashboard.multigetcount' => 1,
1083
- 'dashboard.multigetnews' => 1,
1084
- 'data.getcookies' => 1,
1085
- 'events.get' => 1,
1086
- 'events.getmembers' => 1,
1087
- 'fbml.getcustomtags' => 1,
1088
- 'feed.getappfriendstories' => 1,
1089
- 'feed.getregisteredtemplatebundlebyid' => 1,
1090
- 'feed.getregisteredtemplatebundles' => 1,
1091
- 'fql.multiquery' => 1,
1092
- 'fql.query' => 1,
1093
- 'friends.arefriends' => 1,
1094
- 'friends.get' => 1,
1095
- 'friends.getappusers' => 1,
1096
- 'friends.getlists' => 1,
1097
- 'friends.getmutualfriends' => 1,
1098
- 'gifts.get' => 1,
1099
- 'groups.get' => 1,
1100
- 'groups.getmembers' => 1,
1101
- 'intl.gettranslations' => 1,
1102
- 'links.get' => 1,
1103
- 'notes.get' => 1,
1104
- 'notifications.get' => 1,
1105
- 'pages.getinfo' => 1,
1106
- 'pages.isadmin' => 1,
1107
- 'pages.isappadded' => 1,
1108
- 'pages.isfan' => 1,
1109
- 'permissions.checkavailableapiaccess' => 1,
1110
- 'permissions.checkgrantedapiaccess' => 1,
1111
- 'photos.get' => 1,
1112
- 'photos.getalbums' => 1,
1113
- 'photos.gettags' => 1,
1114
- 'profile.getinfo' => 1,
1115
- 'profile.getinfooptions' => 1,
1116
- 'stream.get' => 1,
1117
- 'stream.getcomments' => 1,
1118
- 'stream.getfilters' => 1,
1119
- 'users.getinfo' => 1,
1120
- 'users.getloggedinuser' => 1,
1121
- 'users.getstandardinfo' => 1,
1122
- 'users.hasapppermission' => 1,
1123
- 'users.isappuser' => 1,
1124
- 'users.isverified' => 1,
1125
- 'video.getuploadlimits' => 1);
1126
- $name = 'api';
1127
- if (isset($READ_ONLY_CALLS[strtolower($method)])) {
1128
- $name = 'api_read';
1129
- } else if (strtolower($method) == 'video.upload') {
1130
- $name = 'api_video';
1131
- }
1132
- return self::getUrl($name, 'restserver.php');
1133
- }
1134
-
1135
- /**
1136
- * Build the URL for given domain alias, path and parameters.
1137
- *
1138
- * @param $name string The name of the domain
1139
- * @param $path string Optional path (without a leading slash)
1140
- * @param $params array Optional query parameters
1141
- *
1142
- * @return string The URL for the given parameters
1143
- */
1144
- protected function getUrl($name, $path='', $params=array()) {
1145
- $url = self::$DOMAIN_MAP[$name];
1146
- if ($path) {
1147
- if ($path[0] === '/') {
1148
- $path = substr($path, 1);
1149
- }
1150
- $url .= $path;
1151
- }
1152
- if ($params) {
1153
- $url .= '?' . http_build_query($params, null, '&');
1154
- }
1155
-
1156
- return $url;
1157
- }
1158
-
1159
- protected function getHttpHost() {
1160
- if ($this->trustForwarded && isset($_SERVER['HTTP_X_FORWARDED_HOST'])) {
1161
- return $_SERVER['HTTP_X_FORWARDED_HOST'];
1162
- }
1163
- return $_SERVER['HTTP_HOST'];
1164
- }
1165
-
1166
- protected function getHttpProtocol() {
1167
- if ($this->trustForwarded && isset($_SERVER['HTTP_X_FORWARDED_PROTO'])) {
1168
- if ($_SERVER['HTTP_X_FORWARDED_PROTO'] === 'https') {
1169
- return 'https';
1170
- }
1171
- return 'http';
1172
- }
1173
- /*apache + variants specific way of checking for https*/
1174
- if (isset($_SERVER['HTTPS']) &&
1175
- ($_SERVER['HTTPS'] === 'on' || $_SERVER['HTTPS'] == 1)) {
1176
- return 'https';
1177
- }
1178
- /*nginx way of checking for https*/
1179
- if (isset($_SERVER['SERVER_PORT']) &&
1180
- ($_SERVER['SERVER_PORT'] === '443')) {
1181
- return 'https';
1182
- }
1183
- return 'http';
1184
- }
1185
-
1186
- /**
1187
- * Get the base domain used for the cookie.
1188
- */
1189
- protected function getBaseDomain() {
1190
- // The base domain is stored in the metadata cookie if not we fallback
1191
- // to the current hostname
1192
- $metadata = $this->getMetadataCookie();
1193
- if (array_key_exists('base_domain', $metadata) &&
1194
- !empty($metadata['base_domain'])) {
1195
- return trim($metadata['base_domain'], '.');
1196
- }
1197
- return $this->getHttpHost();
1198
- }
1199
-
1200
- /**
1201
-
1202
- /**
1203
- * Returns the Current URL, stripping it of known FB parameters that should
1204
- * not persist.
1205
- *
1206
- * @return string The current URL
1207
- */
1208
- protected function getCurrentUrl() {
1209
- $protocol = $this->getHttpProtocol() . '://';
1210
- $host = $this->getHttpHost();
1211
- $currentUrl = $protocol.$host.$_SERVER['REQUEST_URI'];
1212
- $parts = parse_url($currentUrl);
1213
-
1214
- $query = '';
1215
- if (!empty($parts['query'])) {
1216
- // drop known fb params
1217
- $params = explode('&', $parts['query']);
1218
- $retained_params = array();
1219
- foreach ($params as $param) {
1220
- if ($this->shouldRetainParam($param)) {
1221
- $retained_params[] = $param;
1222
- }
1223
- }
1224
-
1225
- if (!empty($retained_params)) {
1226
- $query = '?'.implode($retained_params, '&');
1227
- }
1228
- }
1229
-
1230
- // use port if non default
1231
- $port =
1232
- isset($parts['port']) &&
1233
- (($protocol === 'http://' && $parts['port'] !== 80) ||
1234
- ($protocol === 'https://' && $parts['port'] !== 443))
1235
- ? ':' . $parts['port'] : '';
1236
-
1237
- // rebuild
1238
- return $protocol . $parts['host'] . $port . $parts['path'] . $query;
1239
- }
1240
-
1241
- /**
1242
- * Returns true if and only if the key or key/value pair should
1243
- * be retained as part of the query string. This amounts to
1244
- * a brute-force search of the very small list of Facebook-specific
1245
- * params that should be stripped out.
1246
- *
1247
- * @param string $param A key or key/value pair within a URL's query (e.g.
1248
- * 'foo=a', 'foo=', or 'foo'.
1249
- *
1250
- * @return boolean
1251
- */
1252
- protected function shouldRetainParam($param) {
1253
- foreach (self::$DROP_QUERY_PARAMS as $drop_query_param) {
1254
- if (strpos($param, $drop_query_param.'=') === 0) {
1255
- return false;
1256
- }
1257
- }
1258
-
1259
- return true;
1260
- }
1261
-
1262
- /**
1263
- * Analyzes the supplied result to see if it was thrown
1264
- * because the access token is no longer valid. If that is
1265
- * the case, then we destroy the session.
1266
- *
1267
- * @param $result array A record storing the error message returned
1268
- * by a failed API call.
1269
- */
1270
- protected function throwAPIException($result) {
1271
- $e = new FacebookApiException($result);
1272
- switch ($e->getType()) {
1273
- // OAuth 2.0 Draft 00 style
1274
- case 'OAuthException':
1275
- // OAuth 2.0 Draft 10 style
1276
- case 'invalid_token':
1277
- // REST server errors are just Exceptions
1278
- case 'Exception':
1279
- $message = $e->getMessage();
1280
- if ((strpos($message, 'Error validating access token') !== false) ||
1281
- (strpos($message, 'Invalid OAuth access token') !== false) ||
1282
- (strpos($message, 'An active access token must be used') !== false)
1283
- ) {
1284
- $this->destroySession();
1285
- }
1286
- break;
1287
- }
1288
-
1289
- throw $e;
1290
- }
1291
-
1292
-
1293
- /**
1294
- * Prints to the error log if you aren't in command line mode.
1295
- *
1296
- * @param string $msg Log message
1297
- */
1298
- protected static function errorLog($msg) {
1299
- // disable error log if we are running in a CLI environment
1300
- // @codeCoverageIgnoreStart
1301
- if (php_sapi_name() != 'cli') {
1302
- error_log($msg);
1303
- }
1304
- // uncomment this if you want to see the errors on the page
1305
- // print 'error_log: '.$msg."\n";
1306
- // @codeCoverageIgnoreEnd
1307
- }
1308
-
1309
- /**
1310
- * Base64 encoding that doesn't need to be urlencode()ed.
1311
- * Exactly the same as base64_encode except it uses
1312
- * - instead of +
1313
- * _ instead of /
1314
- * No padded =
1315
- *
1316
- * @param string $input base64UrlEncoded string
1317
- * @return string
1318
- */
1319
- protected static function base64UrlDecode($input) {
1320
- return base64_decode(strtr($input, '-_', '+/'));
1321
- }
1322
-
1323
- /**
1324
- * Base64 encoding that doesn't need to be urlencode()ed.
1325
- * Exactly the same as base64_encode except it uses
1326
- * - instead of +
1327
- * _ instead of /
1328
- *
1329
- * @param string $input string
1330
- * @return string base64Url encoded string
1331
- */
1332
- protected static function base64UrlEncode($input) {
1333
- $str = strtr(base64_encode($input), '+/', '-_');
1334
- $str = str_replace('=', '', $str);
1335
- return $str;
1336
- }
1337
-
1338
- /**
1339
- * Destroy the current session
1340
- */
1341
- public function destroySession() {
1342
- $this->accessToken = null;
1343
- $this->signedRequest = null;
1344
- $this->user = null;
1345
- $this->clearAllPersistentData();
1346
-
1347
- // Javascript sets a cookie that will be used in getSignedRequest that we
1348
- // need to clear if we can
1349
- $cookie_name = $this->getSignedRequestCookieName();
1350
- if (array_key_exists($cookie_name, $_COOKIE)) {
1351
- unset($_COOKIE[$cookie_name]);
1352
- if (!headers_sent()) {
1353
- $base_domain = $this->getBaseDomain();
1354
- setcookie($cookie_name, '', 1, '/', '.'.$base_domain);
1355
- } else {
1356
- // @codeCoverageIgnoreStart
1357
- self::errorLog(
1358
- 'There exists a cookie that we wanted to clear that we couldn\'t '.
1359
- 'clear because headers was already sent. Make sure to do the first '.
1360
- 'API call before outputing anything.'
1361
- );
1362
- // @codeCoverageIgnoreEnd
1363
- }
1364
- }
1365
- }
1366
-
1367
- /**
1368
- * Parses the metadata cookie that our Javascript API set
1369
- *
1370
- * @return an array mapping key to value
1371
- */
1372
- protected function getMetadataCookie() {
1373
- $cookie_name = $this->getMetadataCookieName();
1374
- if (!array_key_exists($cookie_name, $_COOKIE)) {
1375
- return array();
1376
- }
1377
-
1378
- // The cookie value can be wrapped in "-characters so remove them
1379
- $cookie_value = trim($_COOKIE[$cookie_name], '"');
1380
-
1381
- if (empty($cookie_value)) {
1382
- return array();
1383
- }
1384
-
1385
- $parts = explode('&', $cookie_value);
1386
- $metadata = array();
1387
- foreach ($parts as $part) {
1388
- $pair = explode('=', $part, 2);
1389
- if (!empty($pair[0])) {
1390
- $metadata[urldecode($pair[0])] =
1391
- (count($pair) > 1) ? urldecode($pair[1]) : '';
1392
- }
1393
- }
1394
-
1395
- return $metadata;
1396
- }
1397
-
1398
- protected static function isAllowedDomain($big, $small) {
1399
- if ($big === $small) {
1400
- return true;
1401
- }
1402
- return self::endsWith($big, '.'.$small);
1403
- }
1404
-
1405
- protected static function endsWith($big, $small) {
1406
- $len = strlen($small);
1407
- if ($len === 0) {
1408
- return true;
1409
- }
1410
- return substr($big, -$len) === $small;
1411
- }
1412
-
1413
- /**
1414
- * Each of the following four methods should be overridden in
1415
- * a concrete subclass, as they are in the provided Facebook class.
1416
- * The Facebook class uses PHP sessions to provide a primitive
1417
- * persistent store, but another subclass--one that you implement--
1418
- * might use a database, memcache, or an in-memory cache.
1419
- *
1420
- * @see Facebook
1421
- */
1422
-
1423
- /**
1424
- * Stores the given ($key, $value) pair, so that future calls to
1425
- * getPersistentData($key) return $value. This call may be in another request.
1426
- *
1427
- * @param string $key
1428
- * @param array $value
1429
- *
1430
- * @return void
1431
- */
1432
- abstract protected function setPersistentData($key, $value);
1433
-
1434
- /**
1435
- * Get the data for $key, persisted by BaseFacebook::setPersistentData()
1436
- *
1437
- * @param string $key The key of the data to retrieve
1438
- * @param boolean $default The default value to return if $key is not found
1439
- *
1440
- * @return mixed
1441
- */
1442
- abstract protected function getPersistentData($key, $default = false);
1443
-
1444
- /**
1445
- * Clear the data with $key from the persistent storage
1446
- *
1447
- * @param string $key
1448
- * @return void
1449
- */
1450
- abstract protected function clearPersistentData($key);
1451
-
1452
- /**
1453
- * Clear all data from the persistent storage
1454
- *
1455
- * @return void
1456
- */
1457
- abstract protected function clearAllPersistentData();
1458
- }