/**
  * Uses the Brightcove oAuth API to retrieve and store an access key for use with requests. The token is stored as a transient
  * with an expiration time matching that which is returned from Brightcove. The call to the API is only performed if that transient
  * is invalid or expired. Return a WP_Error object for use in WordPress in the case of failure.
  *
  * @since  1.0.0
  *
  * @see    get_transient()
  * @see    set_transient()
  * @see    delete_transient()
  * @see    wp_remote_post()
  *
  * @param bool $force_new_token whether or not to obtain a new OAuth token
  * @param bool $retry           true to retry on failure or false
  *
  * @return string|WP_Error
  */
 public function _request_access_token($force_new_token = false, $retry = true)
 {
     $transient_name = $this->transient_name;
     $token = $force_new_token ? false : get_transient($transient_name);
     if (!$token) {
         $endpoint = esc_url_raw(self::ENDPOINT_BASE . '/access_token?grant_type=client_credentials');
         $request = wp_remote_post($endpoint, $this->_http_headers);
         if ('400' == wp_remote_retrieve_response_code($request)) {
             // Just in case
             delete_transient($transient_name);
             $oauth_error = new WP_Error('oauth_access_token_failure', sprintf(__('There is a problem with your Brightcove %1$s or %2$s', 'brightcove'), '<code>client_id</code>', '<code>client_secret</code>'));
             BC_Logging::log(sprintf('BC OAUTH ERROR: %s', $oauth_error->get_error_message()));
             return $oauth_error;
         }
         $body = wp_remote_retrieve_body($request);
         $data = json_decode($body);
         if (isset($data->access_token)) {
             $token = $data->access_token;
             set_transient($transient_name, $token, $data->expires_in);
         } else {
             if (!$retry) {
                 return new WP_Error('oauth_access_token_response_failure', sprintf(esc_html__('oAuth API did not return us an access token', 'brightcove')));
             }
             return $this->_request_access_token($force_new_token, false);
         }
     }
     return $token;
 }
 /**
  * Provides the handler for saving/updating source data
  *
  * @return bool/WP_Error
  */
 public function save_account()
 {
     global $bc_accounts;
     if (!isset($_POST['brightcove-check_oauth'])) {
         return false;
     }
     if (!current_user_can('brightcove_manipulate_accounts')) {
         $error_message = esc_html__('You do not have permission to manage this account.', 'brightcove');
         BC_Logging::log(sprintf('ACCOUNT: %s', $error_message));
         $this->notices[] = array('message' => $error_message, 'type' => 'error');
         return new WP_Error('brightcove-account-manage-permissions', $error_message);
     }
     if (!wp_verify_nonce($_POST['brightcove-check_oauth'], '_brightcove_check_oauth_for_source')) {
         return false;
     }
     // Only go through the oAuth credential validation when we're adding a new account or editing the account's credentials (not default players etc)
     if ('create' === $_POST['source-action']) {
         $required_keys = array('brightcove-check_oauth', 'source-account-id', 'source-client-id', 'source-client-secret', 'source-name');
         foreach ($required_keys as $key) {
             if (!array_key_exists($key, $_POST)) {
                 return false;
             }
         }
         $account_id = BC_Utility::sanitize_id($_POST['source-account-id']);
         $client_id = sanitize_text_field($_POST['source-client-id']);
         $client_secret = BC_Utility::get_sanitized_client_secret($_POST['source-client-secret']);
         $account_name = sanitize_text_field(stripslashes_deep($_POST['source-name']));
         $set_default = isset($_POST['source-default-account']) && 'on' === $_POST['source-default-account'] ? 'default' : '';
         $hash = BC_Utility::get_hash_for_account(array('account_id' => $account_id, 'client_id' => $client_id, 'client_secret' => $client_secret));
         $account = $bc_accounts->get_account_by_hash($hash);
         if ($account) {
             // Account already exists
             $error_message = esc_html__('The Brightcove credentials provided already exist.', 'brightcove');
             BC_Logging::log(sprintf('BC ACCOUNTS: %s', $error_message));
             $this->notices[] = array('message' => $error_message, 'type' => 'error');
             return new WP_Error('bc-account-exists-error', $error_message);
         }
         if (!$bc_accounts->add_account($account_id, $client_id, $client_secret, $account_name, $set_default, false)) {
             $error_message = esc_html__('We could not authenticate your credentials with Brightcove', 'brightcove');
             BC_Logging::log(sprintf('BC OAUTH ERROR: %s', $error_message));
             $this->notices[] = array('message' => $error_message, 'type' => 'error');
             return new WP_Error('bc-oauth-error', $error_message);
         }
         BC_Utility::clear_cached_api_requests('all');
         $bc_accounts->set_current_account_by_id($account_id);
         $players = new BC_Players();
         $players->sync_players();
     }
     if ('update' === $_POST['source-action']) {
         if (isset($_POST['source-default-account']) && 'on' === $_POST['source-default-account']) {
             update_option('_brightcove_default_account', sanitize_text_field($_POST['hash']));
         }
     }
     // Deleting transient to allow syncing from the new account, otherwise we won't be able to sync it until this transient expires.
     delete_transient('brightcove_sync_videos');
     $this->notices[] = array('message' => sprintf('%s <a href="%s">%s</a>.', esc_html__('Congratulations! Your credentials have been authenticated. Return to', 'brightcove'), admin_url('admin.php?page=brightcove-sources '), esc_html__('Settings', 'brightcove')), 'type' => 'updated');
     return true;
 }
 /**
  * Take an uploaded file, and a supplied name and create a video ID and
  * ingestion request for them.
  * @param $name video name
  * @return bool|string|WP_Error
  */
 public function process_uploaded_video($files, $account_hash, $tags, $name)
 {
     global $bc_accounts;
     $status = array('upload' => 'fail', 'ingest' => 'fail', 'url' => '');
     $account = $bc_accounts->get_account_by_hash($account_hash);
     if (!$account) {
         return new WP_Error('invalid-account', esc_html__('Invalid account', 'brightcove'));
     }
     if (isset($files) && isset($files['file'])) {
         $cms_api = $this->cms_api;
         // check that file is supported by WP
         if ($this->check_allowed_file($files['file']) === false) {
             $error_message = esc_html__('Video type is not supported', 'brightcove');
             BC_Logging::log(sprintf('VIDEO UPLOAD: %s', $error_message));
             return new WP_Error('video_upload_error', $error_message);
         }
         $uploaded = wp_handle_upload($files['file'], array('test_form' => false));
         if (isset($uploaded['error'])) {
             $error_message = esc_html__($uploaded['error'], 'brightcove');
             BC_Logging::log(sprintf('VIDEO UPLOAD ERROR: %s', $error_message));
             return new WP_Error('video_upload_error', $error_message);
         } else {
             $status['upload'] = 'success';
             $status['url'] = $uploaded['url'];
         }
         $tags_array = $this->process_tags($tags);
         $data = array();
         if (false !== $tags_array) {
             $data['tags'] = $tags_array;
         }
         $video_id_creation_result = $cms_api->video_add($name, $data);
         if (false === $video_id_creation_result) {
             return new WP_Error(esc_html__('Unable to create a video on brightcove side', 'brightcove'));
         }
         if (is_wp_error($video_id_creation_result)) {
             return $video_id_creation_result;
         }
         if (isset($video_id_creation_result['created_at'])) {
             $video_id = BC_Utility::sanitize_and_generate_meta_video_id($video_id_creation_result['id']);
             $status['video_id'] = $video_id_creation_result['id'];
             BC_Utility::add_pending_upload($video_id, $uploaded['file']);
             $video_ingestion_request_result = $cms_api->video_upload($video_id_creation_result['id'], $uploaded['url']);
             if (is_array($video_ingestion_request_result) && isset($video_ingestion_request_result['id'])) {
                 $status['ingest'] = 'success';
                 $status['ingestId'] = $video_ingestion_request_result['id'];
             }
             $status['videoDetails'] = $video_id_creation_result;
         }
     }
     return $status;
 }
 /**
  * Sends API requests to remote server
  *
  * Sends the request to the remote server using the appropriate method and
  * logs any errors in the event of failures.
  *
  * @param string  $url             the endpoint to connect to
  * @param string  $method          the http method to use
  * @param array   $data            array of further data to send to the server
  * @param boolean $force_new_token whether or not to force obtaining a new oAuth token
  *
  *
  * @return mixed the return data from the call of false if a failure occurred
  */
 protected function send_request($url, $method = 'GET', $data = array(), $force_new_token = false)
 {
     $method = strtoupper(sanitize_text_field($method));
     $allowed_methods = array('DELETE', 'GET', 'PATCH', 'POST', 'PUT', 'JSON_DELETE', 'JSON_POST');
     //only allow methods required by the brightcove APIs
     if (!in_array($method, $allowed_methods)) {
         return false;
     }
     $url = esc_url_raw($url);
     $transient_key = false;
     if ($method === "GET") {
         $hash = substr(BC_Utility::get_hash_for_object(array("url" => $url, "data" => $data)), 0, 20);
         $transient_key = "_bc_request_{$hash}";
         $cached_request = BC_Utility::get_cache_item($transient_key);
         if (false !== $cached_request) {
             return $cached_request;
         }
     }
     $auth_header = $this->get_authorization_header($force_new_token);
     if (is_wp_error($auth_header)) {
         return $auth_header;
     }
     add_filter('http_request_timeout', array($this, 'increase_http_timeout'));
     $headers = array('Authorization' => $auth_header);
     // All JSON_ methods are used to indicate that application/json is the content type
     if (false !== strpos($method, 'JSON')) {
         $headers['Content-type'] = 'application/json';
         $method = str_replace('JSON_', '', $method);
     } else {
         $headers['Content-type'] = 'application/x-www-form-urlencoded';
     }
     $args = array('headers' => $headers);
     switch ($method) {
         case 'GET':
             $request = $this->cached_get($url, $args);
             break;
         case 'POST':
             $args['body'] = wp_json_encode($data);
             $request = wp_remote_post($url, $args);
             break;
         default:
             $args['method'] = $method;
             $args['body'] = wp_json_encode($data);
             if (!empty($data)) {
                 $args['body'] = json_encode($data);
             }
             $request = wp_remote_request($url, $args);
             break;
     }
     if (401 === wp_remote_retrieve_response_code($request)) {
         if ("Unauthorized" === $request['response']['message']) {
             // Token may have expired, so before we give up, let's retry
             // the request with a fresh OAuth token.
             if (!$force_new_token) {
                 return $this->send_request($url, $method, $data, true);
             } else {
                 $this->errors[] = array('url' => $url, 'error' => new WP_Error('unauthorized-oauth', __('API says permission denied, check your client ID and client secret', 'brightcove')));
                 return false;
             }
         }
     }
     //log errors for further processing or return the body
     if (is_wp_error($request)) {
         $this->errors[] = array('url' => $url, 'error' => $request->get_error_message());
         BC_Logging::log(sprintf('WP_ERROR: %s', $request->get_error_message()));
         return false;
     }
     $body = json_decode(wp_remote_retrieve_body($request), true);
     $successful_response_codes = array(200, 201, 202, 204);
     if (!in_array(wp_remote_retrieve_response_code($request), $successful_response_codes)) {
         $message = esc_html__('An unspecified error has occurred.', 'brightcove');
         if (isset($body[0]) && isset($body[0]['error_code'])) {
             $message = $body[0]['error_code'];
         } elseif (isset($body['message'])) {
             $message = $body['message'];
         }
         $this->errors[] = array('url' => $url, 'error' => new WP_Error($request['response']['message'], $message));
         BC_Logging::log(sprintf('BC API ERROR: %s', $message));
         return false;
     }
     if ('204' == wp_remote_retrieve_response_code($request)) {
         return true;
     }
     if ($transient_key && $body && (!defined('WP_DEBUG') || false === WP_DEBUG)) {
         // Store body for 60s to prevent hammering the BC APIs.
         BC_Utility::set_cache_item($transient_key, 'api-request', $body, 60);
     }
     return $body;
 }
 /**
  * In the event playlist object data is stale in WordPress, or a playlist has never been generated,
  * create/update WP data store with Brightcove data.
  *
  * @param      $playlist
  *
  * @return bool|WP_Error
  */
 public function add_or_update_wp_playlist($playlist)
 {
     $hash = BC_Utility::get_hash_for_object($playlist);
     $playlist_id = $playlist['id'];
     $stored_hash = $this->get_playlist_hash_by_id($playlist_id);
     // No change to existing playlist
     if ($hash === $stored_hash) {
         return true;
     }
     $post_excerpt = !is_null($playlist['description']) ? $playlist['description'] : '';
     $post_content = $post_excerpt;
     $post_title = !is_null($playlist['name']) ? $playlist['name'] : '';
     $post_date = new DateTime($playlist['created_at']);
     $post_date = $post_date->format('Y-m-d g:i:s');
     $utc_timezone = new DateTimeZone('GMT');
     $gmt = new DateTime($playlist['created_at'], $utc_timezone);
     $gmt = $gmt->format('Y-m-d g:i:s');
     $playlist_post_args = array('post_type' => 'brightcove-playlist', 'post_title' => $post_title, 'post_name' => $post_title . uniqid(), 'post_content' => $post_content, 'post_excerpt' => $post_excerpt, 'post_date' => $post_date, 'post_date_gmt' => $gmt, 'post_status' => 'publish');
     $existing_post = $this->get_playlist_by_id($playlist_id);
     if ($existing_post) {
         $playlist_post_args['ID'] = $existing_post->ID;
         $post_id = wp_update_post($playlist_post_args, true);
     } else {
         $post_id = wp_insert_post($playlist_post_args, true);
     }
     if (is_wp_error($post_id)) {
         $error_message = $post_id->get_error_message();
         BC_Logging::log(sprintf('BC WORDPRESS ERROR: %s'), $error_message);
         return new WP_Error('post-not-created', $error_message);
     }
     $this->playlist_ids[] = $post_id;
     update_post_meta($post_id, '_brightcove_hash', $hash);
     update_post_meta($post_id, '_brightcove_playlist_id', BC_Utility::sanitize_and_generate_meta_video_id($playlist_id));
     update_post_meta($post_id, '_brightcove_account_id', BC_Utility::sanitize_id($playlist['account_id']));
     update_post_meta($post_id, '_brightcove_playlist_object', $playlist);
     $video_ids = BC_Utility::sanitize_payload_item($playlist['video_ids']);
     foreach ($video_ids as $video_id) {
         update_post_meta($post_id, '_brightcove_video_id', $video_id);
     }
     $meta = array();
     $meta_keys = apply_filters('brightcove_meta_keys', array('favorite', 'limit', 'search'));
     foreach ($meta_keys as $key) {
         if (!empty($playlist[$key])) {
             $meta[$key] = $playlist[$key];
         }
     }
     update_post_meta($post_id, '_brightcove_metadata', $meta);
     return true;
 }
 /**
  * In the event video object data is stale in WordPress, or a video has never been generated,
  * create/update WP data store with Brightcove data.
  *
  * @param      $video
  * @param bool $add_only True denotes that we know the object is not in our library and we are adding it first time to the library. This is to improve the initial sync.
  *
  * @return bool|WP_Error
  */
 public function add_or_update_wp_video($video, $add_only = false)
 {
     $hash = BC_Utility::get_hash_for_object($video);
     $video_id = $video['id'];
     if (!$add_only) {
         $stored_hash = $this->get_video_hash_by_id($video_id);
         // No change to existing playlist
         if ($hash === $stored_hash) {
             return true;
         }
     }
     $post_excerpt = !is_null($video['description']) ? $video['description'] : '';
     $post_content = !is_null($video['long_description']) ? $video['long_description'] : $post_excerpt;
     $post_title = !is_null($video['name']) ? $video['name'] : '';
     $post_date = new DateTime($video['created_at']);
     $post_date = $post_date->format('Y-m-d g:i:s');
     $utc_timezone = new DateTimeZone('GMT');
     $gmt = new DateTime($video['created_at'], $utc_timezone);
     $gmt = $gmt->format('Y-m-d g:i:s');
     $video_post_args = array('post_type' => $this->video_cpt, 'post_title' => $post_title, 'post_content' => $post_content, 'post_excerpt' => $post_excerpt, 'post_date' => $post_date, 'post_date_gmt' => $gmt, 'post_status' => 'publish');
     if (!$add_only) {
         $existing_post = $this->get_video_by_id($video_id);
         if ($existing_post) {
             $video_post_args['ID'] = $existing_post->ID;
             $post_id = wp_update_post($video_post_args);
         } else {
             $post_id = wp_insert_post($video_post_args);
         }
     } else {
         $post_id = wp_insert_post($video_post_args);
     }
     if (!$post_id) {
         $error_message = esc_html__('The video has not been created in WordPress', 'brightcove');
         BC_Logging::log(sprintf('BC WORDPRESS ERROR: %s'), $error_message);
         return new WP_Error('post-not-created', $error_message);
     }
     BC_Logging::log(sprintf(esc_html__('BC WORDPRESS: Video with ID #%d has been created', 'brightcove'), $post_id));
     if (!empty($video['tags'])) {
         wp_set_post_terms($post_id, $video['tags'], 'brightcove_tags', false);
     }
     update_post_meta($post_id, '_brightcove_hash', $hash);
     update_post_meta($post_id, '_brightcove_video_id', BC_Utility::sanitize_and_generate_meta_video_id($video['id']));
     update_post_meta($post_id, '_brightcove_transcoded', $video['complete']);
     update_post_meta($post_id, '_brightcove_account_id', $video['account_id']);
     update_post_meta($post_id, '_brightcove_video_object', $video);
     $meta = array();
     $meta_keys = apply_filters('brightcove_meta_keys', array('images', 'state', 'cue_points', 'custom_fields', 'duration', 'economics'));
     foreach ($meta_keys as $key) {
         if (!empty($video[$key])) {
             $meta[$key] = $video[$key];
         }
     }
     update_post_meta($post_id, '_brightcove_metadata', $meta);
     return true;
 }