PageRenderTime 65ms CodeModel.GetById 23ms RepoModel.GetById 0ms app.codeStats 0ms

/wp-content/plugins/all-in-one-event-calendar/lib/plugin/Ai1ecFacebookConnectorPlugin.php

https://gitlab.com/Blueprint-Marketing/interoccupy.net
PHP | 1243 lines | 792 code | 21 blank | 430 comment | 77 complexity | c8dab9679aa9c006fadfbc753f08cb8d MD5 | raw file
  1. <?php
  2. /**
  3. *
  4. * @author time.ly
  5. *
  6. * This class extend the base abstract class and handles all the task for setting-up the environment, handling ajax rquests, POST requests and rendering the appropriate html
  7. *
  8. */
  9. class Ai1ecFacebookConnectorPlugin extends Ai1ec_Connector_Plugin {
  10. // The path of the Facebook SDK
  11. const FB_APP_ID = 'facebook-app-id';
  12. const FB_APP_SECRET = 'facebook-app-secret';
  13. const FB_TOKEN = 'facebook-token';
  14. const FB_VALID_APP_ID = 'valid-app-id';
  15. const FB_LOGGED_TIMEZONE = 'facebook-logged-timezone';
  16. // The permissions requred for the app.
  17. const FB_SCOPE = 'user_events,friends_events,user_groups,manage_pages,create_event';
  18. // The name of the plugin table
  19. const FB_DB_TABLE = 'ai1ec_facebook_users';
  20. // The plugin cron version
  21. const FB_CRON_VERSION = 100;
  22. // The name of the CRON for pages
  23. const FB_CRON_PAGES = 'ai1ec_facebook_cron_pages';
  24. // The name of the CRON for users
  25. const FB_CRON_USERS = 'ai1ec_facebook_cron_users';
  26. // The name of the CRON for groups
  27. const FB_CRON_GROUPS = 'ai1ec_facebook_cron_groups';
  28. // The name of the CRON for pages
  29. const FB_CRON_EVENTS = 'ai1ec_facebook_cron_events';
  30. // CRON frequency
  31. const FB_CRON_FREQUENCY = 'twicedaily';
  32. // Option where saving the message that will be shown in the notice.
  33. const FB_OPTION_CRON_NOTICE = 'ai1ec_facebook_admin_notice';
  34. // Option where saving the CRON version.
  35. const FB_OPTION_CRON_VERSION = 'ai1ec_facebook_cron_version';
  36. // Option where saving the DB version
  37. const FB_OPTION_DB_VERSION = 'ai1ec_facebook_db_version';
  38. // The error code for an Auth exception
  39. const FB_OAUTH_EXC_CODE = 190;
  40. // The plugin DB version
  41. const FB_DB_VERSION = 106;
  42. // The id of the checkbox that the user must check to export the PSOT to Facebook
  43. const FB_EXPORT_CHKBX_NAME = 'ai1ec_facebook_export';
  44. // The value for the facebook_status column when an event is exported to facebook
  45. const FB_EXPORTED_EVENT = 'E';
  46. // The value for the facebook_status column when an event is imported from facebook
  47. const FB_IMPORTED_EVENT = 'I';
  48. // The value when an event is neither imported nor exported to Facebook
  49. const FB_STANDARD_EVENT = '';
  50. // THe name of the hidden field we set when we choose to delete an event we exported to FB
  51. const FB_HIDDEN_INPUT_REMOVE_EVENT_NAME = 'ai1ec-remove-event';
  52. // THe name of the hidden field we set in the add new event page when we have no active ticket.
  53. const FB_HIDDEN_INPUT_NO_ACTIVE_TOKEN = 'ai1ec-no-active-token';
  54. // The name of the submit input that the facebook modal triggers to submit the form
  55. const FB_SUBMIT_MODAL_NAME = 'ai1ec_facebook_modal_submit';
  56. // The info variable for this class
  57. const FB_INFO_VARIABLE = '<label><a href="http://help.time.ly/customer/portal/articles/616553-how-do-i-use-the-facebook-import-" target="_blank">How do I use Facebook import?</a></label>';
  58. // The FB_APP_ID description text
  59. const FB_APP_ID_DESCRIPTION_TEXT = "Enter your Facebook App ID:";
  60. // The FB_APP_SECRET description text
  61. const FB_APP_SECRET_DESCRIPTION_TEXT = "Enter your Facebook App Secret:";
  62. // The key used to persist
  63. const KEY_FOR_PERSISTANCE = "ai1ec_facebook_current_user";
  64. /**
  65. * @var array $settings An Associative array that holds the settings for the plugin. this array is persisted in Ai1ec_Settings
  66. * facebook-app-id: The Facebook app-id assigned from Facebook
  67. * facebook-app-secret: The Facebook app secret assigned from Facebook
  68. * facebook-token: The token that Facebook returns after authorizing the app
  69. * valid-app-id: an app id that has validate succesfully
  70. * facebook-logged-timezone: the timezone offset of the current user
  71. */
  72. protected $settings = array(
  73. array(
  74. "id" => self::FB_APP_ID,
  75. "description" => self::FB_APP_ID_DESCRIPTION_TEXT,
  76. "admin-page" => TRUE
  77. ),
  78. array(
  79. "id" => self::FB_APP_SECRET,
  80. "description" => self::FB_APP_SECRET_DESCRIPTION_TEXT,
  81. "admin-page" => TRUE
  82. ),
  83. array(
  84. "id" => self::FB_TOKEN,
  85. "description" => "This is the token which is used internally.",
  86. "admin-page" => FALSE
  87. ),
  88. array(
  89. "id" => self::FB_VALID_APP_ID,
  90. "description" => "This is an app ID that has been validated and is an app.",
  91. "admin-page" => FALSE
  92. )
  93. );
  94. /**
  95. *
  96. * @var array
  97. * title: The title of the tab and the title of the configuration section
  98. * id: The id used in the generation of the tab
  99. */
  100. protected $variables = array(
  101. "title" => "Facebook Feeds",
  102. "id" => "facebook",
  103. "info" => self::FB_INFO_VARIABLE
  104. );
  105. /**
  106. * Stores messages that must be printed to the user.
  107. *
  108. * @var array
  109. */
  110. private $_information_message = array();
  111. /**
  112. * Stores any error messages that will be shown on top of the area.
  113. *
  114. * @var array
  115. */
  116. private $_error_messages = array();
  117. /**
  118. * If set to TRUE, try to perform a Login
  119. *
  120. * @var boolean
  121. */
  122. private $do_a_facebook_login;
  123. /**
  124. * An instance of the Facebook class. For cache purpose.
  125. *
  126. * @var Facebook
  127. */
  128. private $facebook;
  129. /**
  130. * Facebook has problems if you call getLoginUrl multiple times so i store the value here.
  131. *
  132. * @var string
  133. */
  134. private $facebook_login_url;
  135. /**
  136. *
  137. * @var Ai1ec_Persistence_Context
  138. */
  139. private $persistance_context;
  140. /**
  141. *
  142. * @var string
  143. */
  144. public $error_message_after_validating_app_id_and_secret;
  145. /**
  146. * In the constructor all action and filter hooks are declared as the constructor is called when the app initialize. We als handle the CROn and the DB Schema.
  147. *
  148. */
  149. public function __construct() {
  150. // We don't use the filesystem as cache as this contains private data.
  151. $this->persistance_context = Ai1ec_Strategies_Factory::create_persistence_context( self::KEY_FOR_PERSISTANCE );
  152. // Set the AJAX action to dismiss the notice.
  153. add_action( 'wp_ajax_ai1ec_facebook_cron_dismiss' , array( $this, 'dismiss_notice_ajax' ) );
  154. // Set the AJAX action to refresh Facebook Graph Objects
  155. add_action( 'wp_ajax_ai1ec_refresh_facebook_objects' , array( $this, 'refresh_facebook_ajax' ) );
  156. // Set AJAX action to remove a subscribed user
  157. add_action( 'wp_ajax_ai1ec_remove_subscribed' , array( $this, 'remove_subscribed_ajax' ) );
  158. // Set AJAX action to refresh multiselect
  159. add_action( 'wp_ajax_ai1ec_refresh_multiselect' , array( $this, 'refresh_multiselect' ) );
  160. // Set AJAX action to refresh events
  161. add_action( 'wp_ajax_ai1ec_refresh_events' , array( $this, 'refresh_events_ajax' ) );
  162. // Add the "Export to facebook" widget.
  163. add_action( 'post_submitbox_misc_actions' , array( $this, 'render_export_box' ) );
  164. // Ad action to check if app-id / secret where changed
  165. add_action( 'ai1ec-Ai1ecFacebookConnectorPlugin-postsave-setting', array( $this, 'if_app_id_or_secret_changed_perform_logout' ), 10, 1 );
  166. // Add action to perform a login to Facebook if needed
  167. add_action( 'ai1ec-post-save-facebook-login' , array( $this, 'do_facebook_login_if_app_id_and_secret_were_changed' ) );
  168. //allow redirection, even if my theme starts to send output to the browser
  169. add_action('init' , array( $this, 'start_output_buffering' ) );
  170. // I leave all add_action() call in one place since it's easier to understand and mantain
  171. $facebook_custom_bulk_action = Ai1ec_Facebook_Factory::get_facebook_custom_bulk_action_instance( $this );
  172. // Add the select to filter events in the "All events" page
  173. add_action( 'restrict_manage_posts' , array( $facebook_custom_bulk_action, 'facebook_filter_restrict_manage_posts' ) );
  174. // Add action to handle export to facebook
  175. add_action( 'load-edit.php' , array( $facebook_custom_bulk_action , 'facebook_custom_bulk_action' ) );
  176. // Add action to filter data
  177. add_filter( 'posts_where' , array( $facebook_custom_bulk_action , 'facebook_filter_posts_where' ) );
  178. // Install db
  179. $this->install_schema();
  180. // Install CRON
  181. $this->install_cron();
  182. }
  183. /**
  184. * Start output buffering to allow redirection ( it's used maily by the automatic login )
  185. */
  186. function start_output_buffering() {
  187. ob_start();
  188. }
  189. /**
  190. * Login to facebook if the user changed app_id and secret and they are valid
  191. *
  192. */
  193. public function do_facebook_login_if_app_id_and_secret_were_changed() {
  194. if( $this->do_a_facebook_login === TRUE ) {
  195. unset( $this->facebook );
  196. $this->do_facebook_login();
  197. }
  198. }
  199. /**
  200. * If the use has entered new settings for app-id or secret in the settings page, logout the user from fb.
  201. *
  202. * @param array $new_data
  203. */
  204. public function if_app_id_or_secret_changed_perform_logout( array $old_data ) {
  205. if( $old_data['page'] === 'all-in-one-event-calendar-settings' ) {
  206. $new_data = $this->get_plugin_settings( get_class( $this ) );
  207. if( $this->has_app_id_or_secret_changed( $old_data, $new_data ) ) {
  208. $this->clear_facebook_data_from_session_and_db_and_disable_cron();
  209. if( $this->has_app_id_and_app_secret_not_empty( $new_data ) ) {
  210. $facebook_app = Ai1ec_Facebook_Factory::get_facebook_application_instance( $new_data[self::FB_APP_ID], $new_data[self::FB_APP_SECRET] );
  211. try {
  212. $facebook_app->get_back_an_access_token_from_facebook_for_the_app();
  213. $this->do_a_facebook_login = TRUE;
  214. }
  215. catch ( Ai1ec_Error_Validating_App_Id_And_Secret $e ) {
  216. $this->error_message_after_validating_app_id_and_secret = __( $e->getMessage(), AI1EC_PLUGIN_NAME );
  217. $this->reset_app_id_and_secret();
  218. }
  219. catch ( Exception $e ) {
  220. $this->error_message_after_validating_app_id_and_secret = __( $e->getMessage(), AI1EC_PLUGIN_NAME );
  221. $this->reset_app_id_and_secret();
  222. }
  223. }
  224. }
  225. }
  226. }
  227. /**
  228. * Reset app id and secret if they are not valid.
  229. *
  230. */
  231. private function reset_app_id_and_secret() {
  232. $this->save_plugin_variable( self::FB_APP_ID, '' );
  233. $this->save_plugin_variable( self::FB_APP_SECRET, '' );
  234. // unset the facebook instance because it still has previou app id and secret
  235. unset( $this->facebook );
  236. }
  237. /**
  238. * Check if we have both an app id and an app secret.
  239. *
  240. * @param array $new_data
  241. *
  242. * @return boolean
  243. */
  244. private function has_app_id_and_app_secret_not_empty( array $new_data ) {
  245. return ! empty( $new_data[self::FB_APP_ID] ) && ! empty( $new_data[self::FB_APP_SECRET] );
  246. }
  247. /**
  248. * Check if facebook app_id or secrets have been changed
  249. *
  250. * @param array $old_data
  251. * @param array $new_data
  252. * @return boolean
  253. */
  254. private function has_app_id_or_secret_changed( array $old_data, array $new_data ) {
  255. return ( ( $old_data[self::FB_APP_ID] !== $new_data[self::FB_APP_ID] ) || ( $old_data[self::FB_APP_SECRET] !== $new_data[self::FB_APP_SECRET] ) );
  256. }
  257. /**
  258. * Clears Facebook data from Session, clear the Access token and disable cron functions
  259. *
  260. */
  261. private function clear_facebook_data_from_session_and_db_and_disable_cron() {
  262. // Get an instance of the Facebook class
  263. $facebook = $this->facebook_instance_factory();
  264. // Destroy the session so that no Facebook data is held
  265. $facebook->destroySession();
  266. // Invalidate the Token
  267. $this->save_plugin_variable( self::FB_TOKEN, '' );
  268. // Delete all data from the facebook users table otherwise if a different user logs in data is mixed
  269. $this->clean_facebook_users_table_on_logout();
  270. // Delete the user saved in session
  271. $this->persistance_context->delete_data_from_persistence();
  272. // Disable the cron Functions
  273. $this->disable_cron_functions();
  274. // Delete the option that saves the cron version: when a new acces token is obtained the CRON will be set again
  275. delete_option( self::FB_OPTION_CRON_VERSION );
  276. }
  277. /**
  278. * Deletes all data from the facebook_users table
  279. *
  280. */
  281. private function clean_facebook_users_table_on_logout() {
  282. global $wpdb;
  283. $user_event_table = Ai1ec_Facebook_Factory::get_plugin_table();
  284. $wpdb->query( "DELETE FROM $user_event_table WHERE 1 = 1" );
  285. }
  286. /**
  287. * Handles all the required steps to install / update the schema
  288. */
  289. private function install_schema() {
  290. // If existing DB version is not consistent with current plugin's version,
  291. // or does not exist, then create/update table structure using dbDelta().
  292. if (
  293. Ai1ec_Meta::get_option( self::FB_OPTION_DB_VERSION ) !=
  294. self::FB_DB_VERSION
  295. ) {
  296. $table_name = Ai1ec_Facebook_Factory::get_plugin_table();
  297. $sql = "CREATE TABLE $table_name (
  298. user_id bigint(20) NOT NULL,
  299. user_name varchar(255) NOT NULL,
  300. user_pic varchar(255) NOT NULL,
  301. subscribed tinyint(1) NOT NULL DEFAULT '0',
  302. type varchar(20) CHARACTER SET ascii COLLATE ascii_general_ci NOT NULL,
  303. tag varchar(255) NOT NULL DEFAULT '',
  304. category int(11) NOT NULL DEFAULT '0',
  305. comments_enabled tinyint(1) NOT NULL DEFAULT '1',
  306. map_display_enabled tinyint(1) NOT NULL DEFAULT '0',
  307. last_synced timestamp NULL DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP,
  308. PRIMARY KEY (user_id),
  309. KEY subscribers (type(2),subscribed)
  310. ) DEFAULT CHARSET=utf8;";
  311. $table_users_events = Ai1ec_Facebook_Factory::get_user_events_table();
  312. $sql .= "CREATE TABLE $table_users_events (
  313. user_id bigint(20) unsigned NOT NULL,
  314. eid bigint(20) unsigned NOT NULL,
  315. start datetime NOT NULL,
  316. PRIMARY KEY (user_id,eid)
  317. ) DEFAULT CHARSET=utf8;";
  318. if ( Ai1ec_Database::instance()->apply_delta( $sql ) ) {
  319. update_option( self::FB_OPTION_DB_VERSION, self::FB_DB_VERSION );
  320. } else {
  321. trigger_error(
  322. 'Failed to upgrade Facebook DB schema',
  323. E_USER_WARNING
  324. );
  325. }
  326. }
  327. }
  328. /**
  329. * Disables all cron functions. This is called when the users logs out or when the plugin is uninstalled.
  330. */
  331. private function disable_cron_functions() {
  332. // delete our scheduled crons
  333. wp_clear_scheduled_hook( self::FB_CRON_GROUPS, array( Ai1ec_Facebook_Graph_Object_Collection::FB_GROUP ) );
  334. wp_clear_scheduled_hook( self::FB_CRON_PAGES, array( Ai1ec_Facebook_Graph_Object_Collection::FB_PAGE ) );
  335. wp_clear_scheduled_hook( self::FB_CRON_USERS, array( Ai1ec_Facebook_Graph_Object_Collection::FB_USER ) );
  336. wp_clear_scheduled_hook( self::FB_CRON_EVENTS );
  337. }
  338. /**
  339. * (non-PHPdoc)
  340. * @see Ai1ec_Connector_Plugin::run_uninstall_procedures()
  341. */
  342. public function run_uninstall_procedures() {
  343. // delete our scheduled crons
  344. $this->disable_cron_functions();
  345. // Clean up opions
  346. delete_option( self::FB_OPTION_DB_VERSION );
  347. delete_option( self::FB_OPTION_CRON_VERSION );
  348. delete_option( self::FB_OPTION_CRON_NOTICE );
  349. // Delete tables
  350. global $wpdb;
  351. $plugin_table = Ai1ec_Facebook_Factory::get_plugin_table();
  352. $user_events_table = Ai1ec_Facebook_Factory::get_user_events_table();
  353. $wpdb->query( "DROP TABLE IF EXISTS $plugin_table" );
  354. $wpdb->query( "DROP TABLE IF EXISTS $user_events_table" );
  355. }
  356. /**
  357. * Handles all required steps to install the CRON
  358. */
  359. private function install_cron() {
  360. // Set CRON action for users
  361. add_action( self::FB_CRON_PAGES , array( &$this, 'do_facebook_sync_cron' ) );
  362. // Set CRON action for users
  363. add_action( self::FB_CRON_GROUPS , array( &$this, 'do_facebook_sync_cron' ) );
  364. // Set CRON action for users
  365. add_action( self::FB_CRON_USERS , array( &$this, 'do_facebook_sync_cron' ) );
  366. // Set the CRON for updating events
  367. add_action( self::FB_CRON_EVENTS , array( &$this, 'refresh_events_cron' ) );
  368. // Check if we have a token. This means that the user has logged into facebook, so start the cron
  369. $token = $this->get_plugin_variable( self::FB_TOKEN );
  370. // If existing CRON version is not consistent with current plugin's version,
  371. // or does not exist, then create/update cron
  372. if (
  373. $token &&
  374. Ai1ec_Meta::get_option( self::FB_OPTION_CRON_VERSION ) !=
  375. self::FB_CRON_VERSION
  376. ) {
  377. // delete our scheduled crons
  378. wp_clear_scheduled_hook( self::FB_CRON_GROUPS, array( Ai1ec_Facebook_Graph_Object_Collection::FB_GROUP ) );
  379. wp_clear_scheduled_hook( self::FB_CRON_PAGES, array( Ai1ec_Facebook_Graph_Object_Collection::FB_PAGE ) );
  380. wp_clear_scheduled_hook( self::FB_CRON_USERS, array( Ai1ec_Facebook_Graph_Object_Collection::FB_USER ) );
  381. wp_clear_scheduled_hook( self::FB_CRON_EVENTS );
  382. // set the new crons ( use current_time( 'timestamp' ) to be more consisted as suggest in the codex)
  383. // Set the CRON for pages
  384. wp_schedule_event( current_time( 'timestamp' ) + 1800, self::FB_CRON_FREQUENCY, self::FB_CRON_PAGES, array( Ai1ec_Facebook_Graph_Object_Collection::FB_PAGE ) );
  385. // Set the CRON for groups
  386. wp_schedule_event( current_time( 'timestamp' ) + 3600, self::FB_CRON_FREQUENCY, self::FB_CRON_GROUPS, array( Ai1ec_Facebook_Graph_Object_Collection::FB_GROUP ) );
  387. // Set the CRON for users
  388. wp_schedule_event( current_time( 'timestamp' ) + 5400, self::FB_CRON_FREQUENCY, self::FB_CRON_USERS, array( Ai1ec_Facebook_Graph_Object_Collection::FB_USER ) );
  389. // Set the CRON for events
  390. wp_schedule_event( current_time( 'timestamp' ), self::FB_CRON_FREQUENCY, self::FB_CRON_EVENTS );
  391. // update the cron version
  392. update_option( self::FB_OPTION_CRON_VERSION, self::FB_CRON_VERSION );
  393. }
  394. }
  395. /**
  396. * This just delete the option when the user clicks on dismiss.
  397. */
  398. public function dismiss_notice_ajax() {
  399. $response = array();
  400. $ok = delete_option( self::FB_OPTION_CRON_NOTICE );
  401. if( ! $ok ) {
  402. $response['message'] = __( "Something went wrong when deleting the option", AI1EC_PLUGIN_NAME );
  403. }
  404. echo json_encode( $response );
  405. die();
  406. }
  407. /**
  408. * This is just a wrapper around the other function that allows me to return if the Token is not set
  409. *
  410. * @param string $type
  411. */
  412. public function do_facebook_sync_cron( $type ) {
  413. // Set time limits so that the CRON doesn't stop
  414. @set_time_limit( 0 );
  415. @ini_set( 'memory_limit' , '256M' );
  416. @ini_set( 'max_input_time', '-1' );
  417. // If there is no token, block the CRON.
  418. $token = $this->get_plugin_variable( self::FB_TOKEN );
  419. if( empty( $token ) ) {
  420. return;
  421. }
  422. // Call the standard function
  423. $this->do_facebook_sync( $type );
  424. }
  425. /**
  426. * If at least one config option is set we need the settings meta box
  427. */
  428. public function is_settings_meta_box_required() {
  429. return $this->at_least_one_config_field_is_set( $this->generate_settings_array_for_admin_view() );
  430. }
  431. /**
  432. * Remove a Facebook graph object that the user had subscribed to. This also deletes the orphaned (not referred to by other subscribed users) events if the user choose to do so.
  433. */
  434. public function remove_subscribed_ajax() {
  435. // Create the object that will be returned.
  436. $response = array(
  437. "errors" => FALSE,
  438. "id" => $_POST['ai1ec_post_id'],
  439. "logged" => $_POST['ai1ec_logged_user'],
  440. "type" => $_POST['type'],
  441. );
  442. if( isset( $_POST['ai1ec_post_id'] ) ) {
  443. try {
  444. $fgoc = Ai1ec_Facebook_Factory::get_facebook_graph_object_collection( (array) $_POST['ai1ec_post_id'] );
  445. $remove_events = $_POST['ai1ec_remove_events'] === 'true' ? TRUE : FALSE;
  446. // Try to unsubscribe.
  447. $how_many = $fgoc->update_subscription_status( FALSE, $remove_events );
  448. // Get an object
  449. $fgo = Ai1ec_Facebook_Factory::get_facebook_graph_object( $_POST['ai1ec_post_id'] );
  450. // No exception, prepare the message to return
  451. $user_name = $fgo->get_user_name();
  452. $response['message'] = sprintf( __( "You unsubscribed from %s. ", AI1EC_PLUGIN_NAME ), $user_name );
  453. if( $how_many > 0 ) {
  454. $response['message'] .= sprintf( _n( "%d event was deleted", "%d events were deleted.",$how_many, AI1EC_PLUGIN_NAME ), $how_many );
  455. }
  456. // IF we are removing the logged user, update the session variable.
  457. if( $_POST['ai1ec_logged_user'] === 'true' ) {
  458. $current_user = $this->get_current_facebook_user_from_cache();
  459. $current_user->set_subscribed( FALSE );
  460. $current_user->set_tag( '' );
  461. $current_user->set_category( '' );
  462. $this->save_facebook_user_in_cache( $current_user );
  463. }
  464. // Get the data for the update of the multiselect
  465. $response['html'] = $this->refresh_multiselect( $_POST['type'] );
  466. } catch ( Ai1ec_Facebook_Db_Exception $e ) {
  467. $response['errors'] = TRUE;
  468. $error_messages = $e->get_error_messages();
  469. $response['error_message'] = $error_messages[0];
  470. }
  471. }
  472. echo( json_encode( $response ) );
  473. die();
  474. }
  475. /**
  476. * Check if we have a valid access token. If we have, it saves the user in session.
  477. *
  478. * @return boolean TRUE if we have a valid token, FALSE if we don't
  479. */
  480. public function check_if_we_have_a_valid_access_token() {
  481. if( $this->get_current_facebook_user_from_cache() !== NULL ) {
  482. // If we have a seesion, we use it
  483. return TRUE;
  484. } else {
  485. $facebook = $this->facebook_instance_factory();
  486. $current_user = Ai1ec_Facebook_Factory::get_facebook_user_instance( $facebook );
  487. // Do login
  488. $logged_in = $current_user->do_login();
  489. if( $logged_in ) {
  490. $this->save_facebook_user_in_cache( $current_user );
  491. return TRUE;
  492. } else {
  493. return FALSE;
  494. }
  495. }
  496. }
  497. /**
  498. * (non-PHPdoc)
  499. * @see Ai1ec_Connector_Plugin::display_admin_notices()
  500. */
  501. public function display_admin_notices(){
  502. // Let's check if the cron has set a message.
  503. $message = Ai1ec_Meta::get_option( self::FB_OPTION_CRON_NOTICE, false );
  504. if ( $message === false ) {
  505. return;
  506. }
  507. global $ai1ec_view_helper;
  508. $args = array(
  509. 'label' => $message['label'],
  510. 'msg' => $message['message'],
  511. 'button' => (object) array(
  512. 'class' => 'ai1ec-facebook-cron-dismiss-notification',
  513. 'value' => 'Dismiss',
  514. ),
  515. 'message_type' => $message['message_type'],
  516. );
  517. $ai1ec_view_helper->display_admin( 'admin_notices.php', $args );
  518. }
  519. /**
  520. * Sync the data of a multiselect with Facebook.
  521. */
  522. public function refresh_facebook_ajax() {
  523. if ( isset( $_POST['ai1ec_type'] ) ) {
  524. $response = $this->do_facebook_sync( $_POST['ai1ec_type'] );
  525. if( $response['errors'] === FALSE ) {
  526. $facebook_tab = Ai1ec_Facebook_Factory::get_facebook_tab_instance();
  527. $facebook_user = $this->get_current_facebook_user_from_cache();
  528. $response['html'] = $facebook_tab->render_multi_select( $_POST['ai1ec_type'], $facebook_user->get_id() );
  529. $response['type'] = $_POST['ai1ec_type'];
  530. }
  531. echo( json_encode( $response ) );
  532. die();
  533. }
  534. }
  535. /**
  536. * Saves a value in the option so that a notice is visualized when there is an error
  537. */
  538. private function set_error_message_for_cron( $exception_message = '' ) {
  539. $message = array(
  540. "label" => __( 'All-in-One Event Calendar Facebook Import Notice', AI1EC_PLUGIN_NAME ),
  541. "message" => __( "Something went wrong while syncing events from Facebook. Please check that your Access Token is still valid by visiting the <strong>Events</strong> &gt; <strong>Calendar Feeds</strong> screen.", AI1EC_PLUGIN_NAME ),
  542. "message_type" => "updated",
  543. );
  544. if( ! empty( $exception_message ) ) {
  545. $message["message"] .= "<br />" . __( "Facebook provided this error message: <em>$exception_message</em>", AI1EC_PLUGIN_NAME );
  546. }
  547. update_option( self::FB_OPTION_CRON_NOTICE, $message );
  548. }
  549. /**
  550. * Private, used to log events to check if cron works
  551. */
  552. private function log_number_of_events() {
  553. global $wpdb;
  554. $event_table = Ai1ec_Facebook_Factory::get_events_table();
  555. $event_count = $wpdb->get_var( $wpdb->prepare( "SELECT COUNT(*) FROM $event_table;" ) );
  556. error_log( "There are $event_count in the table $event_table" );
  557. }
  558. /**
  559. * This is the function that is called by the cron and refreshes all the events of the user / groups /pages that are subscribed.
  560. */
  561. public function refresh_events_cron() {
  562. @set_time_limit( 0 );
  563. @ini_set( 'memory_limit' , '256M' );
  564. @ini_set( 'max_input_time', '-1' );
  565. // If there is no token, block the CRON.
  566. $token = $this->get_plugin_variable( self::FB_TOKEN );
  567. if( empty( $token ) ) {
  568. return;
  569. }
  570. $fgoc = Ai1ec_Facebook_Factory::get_facebook_graph_object_collection( array() );
  571. foreach( Ai1ec_Facebook_Graph_Object_Collection::$fb_all_types as $type ) {
  572. $fgoc->set_type( $type );
  573. $facebook = $this->facebook_instance_factory();
  574. // Set the facebook instance
  575. $fgoc->set_facebook( $facebook );
  576. // We don't have a session,get a new user
  577. $current_user = Ai1ec_Facebook_Factory::get_facebook_user_instance( $facebook );
  578. // Do login
  579. $logged_in = $current_user->do_login();
  580. if( $logged_in ) {
  581. $fgoc->set_facebook_user( $current_user );
  582. } else {
  583. $this->set_error_message_for_cron( $current_user->get_error_message() );
  584. }
  585. // Load the subcriber for the type and check that at least one subscriber is loaded
  586. $at_least_one_user = $fgoc->load_subscribers_for_type();
  587. if( $at_least_one_user === TRUE ) {
  588. try {
  589. $response = $fgoc->refresh_events();
  590. } catch ( WP_FacebookApiException $e ) {
  591. $this->set_error_message_for_cron( $e->getMessage() );
  592. }
  593. }
  594. }
  595. }
  596. /**
  597. * Refreshes the events for the clicked user / group / page.
  598. *
  599. * This function can become very slow if the official Facebook plugin is present.
  600. * This is because Facebook attaches some very expensive call to the save_post action
  601. * which we trigger recursively and time for a single wp_update_post or wp_save_post call
  602. * goes up to 1.5 sec.
  603. * Listed are the calls to remove if needs arise
  604. * remove_action( 'save_post', 'fb_add_page_mention_box_save' );
  605. * remove_action( 'save_post', 'fb_add_friend_mention_box_save' );
  606. *
  607. */
  608. public function refresh_events_ajax() {
  609. $to_return = array();
  610. if( isset( $_POST['ai1ec_post_id'] ) ) {
  611. // Set the id
  612. $to_return['id'] = $_POST['ai1ec_post_id'];
  613. // Get a collection object
  614. $fgoc = Ai1ec_Facebook_Factory::get_facebook_graph_object_collection( array( $_POST['ai1ec_post_id'] ) );
  615. // Set the type. This loads the correct strategy object for querying events.
  616. $fgoc->set_type( $_POST['ai1ec_type'] );
  617. $facebook = $this->facebook_instance_factory();
  618. // Set the currently active user. This is neede to calculate starting times of events.
  619. $fgoc->set_facebook_user( $this->get_current_facebook_user_from_cache() );
  620. // Set the facebook instance
  621. $fgoc->set_facebook( $facebook );
  622. try {
  623. $response = $fgoc->refresh_events();
  624. $tab = Ai1ec_Facebook_Factory::get_facebook_tab_instance();
  625. // IF there are errors i show a warning message in the front-end, that say that something went wrong but not totally wrong.
  626. $to_return['errors'] = $response['errors'];
  627. $to_return['message'] = $tab->create_refresh_message( $response );
  628. }
  629. catch ( WP_FacebookApiException $e ) {
  630. $message = '';
  631. if( $e->getCode() === self::FB_OAUTH_EXC_CODE ) {
  632. $message = __( "Something went wrong with Facebook authentication, try to log out and then login again", AI1EC_PLUGIN_NAME );
  633. $facebook->destroySession();
  634. } else {
  635. $message = __( "Something went wrong when retrieving data from Facebook. Try subscribing to fewer users or try logging off and logging in again.", AI1EC_PLUGIN_NAME );
  636. }
  637. // An exception happened, set it so that an error alert can be visualized in the front-end.
  638. $to_return['exception'] = TRUE;
  639. $to_return['message'] = $message;
  640. }
  641. }
  642. echo json_encode( $to_return );
  643. die();
  644. }
  645. /**
  646. * Logs out the user from Facebook
  647. *
  648. */
  649. private function do_facebook_logout() {
  650. $this->clear_facebook_data_from_session_and_db_and_disable_cron();
  651. // Get an instance of the Facebook class
  652. $facebook = $this->facebook_instance_factory();
  653. // Get the logout URL from the Facebook Class
  654. $logout = $facebook->getLogoutUrl();
  655. $this->facebook->setAccessToken('');
  656. // Redirect the user to the logout url, facebook will redirect him to our page
  657. wp_redirect( $logout );
  658. }
  659. /**
  660. * Check if the plugin has been configured, i.e. the user has entered data in the settings page
  661. *
  662. * @return boolean TRUE if all settings have been set, FALSE otherwise
  663. */
  664. private function is_plugin_configured() {
  665. // Get the plugin settings.
  666. $plugin_settings = $this->get_plugin_settings( get_class( $this ) );
  667. foreach ( $this->settings as $setting ) {
  668. // Check only settings that appear on the settings page
  669. if ( $setting['admin-page'] === TRUE && empty( $plugin_settings[$setting['id']] ) ) {
  670. return FALSE;
  671. }
  672. }
  673. return TRUE;
  674. }
  675. /**
  676. * Checks if the user set a valid App Id in the settings page. If the id is valid, it's saved in the plugin settings, otherwise an appropriate error message is shown.
  677. *
  678. * @return boolean $valid Wheter a valid app id has been set or not
  679. */
  680. private function has_valid_app_id() {
  681. $valid = TRUE;
  682. // Get plugin settings
  683. $plugin_settings = $this->get_plugin_settings( get_class( $this ) );
  684. // Let's check that app-id is a valid app id if we already did'n to this.
  685. // We also run this check if the user has changed the app_id in the settings page
  686. if ( empty( $plugin_settings[self::FB_VALID_APP_ID]) || ( (int) $plugin_settings[self::FB_VALID_APP_ID] !== (int) $plugin_settings[self::FB_APP_ID] ) ) {
  687. try {
  688. $this->is_valid_facebook_app_id( $plugin_settings[self::FB_APP_ID] );
  689. // If we arrive here, it's valid, let's save that as valid app id
  690. $this->save_plugin_settings( array( self::FB_VALID_APP_ID => $plugin_settings[self::FB_APP_ID] ), TRUE );
  691. }
  692. catch ( InvalidArgumentException $e ) {
  693. $this->render_error_page( $e->getMessage(), TRUE );
  694. $valid = FALSE;
  695. }
  696. catch ( Exception $e ) {
  697. // This is not the expected error so we log it
  698. $this->render_error_page( $e->getMessage(), TRUE );
  699. $valid = FALSE;
  700. }
  701. }
  702. return $valid;
  703. }
  704. /**
  705. * Return an error message if set, FALSE otherwise
  706. *
  707. * @return mixed <boolean, string>
  708. */
  709. public function are_there_any_errors_to_show_on_calendar_settings_page() {
  710. return isset( $this->error_message_after_validating_app_id_and_secret ) ? $this->error_message_after_validating_app_id_and_secret : FALSE;
  711. }
  712. /**
  713. * Returns the currently logged on user from cache if set
  714. *
  715. * @return Ai1ec_Facebook_Current_User or NULL if it is not set
  716. */
  717. private function get_current_facebook_user_from_cache() {
  718. try{
  719. $data = $this->persistance_context->get_data_from_persistence();
  720. $data = unserialize( $data );
  721. } catch ( Ai1ec_Cache_Not_Set_Exception $e ) {
  722. $data = null;
  723. }
  724. return $data;
  725. }
  726. /**
  727. * Sets the user as the current user in cache.
  728. *
  729. * @param Ai1ec_Facebook_Current_User $user
  730. */
  731. private function save_facebook_user_in_cache( Ai1ec_Facebook_Current_User $user ) {
  732. try {
  733. $this->persistance_context->write_data_to_persistence(
  734. serialize( $user )
  735. );
  736. } catch ( Ai1ec_Cache_Not_Set_Exception $excpt ) {
  737. // we are on a slow lane
  738. }
  739. }
  740. /**
  741. * (non-PHPdoc)
  742. * @see Ai1ec_Connector_Plugin::render_tab_content()
  743. */
  744. public function render_tab_content() {
  745. // Load CSS for the plugin
  746. $this->load_css();
  747. $this->render_opening_div_of_tab();
  748. $facebook = $this->facebook_instance_factory();
  749. $logged_in = FALSE;
  750. // Check if the user has been saved into the session.
  751. if( $this->get_current_facebook_user_from_cache() !== NULL ) {
  752. $facebook_user = $this->get_current_facebook_user_from_cache();
  753. $logged_in = TRUE;
  754. } else {
  755. $facebook_user = Ai1ec_Facebook_Factory::get_facebook_user_instance( $facebook );
  756. $logged_in = $facebook_user->do_login();
  757. }
  758. global $ai1ec_view_helper;
  759. // Login or logout url will be needed depending on current user state.
  760. if ( $logged_in ) {
  761. // We have a valisave_current_facebook_user_in_sessiond token, save it
  762. $this->save_plugin_variable( self::FB_TOKEN, $facebook_user->get_token() );
  763. // Save the current user in session
  764. $this->save_facebook_user_in_cache( $facebook_user );
  765. // Create all multi selects
  766. $facebook_tab = Ai1ec_Facebook_Factory::get_facebook_tab_instance();
  767. // IF we have errors, set them in the class.
  768. if( isset( $this->_error_messages ) ) {
  769. $facebook_tab->set_error_messages( $this->_error_messages );
  770. }
  771. // If we have messages, set them in the class.
  772. if( isset( $this->_information_message ) ) {
  773. $facebook_tab->set_informational_messages( $this->_information_message );
  774. }
  775. // Render the tab.
  776. $facebook_tab->render_tab( $facebook_user, Ai1ec_Facebook_Graph_Object_Collection::$fb_all_types );
  777. // Close the div
  778. $this->render_closing_div_of_tab();
  779. } else {
  780. if( isset( $this->error_message_after_validating_app_id_and_secret ) ) {
  781. $this->render_error_page( $this->error_message_after_validating_app_id_and_secret );
  782. }
  783. $modal_html = '';
  784. $login_url = '#';
  785. $facebook_tab = Ai1ec_Facebook_Factory::get_facebook_tab_instance();
  786. $question_mark = $facebook_tab->render_question_mark_for_facebook();
  787. if( $this->at_least_one_config_field_is_set( $this->generate_settings_array_for_admin_view() ) ) {
  788. // Get login url.
  789. $login_url = $this->get_facebook_login_url();
  790. } else {
  791. $modal_html = $facebook_tab->render_modal_for_facebook_app_id_and_secret_and_return_html();
  792. }
  793. $args = array(
  794. 'login_url' => $login_url,
  795. 'modal_html' => $modal_html,
  796. 'submit_name' => self::FB_SUBMIT_MODAL_NAME,
  797. 'question_mark' => $question_mark,
  798. );
  799. $ai1ec_view_helper->display_admin( 'plugins/facebook/user_login.php', $args );
  800. $this->render_closing_div_of_tab();
  801. }
  802. }
  803. /**
  804. * Enqueues styles for the plugin
  805. */
  806. private function load_css() {
  807. global $ai1ec_view_helper;
  808. $ai1ec_view_helper->admin_enqueue_style( 'ai1ec-facebook', 'plugins/facebook.css' );
  809. }
  810. /**
  811. * Returns the facebook login url. It's cached to avoid invalidating the CRSF token
  812. *
  813. */
  814. private function get_facebook_login_url() {
  815. if( ! isset( $this->facebook_login_url ) ) {
  816. $facebook = $this->facebook_instance_factory();
  817. $params = array(
  818. 'scope' => self::FB_SCOPE,
  819. );
  820. $this->facebook_login_url = $facebook->getLoginUrl( $params );
  821. }
  822. return $this->facebook_login_url;
  823. }
  824. /**
  825. * Returns an instance of the facebook class. The token is added if present.
  826. *
  827. * @return Facebook_WP_Extend_Ai1ec
  828. */
  829. public function facebook_instance_factory() {
  830. if( ! isset( $this->facebook ) ) {
  831. // Get plugin settings.
  832. $plugin_settings = $this->get_plugin_settings( get_class( $this ) );
  833. // Create our Application instance.
  834. $facebook = new Facebook_WP_Extend_Ai1ec( array(
  835. 'appId' => $plugin_settings[self::FB_APP_ID],
  836. 'secret' => $plugin_settings[self::FB_APP_SECRET],
  837. ) );
  838. // Retrieve the token from the configuration.
  839. $token = $this->get_plugin_variable( 'facebook-token' );
  840. // If the token was set, use it.
  841. if( $token ) {
  842. $facebook->setAccessToken( $token );
  843. }
  844. $this->facebook = $facebook;
  845. }
  846. return $this->facebook;
  847. }
  848. /**
  849. * Refresh a multiselect through AJAX. This is called after a succesful remove.
  850. *
  851. */
  852. private function refresh_multiselect( $type ) {
  853. $facebook_tab = Ai1ec_Facebook_Factory::get_facebook_tab_instance();
  854. $facebook_user = $this->get_current_facebook_user_from_cache();
  855. return $facebook_tab->render_multi_select( $type, $facebook_user->get_id() );
  856. }
  857. /**
  858. * Fetches data from Facebook for the specified type and then updates the DB
  859. *
  860. * @param string $type the facebook graph object type to sync
  861. *
  862. * @return array
  863. */
  864. public function do_facebook_sync( $type ) {
  865. $response = array(
  866. "errors" => false,
  867. "error_messages" => array(),
  868. );
  869. $fgoc = Ai1ec_Facebook_Factory::get_facebook_graph_object_collection( array() );
  870. // Set the an instance of the Facebook class
  871. $facebook = $this->facebook_instance_factory();
  872. $fgoc->set_facebook( $facebook );
  873. // Set the currently active user.
  874. if( $this->get_current_facebook_user_from_cache() !== NULL ) {
  875. // If we have a seesion, we use it
  876. $fgoc->set_facebook_user( $this->get_current_facebook_user_from_cache() );
  877. } else {
  878. // We don't have a session, this is probably the cron. get a new user
  879. $current_user = Ai1ec_Facebook_Factory::get_facebook_user_instance( $facebook );
  880. // Do login
  881. $logged_in = $current_user->do_login();
  882. if( $logged_in ) {
  883. $fgoc->set_facebook_user( $current_user );
  884. } else {
  885. $this->set_error_message_for_cron( $current_user->get_error_message() );
  886. }
  887. }
  888. // Set the type of the collection.
  889. $fgoc->set_type( $type );
  890. try {
  891. $fgoc->sync_facebook_users();
  892. $response['message'] = sprintf( __( "Fetching data for %s has completed succesfully", AI1EC_PLUGIN_NAME ),
  893. Ai1ec_Facebook_Graph_Object_Collection::get_type_printable_text( $type ) );
  894. }
  895. catch ( WP_FacebookApiException $e ) {
  896. // The first FQL query failed and nothing has changed
  897. $response['errors'] = TRUE;
  898. $response['error_messages'][] = $e->getMessage();
  899. }
  900. catch ( Ai1ec_Facebook_Friends_Sync_Exception $e ) {
  901. // The first query failed but we have something to return
  902. $response['errors'] = TRUE;
  903. $response['error_messages'] = array_merge( $response['error_messages'], $e->get_error_messages() );
  904. }
  905. return $response;
  906. }
  907. /**
  908. * (non-PHPdoc)
  909. * @see Ai1ec_Connector_Plugin::handle_feeds_page_post()
  910. */
  911. public function handle_feeds_page_post() {
  912. // Get tag and category here as they are needed in more than one function.
  913. $category = isset( $_POST['ai1ec_facebook_feed_category'] )
  914. ? $_POST['ai1ec_facebook_feed_category']
  915. : '';
  916. $tag = isset( $_POST['ai1ec_facebook_feed_tags'] )
  917. ? $_POST['ai1ec_facebook_feed_tags']
  918. : '';
  919. $enable_comments = false;
  920. $display_map = false;
  921. if (
  922. isset( $_POST['ai1ec_facebook_comments_enabled'] ) &&
  923. $_POST['ai1ec_facebook_comments_enabled'] > 0
  924. ) {
  925. $enable_comments = 1;
  926. }
  927. if (
  928. isset( $_POST['ai1ec_facebook_map_display_enabled'] ) &&
  929. $_POST['ai1ec_facebook_map_display_enabled'] > 0
  930. ) {
  931. $display_map = 1;
  932. }
  933. // Handle when the user wants to subscribe to his events
  934. if( isset( $_POST['ai1ec_facebook_subscribe_yours'] ) ) {
  935. // Get the user from session
  936. $facebook_user = $this->get_current_facebook_user_from_cache();
  937. // Get the id of the user
  938. $current_user_id = $facebook_user->get_id();
  939. // SUbscribe to it
  940. $this->subscribe_users(
  941. array( $current_user_id ),
  942. Ai1ec_Facebook_Graph_Object_Collection::FB_USER,
  943. $category,
  944. $tag,
  945. $enable_comments,
  946. $display_map
  947. );
  948. // Set the user as subscribed
  949. $facebook_user->set_subscribed( TRUE );
  950. // Set tag and categorsave_current_facebook_user_in_sessiony for the current user
  951. $facebook_user->set_category( $category );
  952. $facebook_user->set_tag( $tag );
  953. $facebook_user->set_enable_comments( $enable_comments );
  954. $facebook_user->set_display_map( $display_map );
  955. // Save the user back into the Session
  956. $this->save_facebook_user_in_cache( $facebook_user );
  957. }
  958. // Handle when the user logs out
  959. if( isset( $_POST['ai1ec_logout_facebook'] ) ) {
  960. $this->do_facebook_logout();
  961. }
  962. // Handle when the user subscribe other users.
  963. if ( isset( $_POST['ai1ec_subscribe_users'] ) ) {
  964. foreach (
  965. Ai1ec_Facebook_Graph_Object_Collection::$fb_all_types as $type
  966. ) {
  967. $name = Ai1ec_Facebook_Tab::FB_MULTISELECT_NAME . $type;
  968. if ( isset( $_POST[$name] ) ) {
  969. $this->subscribe_users(
  970. $_POST[$name],
  971. $type,
  972. $category,
  973. $tag,
  974. $enable_comments,
  975. $display_map
  976. );
  977. }
  978. }
  979. }
  980. // Handle when saving app_id_and secret from the modal
  981. if( isset( $_POST[self::FB_SUBMIT_MODAL_NAME] ) ) {
  982. $app_id = $_POST['ai1ec_facebook_app_id_modal'];
  983. $secret = $_POST['ai1ec_facebook_app_secret_modal'];
  984. $facebook_app = Ai1ec_Facebook_Factory::get_facebook_application_instance( $app_id, $secret );
  985. try {
  986. $facebook_app->get_back_an_access_token_from_facebook_for_the_app();
  987. $this->update_app_id_and_secret( $app_id, $secret );
  988. $this->do_facebook_login();
  989. }
  990. catch ( Ai1ec_Error_Validating_App_Id_And_Secret $e ) {
  991. $this->error_message_after_validating_app_id_and_secret = __( $e->getMessage(), AI1EC_PLUGIN_NAME );
  992. }
  993. catch ( Exception $e ) {
  994. $this->error_message_after_validating_app_id_and_secret = __( $e->getMessage(), AI1EC_PLUGIN_NAME );
  995. }
  996. }
  997. }
  998. /**
  999. * Save app id and secret and unset Facebook instance
  1000. *
  1001. * @param string $app_id
  1002. * @param string $secret
  1003. */
  1004. private function update_app_id_and_secret( $app_id, $secret ) {
  1005. $this->save_plugin_variable( self::FB_APP_ID , $app_id );
  1006. $this->save_plugin_variable( self::FB_APP_SECRET , $secret );
  1007. // unset the facebook instance because it still has previou app id and secret
  1008. unset( $this->facebook );
  1009. }
  1010. /**
  1011. * Try to login the user to Facebook
  1012. *
  1013. */
  1014. private function do_facebook_login() {
  1015. $login_url = $this->get_facebook_login_url();
  1016. wp_redirect( $login_url );
  1017. }
  1018. /**
  1019. * Subscribe the passed users and fetches from Facebook their events. The events and the users are tagged with the relative category and tag.
  1020. *
  1021. * @param array $users the users to subscribe to
  1022. *
  1023. * @param string $type the type of the users array (user / page / group)
  1024. *
  1025. * @param int $category the category
  1026. *
  1027. * @param string $tag the tags
  1028. *
  1029. * @param bool $enable_comments whereas comments shall be enabled
  1030. *
  1031. * @param bool $display_map whereas map shall be displayed
  1032. */
  1033. private function subscribe_users(
  1034. array $users,
  1035. $type,
  1036. $category,
  1037. $tag,
  1038. $enable_comments = false,
  1039. $display_map = false
  1040. ) {
  1041. // Get a collection object
  1042. $fgoc = Ai1ec_Facebook_Factory::get_facebook_graph_object_collection( $users );
  1043. // Set the category (needed to attach to the users, not to the events)
  1044. $fgoc->set_category( $category );
  1045. // Set the tags (needed to attach to the users, not to the events)
  1046. $fgoc->set_tag( $tag );
  1047. // Set the comments enablement (needed to attach to the users, not to the events)
  1048. $fgoc->set_enable_comments( $enable_comments );
  1049. // Set the map display (needed to attach to the users, not to the events)
  1050. $fgoc->set_display_map( $display_map );
  1051. // Set the an instance of the Facebook class
  1052. $facebook = $this->facebook_instance_factory();
  1053. $fgoc->set_facebook( $facebook );
  1054. // Set the currently active user.
  1055. $fgoc->set_facebook_user( $this->get_current_facebook_user_from_cache() );
  1056. // Set the type of the collection.
  1057. $fgoc->set_type( $type );
  1058. try {
  1059. // Update the subscription status.
  1060. $fgoc->update_subscription_status( TRUE, $category, $tag );
  1061. $this->_information_message[] = $fgoc->refresh_events();
  1062. } catch ( Ai1ec_Facebook_Db_Exception $e ) {
  1063. $this->_error_messages = array_merge( $this->_error_messages, $e->get_error_messages() );
  1064. } catch ( WP_FacebookApiException $e ) {
  1065. if ( $e->getCode() === self::FB_OAUTH_EXC_CODE ) {
  1066. $message = __( "Something went wrong with Facebook authentication. Try to log out and then log in again.", AI1EC_PLUGIN_NAME );
  1067. $this->_error_messages = array_merge( $this->_error_messages, array( $message ) );
  1068. $facebook->destroySession();
  1069. } else {
  1070. $message = __( "Something went wrong when retrieving data from Facebook. Try subscribing to fewer calendars.", AI1EC_PLUGIN_NAME );
  1071. $this->_error_messages = array_merge( $this->_error_messages, array( $message ) );
  1072. }
  1073. }
  1074. }
  1075. /**
  1076. * Creates the HTML to display on the "Add new event" page so that the user can choose to export the event to Facebook.
  1077. *
  1078. * @return string an empty string or the html to show
  1079. */
  1080. public function render_export_box() {
  1081. global $post;
  1082. // We only want this for events.
  1083. if( $post->post_type !== AI1EC_POST_TYPE ) {
  1084. return;
  1085. }
  1086. try {
  1087. $event = new Ai1ec_Event( $post->ID );
  1088. } catch( Ai1ec_Event_Not_Found $e ) {
  1089. // Post exists, but event data hasn't been saved yet. Create new event
  1090. // object.
  1091. $event = NULL;
  1092. }
  1093. // If we have an event end the event was imported from facebook, return, we can't export it.
  1094. if( $event !== NULL && $event->facebook_status === Ai1ecFacebookConnectorPlugin::FB_IMPORTED_EVENT ) {
  1095. return;
  1096. }
  1097. // Let's check if we have a user in session
  1098. if( $this->get_current_facebook_user_from_cache() === NULL ) {
  1099. // No user in session, let's see if we have a token and the user can login.
  1100. $facebook = $this->facebook_instance_factory();
  1101. $current_user = Ai1ec_Facebook_Factory::get_facebook_user_instance( $facebook );
  1102. $logged_in = $current_user->do_login();
  1103. // If the user couldn't login, do not print anything but return an hidden inpsave_current_facebook_user_in_sessionut, in this way the
  1104. // plugin save post handler simply returns.
  1105. if( $logged_in === FALSE ) {
  1106. $hidden_input_name = self::FB_HIDDEN_INPUT_NO_ACTIVE_TOKEN;
  1107. echo "<input type='hidden' name='$hidden_input_name' value='1' />";
  1108. return;
  1109. }
  1110. // Save it in session.
  1111. $this->save_facebook_user_in_cache( $current_user );
  1112. }
  1113. $checked = '';
  1114. if( $event !== NULL && $event->facebook_status === Ai1ecFacebookConnectorPlugin::FB_EXPORTED_EVENT ) {
  1115. $checked = 'checked';
  1116. }
  1117. $link = '';
  1118. $modal_html = '';
  1119. if( isset( $event->facebook_eid ) && ( int ) $event->facebook_eid !== 0 ) {
  1120. $link_label = __( "Linked Facebook event", AI1EC_PLUGIN_NAME );
  1121. $link = "<div id='ai1ec-facebook-linked-event'><a href='https://www.facebook.com/events/{$event->facebook_eid}'>$link_label</a></div>";
  1122. // We include the modal only when the event has been exported to facebook
  1123. $twitter_bootstrap_modal = new Ai1ec_Bootstrap_Modal( __( "Would you like to delete the linked Facebook event or keep it? If you choose to keep it and later you export this event again, a new one will be created.", AI1EC_PLUGIN_NAME ) );
  1124. $twitter_bootstrap_modal->set_header_text( __( "Unpublish Facebook event?", AI1EC_PLUGIN_NAME ) );
  1125. $twitter_bootstrap_modal->set_id( "ai1ec-facebook-export-modal" );
  1126. $twitter_bootstrap_modal->set_delete_button_text( __( "Delete event", AI1EC_PLUGIN_NAME ) );
  1127. $twitter_bootstrap_modal->set_keep_button_text( __( "Keep event", AI1EC_PLUGIN_NAME ) );
  1128. $modal_html = $twitter_bootstrap_modal->render_modal_and_return_html();
  1129. }
  1130. $label = __( 'Export event to Facebook?', AI1EC_PLUGIN_NAME );
  1131. $name = self::FB_EXPORT_CHKBX_NAME;
  1132. $html = <<<HTML
  1133. <div id="ai1ec-facebook-publish" class="misc-pub-section">
  1134. <div id="ai1ec-facebook-small-logo"></div>
  1135. <label for="$name">
  1136. $label
  1137. </label>
  1138. <input type="checkbox" $checked name="$name" id="$name" value="1" />
  1139. $link
  1140. </div>
  1141. <div class="timely">
  1142. $modal_html
  1143. </div>
  1144. HTML;
  1145. echo $html;
  1146. }
  1147. /**
  1148. * Return a Facebook instance if we have a valid access token
  1149. *
  1150. * @throws Ai1ec_No_Valid_Facebook_Access_Token
  1151. * @return Facebook
  1152. */
  1153. private function get_facebook_instance_if_there_is_a_valid_access_token() {
  1154. if( $this->check_if_we_have_a_valid_access_token() ) {
  1155. $facebook = $this->facebook_instance_factory();
  1156. return $facebook;
  1157. } else {
  1158. $this->set_error_message_for_no_valid_access_token();
  1159. throw new Ai1ec_No_Valid_Facebook_Access_Token();
  1160. }
  1161. }
  1162. /**
  1163. * Set an error message because no valid access token was found
  1164. *
  1165. */
  1166. private function set_error_message_for_no_valid_access_token() {
  1167. $message = array(
  1168. "label" => __( 'All-in-One Event Calendar Facebook Import Error', AI1EC_PLUGIN_NAME ),
  1169. "message" => __( "We couldn't synchronize data with Facebook as we don't have a valid access token. Try to log out of Facebook from the <strong>Events</strong> &gt; <strong>Calendar Feeds</strong> screen, then log in again.", AI1EC_PLUGIN_NAME ),
  1170. "message_type" => "error",
  1171. );
  1172. update_option( Ai1ecFacebookConnectorPlugin::FB_OPTION_CRON_NOTICE, $message );
  1173. }
  1174. /**
  1175. * If the checkbox for exporting events to facebook is checked, export the event to facebook
  1176. *
  1177. * @param Ai1ec_Event $event a referemce to the event which is being saved. We need a refernce as we are going to modify it
  1178. */
  1179. public function handle_save_event( Ai1ec_Event &$event ) {
  1180. // If the hidden input that states that no active token is present was set, just return;
  1181. if( isset( $_POST[self::FB_HIDDEN_INPUT_NO_ACTIVE_TOKEN] ) ) {
  1182. return;
  1183. }
  1184. // If the checkbox is not set, reset eid and status
  1185. if( ! isset( $_POST[self::FB_EXPORT_CHKBX_NAME] ) ) {
  1186. $facebook_eid = $event->facebook_eid;
  1187. $event->facebook_eid = 0;
  1188. $event->facebook_status = self::FB_STANDARD_EVENT;
  1189. $event->facebook_user = 0;
  1190. // If the hidden field was added, delete the event from Facebook
  1191. if( isset( $_POST[self::FB_HIDDEN_INPUT_REMOVE_EVENT_NAME] ) ) {
  1192. try {
  1193. $facebook = $this->get_facebook_instance_if_there_is_a_valid_access_token();
  1194. $facebook_event = Ai1ec_Facebook_Factory::get_facebook_event_instance();
  1195. $facebook_event->set_id( $facebook_eid );
  1196. $facebook_event->delete_from_facebook( $facebook );
  1197. } catch ( Ai1ec_No_Valid_Facebook_Access_Token $e ) {
  1198. // The message is set automatically
  1199. }
  1200. }
  1201. return;
  1202. }
  1203. // Check if we have a valid access token, otherwise there is no sense in going on.
  1204. try {
  1205. $facebook = $this->get_facebook_instance_if_there_is_a_valid_access_token();
  1206. $facebook_event = Ai1ec_Facebook_Factory::get_facebook_event_instance();
  1207. $facebook_event->populate_event_from_post_and_ai1ec_event( $_POST, $event );
  1208. $result = $facebook_event->save_to_facebook( $facebook );
  1209. if( is_array( $result ) && ! empty( $result ) ) {
  1210. $event->facebook_eid = $result['id'];
  1211. $event->facebook_status = self::FB_EXPORTED_EVENT;
  1212. }
  1213. } catch ( Ai1ec_No_Valid_Facebook_Access_Token $e ) {
  1214. // The message is set automatically
  1215. }
  1216. }
  1217. /**
  1218. * This function is called when the user deletes a calendar event and we must delete the linked facebook event
  1219. *
  1220. * @param Ai1ec_Event $event
  1221. */
  1222. public function handle_delete_event( Ai1ec_Event $event