Skip to content

Instantly share code, notes, and snippets.

@tamw-wnet
Last active October 12, 2022 15:46
Show Gist options
  • Save tamw-wnet/21f0618fe417e99b7702 to your computer and use it in GitHub Desktop.
Save tamw-wnet/21f0618fe417e99b7702 to your computer and use it in GitHub Desktop.
Foo oAuth plugin, version 2 with persistent logins
<?php
/*
* Plugin Name: Foo oAuth Demo
* Version: 2
* Plugin URI: http://ieg.wnet.org/
* Description: A simple Google oAuth demo plugin
* Author: William Tam
* Author URI: http://ieg.wnet.org/blog/author/tamw/
* Requires at least: 3.6
* Tested up to: 4.2.2
*
* @package WordPress
* @author William Tam
* @since 1.0.0
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
if ( ! defined( 'ABSPATH' ) ) exit;
class foo_oAuth_Demo {
private $dir;
private $file;
private $token;
public function __construct( $file ) {
$this->dir = dirname( $file );
$this->file = $file;
$this->token = 'foo_oauth_demo';
// Register plugin settings
add_action( 'admin_init' , array( $this , 'register_settings' ) );
// Add settings page to menu
add_action( 'admin_menu' , array( $this , 'add_menu_item' ) );
// Add settings link to plugins page
add_filter( 'plugin_action_links_' . plugin_basename( $this->file ) , array( $this , 'add_settings_link' ) );
// setup meta boxes
add_action( 'add_meta_boxes', array( $this, 'meta_box_setup' ), 20 );
add_action( 'save_post', array( $this, 'meta_box_save' ) );
// NEW: setup the wp ajax action for oAuth code exchange
add_action( 'wp_ajax_foo_finish_code_exchange', array($this, 'finish_code_exchange') );
// NEW: setup the wp ajax action to logout from oAuth
add_action( 'wp_ajax_foo_logout_from_google', array($this, 'logout_from_google') );
}
/* The next few functions set up the settings page */
public function add_menu_item() {
add_options_page( 'Foo oAuth Demo Settings' , 'Foo oAuth Demo Settings' , 'manage_options' , 'foo_oauth_demo_settings' , array( $this , 'settings_page' ) );
}
public function add_settings_link( $links ) {
$settings_link = '<a href="options-general.php?page=foo_oauth_demo_settings">Settings</a>';
array_push( $links, $settings_link );
return $links;
}
public function register_settings() {
register_setting( 'foo_oauth_demo_group', 'foo_oauth_demo_settings' );
add_settings_section('settingssection1', 'Google App Settings', array( $this, 'settings_section_callback'), 'foo_oauth_demo_settings');
// you can define EVERYTHING to create, display, and process each settings field as one line per setting below. And all settings defined in this function are stored as a single serialized object.
add_settings_field( 'google_app_client_id', 'Google App Client ID', array( $this, 'settings_field'), 'foo_oauth_demo_settings', 'settingssection1', array('setting' => 'foo_oauth_demo_settings', 'field' => 'google_app_client_id', 'label' => '', 'class' => 'regular-text') );
add_settings_field( 'google_app_client_secret', 'Google App Client Secret', array( $this, 'settings_field'), 'foo_oauth_demo_settings', 'settingssection1', array('setting' => 'foo_oauth_demo_settings', 'field' => 'google_app_client_secret', 'label' => '', 'class' => 'regular-text') );
add_settings_field( 'google_app_redirect_uri', 'Google App Redirect URI', array( $this, 'settings_field'), 'foo_oauth_demo_settings', 'settingssection1', array('setting' => 'foo_oauth_demo_settings', 'field' => 'google_app_redirect_uri', 'label' => '', 'class' => 'regular-text') );
}
public function settings_section_callback() { echo ' '; }
public function settings_field( $args ) {
// This is the default processor that will handle standard text input fields. Because it accepts a class, it can be styled or even have jQuery things (like a calendar picker) integrated in it. Pass in a 'default' argument only if you want a non-empty default value.
$settingname = esc_attr( $args['setting'] );
$setting = get_option($settingname);
$field = esc_attr( $args['field'] );
$label = esc_attr( $args['label'] );
$class = esc_attr( $args['class'] );
$default = ($args['default'] ? esc_attr( $args['default'] ) : '' );
$value = (($setting[$field] && strlen(trim($setting[$field]))) ? $setting[$field] : $default);
echo '<input type="text" name="' . $settingname . '[' . $field . ']" id="' . $settingname . '[' . $field . ']" class="' . $class . '" value="' . $value . '" /><p class="description">' . $label . '</p>';
}
public function settings_page() {
if (!current_user_can('manage_options')) {
wp_die( __('You do not have sufficient permissions to access this page.') );
}
?>
<div class="wrap">
<h2>Foo oAuth Demo Settings</h2>
<p>You'll need to go to the <a href="https://console.developers.google.com">Google Developer Console</a> to setup your project and setup the values below.</p>
<form action="options.php" method="POST">
<?php settings_fields( 'foo_oauth_demo_group' ); ?>
<?php do_settings_sections( 'foo_oauth_demo_settings' ); ?>
<?php submit_button(); ?>
</form>
<!-- We handle the login process on the settings page now -->
<?php $this->write_out_oAuth_JavaScript(); ?>
</div>
<?php
}
// This function is the clearest way to get the oAuth JavaScript onto a page as needed.
private function write_out_oAuth_JavaScript() {
$settings = get_option('foo_oauth_demo_settings', true);
?>
<script language=javascript>
// we declare this variable at the top level scope to make it easier to pass around
var google_access_token = "<?php echo $this->get_google_access_token(); ?>";
jQuery(document).ready(function($) {
var GOOGLECLIENTID = "<?php echo $settings['google_app_client_id']; ?>";
var GOOGLECLIENTREDIRECT = "<?php echo $settings['google_app_redirect_uri']; ?>";
// we don't need the client secret for this, and should not expose it to the web.
function requestGoogleoAuthCode() {
var OAUTHURL = 'https://accounts.google.com/o/oauth2/auth';
var SCOPE = 'profile email openid https://www.googleapis.com/auth/youtube';
var popupurl = OAUTHURL + '?scope=' + SCOPE + '&client_id=' + GOOGLECLIENTID + '&redirect_uri=' + GOOGLECLIENTREDIRECT + '&response_type=code&access_type=offline&prompt=select_account consent';
var win = window.open(popupurl, "googleauthwindow", 'width=800, height=600');
var pollTimer = window.setInterval(function() {
try {
if (win.document.URL.indexOf(GOOGLECLIENTREDIRECT) != -1) {
window.clearInterval(pollTimer);
var response_url = win.document.URL;
var auth_code = gup(response_url, 'code');
console.log(response_url);
win.close();
// We don't have an access token yet, have to go to the server for it
var data = {
action: 'foo_finish_code_exchange',
auth_code: auth_code
};
$.post(ajaxurl, data, function(response) {
console.log(response);
google_access_token = response;
getGoogleUserInfo(google_access_token);
});
}
} catch(e) {}
}, 500);
}
// helper function to parse out the query string params
function gup(url, name) {
name = name.replace(/[\[]/,"\\\[").replace(/[\]]/,"\\\]");
var regexS = "[\\?#&]"+name+"=([^&#]*)";
var regex = new RegExp( regexS );
var results = regex.exec( url );
if( results == null )
return "";
else
return results[1];
}
function getGoogleUserInfo(google_access_token) {
$.ajax({
url: 'https://www.googleapis.com/plus/v1/people/me/openIdConnect',
data: {
access_token: google_access_token
},
success: function(resp) {
var user = resp;
console.log(user);
$('#googleUserName').text('You are logged in as ' + user.name);
loggedInToGoogle = true;
$('#google-login-block').hide();
$('#google-logout-block').show();
},
dataType: "jsonp"
});
}
function logoutFromGoogle() {
$.ajax({
url: ajaxurl,
data: {
action: 'foo_logout_from_google'
},
success: function(resp) {
console.log(resp);
$('#googleUserName').text(resp);
$('#google-login-block').show();
$('#google-logout-block').hide();
google_access_token = '';
}
});
}
// We also want to setup the initial click event and page status on document.ready
$(function() {
$('#google-login-block').click(requestGoogleoAuthCode);
$('#google-logout-block').hide();
$('#google-logout-block').click(logoutFromGoogle);
// now lets show that they're logged in if they are
if (google_access_token) {
getGoogleUserInfo(google_access_token);
}
});
});
</script>
<a id="google-login-block">Login to Google </a>
<span id="googleUserName">You are not logged in </span>
<span id="google-logout-block"><a>Logout from Google</a></span>
<iframe id="googleAuthIFrame" style="visibility:hidden;" width=1 height=1></iframe>
<?php
// END inlined JavaScript and HTML
}
private function write_out_youtube_js_html() {
?>
<script language=javascript>
var google_access_token = "<?php echo $this->get_google_access_token(); ?>";
jQuery(document).ready(function($) {
function getYouTubeVidInfo() {
var video_id = $('#youtubevidid').val();
$.ajax({
url: 'https://www.googleapis.com/youtube/v3/videos',
method: 'GET',
headers: {
Authorization: 'Bearer ' + google_access_token
},
data: {
part: 'snippet',
id: video_id
}
}).done(function(response) {
if (response.items[0].snippet){
var thisdata = response.items[0].snippet;
$('#youtubevideodata').html('<b>' + thisdata.title + '</b><br />' + thisdata.description);
}
});
}
//write out a click event for to trigger the youtube request
$(function() {
$('#youtube-get-vidinfo').click(getYouTubeVidInfo);
if (! google_access_token ) {
$('#youtube-get-vidinfo').hide();
}
});
});
</script>
<input id="youtubevidid" type=text value="5ywjpbThDpE" /><a id="youtube-get-vidinfo">Get Video Info</a>
<div id="youtubevideodata"></div>
<?php
// END inlined JavaScript and HTML
}
/* NEW section to handle doing oAuth server-to-server */
// wrapper for wp_ajax to point to reusable function
public function finish_code_exchange() {
$auth_code = ( isset( $_POST['auth_code'] ) ) ? $_POST['auth_code'] : '';
echo $this->set_google_oauth2_token($auth_code, 'auth_code');
wp_die();
}
private function set_google_oauth2_token($grantCode, $grantType) {
/* based on code written by Jennifer L Kang that I found here
* http://www.jensbits.com/2012/01/09/google-api-offline-access-using-oauth-2-0-refresh-token/
* and modified to integrate with WordPress and to calculate and store the expiration date.
*/
$settings = get_option('foo_oauth_demo_settings', true);
$success = true;
$oauth2token_url = "https://accounts.google.com/o/oauth2/token";
$clienttoken_post = array(
"client_id" => $settings['google_app_client_id'],
"client_secret" => $settings['google_app_client_secret']
);
if ($grantType === "auth_code"){
$clienttoken_post["code"] = $grantCode;
$clienttoken_post["redirect_uri"] = $settings['google_app_redirect_uri'];
$clienttoken_post["grant_type"] = "authorization_code";
}
if ($grantType === "refresh_token"){
$clienttoken_post["refresh_token"] = get_option('foo_google_refresh_token', true);
$clienttoken_post["grant_type"] = "refresh_token";
}
$postargs = array(
'body' => $clienttoken_post
);
$response = wp_remote_post($oauth2token_url, $postargs );
$authObj = json_decode(wp_remote_retrieve_body( $response ), true);
if (isset($authObj['refresh_token'])){
$refreshToken = $authObj['refresh_token'];
$success = update_option('foo_google_refresh_token', $refreshToken, false);
// the final 'false' is so we don't autoload this value into memory on every page load
}
if ($success) {
$success = update_option('foo_google_access_token_expires', strtotime("+" . $authObj['expires_in'] . " seconds"));
}
if ($success) {
$success = update_option('foo_google_access_token', $authObj[access_token], false);
if ($success) {
$success = $authObj[access_token];
}
}
// if there were any errors $success will be false, otherwise it'll be the access token
if (!$success) { $success=false; }
return $success;
}
public function get_google_access_token() {
$expiration_time = get_option('foo_google_access_token_expires', true);
if (! $expiration_time) {
return false;
}
// Give the access token a 5 minute buffer (300 seconds)
$expiration_time = $expiration_time - 300;
if (time() < $expiration_time) {
return get_option('foo_google_access_token', true);
}
// at this point we have an expiration time but it is in the past or will be very soon
return $this->set_google_oauth2_token(null, 'refresh_token');
}
public function revoke_google_tokens() {
/* This function finds either the access token or refresh token
* revokes them with google (revoking the access token does the refresh too)
* then deletes the data from the options table
*/
$return = '';
$token = get_option('foo_google_access_token', true);
$expiration_time = get_option('foo_google_access_token_expires', true);
if (!$token || (time() > $expiration_time)){
$token = get_option('foo_google_refresh_token', true);
}
if ($token) {
$return = wp_remote_retrieve_response_code(wp_remote_get("https://accounts.google.com/o/oauth2/revoke?token=" . $token));
} else {
$return = "no tokens found";
}
if ($return == 200) {
delete_option('foo_google_access_token');
delete_option('foo_google_refresh_token');
delete_option('foo_google_access_token_expires');
return true;
} else {
return $return;
}
}
// wrapper for wp_ajax to point to reusable function
public function logout_from_google() {
$response = $this->revoke_google_tokens();
if ($response === true) {
$response = "success";
}
echo $response;
wp_die();
}
public function submit_youtube_expire_request($videoid){
$access_token = $this->get_google_access_token();
if (! $access_token) {
error_log("no access token for $videoid");
return false;
}
$bodyargs = array(
"id" => $videoid,
"kind" => "youtube#video",
"status" => array(
"privacyStatus" => "private"
)
);
$body = json_encode($bodyargs);
$url = "https://www.googleapis.com/youtube/v3/videos?part=status&fields=status";
$args = array(
"method" => "PUT",
"headers" => array(
"Authorization" => "Bearer " . $access_token,
"Content-Type" => "application/json"
),
"body" => $body
);
$request = wp_remote_request($url, $args);
if (wp_remote_retrieve_response_code($request) != 200){
error_log("privacy set failed : " . wp_remote_retrieve_body($request));
return false;
}
return json_decode(wp_remote_retrieve_body($request));
}
/* The rest of these functions build and process metaboxes for posts */
public function meta_box_setup( $post_type ) {
add_meta_box( 'foo-oAuth-demo-display', __( 'foo oAuth Demo' , 'foo_oauth_demo' ), array( $this, 'meta_box_content' ), $post_type, 'normal', 'high' );
}
public function meta_box_content() {
global $post_id;
add_thickbox();
$fields = get_post_custom( $post_id );
$field_definitions = $this->get_field_definitions();
// Always include a nonce
$html .= '<input type="hidden" name="' . $this->token . '_nonce" id="' . $this->token . '_nonce" value="' . wp_create_nonce( plugin_basename( $this->dir ) ) . '" />';
if ( 0 < count( $field_definitions ) ) {
$html .= '<table class="form-table">' . "\n";
$html .= '<tbody>' . "\n";
foreach ( $field_definitions as $field => $option ) {
$value = $option['default'];
if ( isset( $fields[$field] ) && isset( $fields[$field][0] ) ) {
$value = $fields[$field][0];
}
$html .= $this->format_input_field_as_tablerow($option, $field, $value);
}
$html .= '</tbody>' . "\n";
$html .= '</table>' . "\n";
}
echo $html;
// we no longer do the oAuth login here, but we still can get youtube info
$this->write_out_youtube_js_html();
}
public function meta_box_save() {
global $post;
$post_id=$post->ID;
// Verify nonce
if ( ! wp_verify_nonce( $_POST[ $this->token . '_nonce'], plugin_basename( $this->dir ) ) ) {
return $post_id;
}
// Verify user permissions
if ( ! current_user_can( 'edit_post', $post_id ) ) {
return $post_id;
}
// Handle custom fields
$field_definitions = $this->get_field_definitions();
$fieldlist = array_keys( $field_definitions );
$vidid = null;
$vidstatus = null;
foreach ( $fieldlist as $field ) {
if( isset( $_POST[$field] ) ) {
// only operate on fields that were submitted
$value = $_POST[$field];
// Escape the URLs.
if ( 'url' == $field_definitions[$field]['type'] ) {
$value = esc_url( $value );
}
update_post_meta( $post_id , $field , $value );
if ($field == 'foo_youtube_videoid') {
$vidid = $value;
}
if ($field == 'foo_youtube_status' && $value == 'setprivate') {
$vidstatus = $value;
}
}
}
if ($vidid && $vidstatus) {
$newstatus = $this->submit_youtube_expire_request($vidid);
update_post_meta( $post_id, 'foo_youtube_status', $newstatus->status->privacyStatus );
}
}
public function get_field_definitions() {
$fields = array();
$fields['foo_youtube_videoid'] = array(
'name' => __( 'YouTube Video ID:' , 'foo_oauth_demo' ),
'type' => 'text',
'default' => '',
'description' => 'The ID for a YouTube Vid',
'section' => 'main'
);
$fields['foo_youtube_status'] = array(
'name' => __( 'YouTube Video status:' , 'foo_oauth_demo' ),
'type' => 'text',
'default' => '',
'description' => 'The status for that video',
'section' => 'main'
);
return $fields;
}
private function format_input_field_as_tablerow($option, $field, $value) {
$html = '';
if ($option['maxlength']) { $maxinput = ' data-limit-input="' . $option['maxlength'] . '" '; }
$html .= '<tr valign="top" class="' . $option['section'] . '"><th scope="row"><label for="' . esc_attr( $field ) . '">' . $option['name'] . '</label></th><td><input name="' . esc_attr( $field ) . '" type="text" id="' . esc_attr( $field ) . '" class="regular-text" value="' . esc_attr( $value ) . '"' . $maxinput . ' />' . "\n";
$html .= '<span></span><p class="description">' . $option['description'] . '</p>' . "\n";
$html .= '</td></tr>' . "\n";
return $html;
}
//end of class
}
// Instantiate our class
global $plugin_obj;
$plugin_obj = new foo_oAuth_Demo( __FILE__ );
// always cleanup after yourself
register_deactivation_hook(__FILE__, 'foo_deactivation');
function foo_deactivation() {
// delete the google tokens
$plugin_obj = new foo_oAuth_Demo( __FILE__ );
$plugin_obj->revoke_google_tokens();
error_log('Foo has been deactivated');
}
/* END OF FILE */
?>
@burakco
Copy link

burakco commented Oct 12, 2022

i am getting warning at editing page, colud you help me?:

`Warning: Undefined variable $html in /home5/linkproje/public_html/linkprojev2/wp-content/plugins/foo/foo.php on line 412

Warning: Undefined array key "maxlength" in /home5/linkproje/public_html/linkprojev2/wp-content/plugins/foo/foo.php on line 493

Warning: Undefined variable $maxinput in /home5/linkproje/public_html/linkprojev2/wp-content/plugins/foo/foo.php on line 494

Warning: Undefined array key "maxlength" in /home5/linkproje/public_html/linkprojev2/wp-content/plugins/foo/foo.php on line 493

Warning: Undefined variable $maxinput in /home5/linkproje/public_html/linkprojev2/wp-content/plugins/foo/foo.php on line 494`

@tamw-wnet
Copy link
Author

These are only warnings, and can be ignored. This code is really only for demo purposes as a start for your own work.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment