I'm working on a front-end app which ties into wordpress API which sits on a seperate domain.
I'm wanting to retrieve a post draft so when the user clicks "Preview Post" in the wordpress admin panel, it opens my app with the correct content.
I'm loading some Javascript into the WP Admin so I can amend all "Preview Post" links in the admin panel with a wp_nonce token for authentication purposes. This is done using the below snippet, which tweaks the preview post link into for example: http://example.com/blog?p=127&preview=true&auth=16045802ee
function admin_inline_js(){
// Grab URL
echo '
<script>
document.addEventListener("DOMContentLoaded", function() {
// Grab all preview anchors
var anchors = document.querySelectorAll("a[href*=\'preview=true\']"), i;
// Loop through and amend to remove the trailing slash, as WP doesnt provide any easy method to achieve this.
for(i = 0; i < anchors.length; i++) {
anchors[i].href = anchors[i].href.replace("blog/", "blog") + \'&auth=' . wp_create_nonce('wp_rest') .'\';
}
});
</script>
';
}
add_action( 'admin_print_scripts', 'admin_inline_js' );
At http://example.com/blog?p=127&preview=true&auth=16045802ee, the auth parameter is then used to post a request back to wordpress to retrieve the draft with an ID of 127, with a nonce token of 16045802ee. However this isn't working, and I'm getting this response:
object(stdClass)#459 (3) { ["code"]=> string(25) "rest_cookie_invalid_nonce" ["message"]=> string(23) "Cookie nonce is invalid" ["data"]=> object(stdClass)#532 (1) { ["status"]=> int(403) } }
Can anybody spot what I'm doing wrong here? :/
Thanks.
There are two issues you are facing:
A Wordpress "nonce" is a tool that is only intended to protect against CSRF tokens. Really useful when writing plugins for the admin panel, but not useful for authenticating against the API.
Passing authentication details over the API is blocked by the default-prescribed .htaccess file.
You need to pass authentication details over the API in order to receive the responses containing the protected data. For simplicity, I recommend using HTTP Basic Auth to get it working first, optionally enhancing your code with more secure authentication methods such as OAuth in the future.
I recommend creating a new user specifically for API usage, rather than passing admin details around in text files on your server.
Once you have your user created, you can request the draft post using simple cURL in PHP as follows:
$apiUsername = "apiuser";
$apiPassword = "sup3r-s3cr3t-p455w0rd";
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, "http://example.com/wp-json/wp/v2/posts/127");
curl_setopt($ch, CURLOPT_USERPWD, "$apiUsername:$apiPassword");
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
$json = curl_exec($ch);
$postObject = json_decode($json);
This will set request header containing the username and password, and as long as nothing interferes with this header before it gets to Wordpress, you will receive your post details in $postObject and you can stop reading here. You're done.
However, for a bit of extra fun, the Wordpress-prescribed default .htaccess file drops HTTP headers, so if you're using that you'll need to fix it yourself. Basically, the Wordpress scripts need to see PHP_AUTH_USER and PHP_AUTH_PW keys within the $_SERVER variable for this to work.
A quick fix is to include the following rule as the first RewriteRule in .htaccess:
RewriteRule .* - [E=REMOTE_USER:%{HTTP:Authorization}]
which will split the headers into variables prefixed with "REMOTE_USER", and the following PHP can be used to populate the expected fields back again (perhaps this can go in index.php).
$userPass = $_SERVER["REDIRECT_REMOTE_USER"];
if(!empty($userPass)
&& strstr($userPass, "Basic")) {
$userPass = substr($userPass, strpos($userPass, " ") + 1);
$userPass = base64_decode($userPass);
$userPass = explode(":", $userPass);
$_SERVER['PHP_AUTH_USER'] = $userPass[0];
$_SERVER['PHP_AUTH_PW'] = $userPass[1];
}
I debbuged the code and think that the cookie is corrupted.
In the code snippet below, we can see.
// Check the nonce.
$result = wp_verify_nonce( $nonce, 'wp_rest' );
if ( ! $result ) {
return new WP_Error( 'rest_cookie_invalid_nonce', __( 'Cookie nonce is invalid' ), array( 'status' => 403 ) );
}
For me, this issue has solved when I clear browser cookies for my domain.
I found this Error [{"code":"json_cookie_invalid_nonce","message":"Cookie nonce is invalid"}]
after I install WP POS , When the POS page opening time ERROR "forbidden"
after expand the error
I get this message [{"code":"json_cookie_invalid_nonce","message":"Cookie nonce is invalid"}]
After little while I found solution. I deactivate newly installed plugins one by one , the i found problem in "Yoast SEO" plugin after deactivation it is POS plugins working fine , then again I activate Yoast SEO plugin no problem it is working fine
You need to add the permission "promote_users" to the POS role. I used "User Role Editor" plugin to do this but you can also add the following to your functions.php:
$pos_role = get_role( 'pos' ); // Or whatever role you want to add it to
$pos_role->add_cap( 'promote_users' );
Something that happened to me and can be useful: assuming that you are properly passing the auth details to API, this error can be triggered if you create the nonce with a not logged user, login in, and checking it after, and that's explained because the nonce is created using user ID.
From file: wp-includes/pluggable.php
function wp_create_nonce( $action = -1 ) {
$user = wp_get_current_user();
$uid = (int) $user->ID;
if ( ! $uid ) {
/** This filter is documented in wp-includes/pluggable.php */
$uid = apply_filters( 'nonce_user_logged_out', $uid, $action );
}
$token = wp_get_session_token();
$i = wp_nonce_tick();
return substr( wp_hash( $i . '|' . $action . '|' . $uid . '|' . $token, 'nonce' ), -12, 10 );
}
Related
In Wordpress function.php, I have following code to get the short URL of the current post:
add_action('wpcf7_before_send_mail', 'save_cf7_data');
function save_cf7_data($cf)
{
$post_url = 'https://' . $_SERVER[ 'SERVER_NAME' ] . $_SERVER[ 'REQUEST_URI' ];
$post_id = url_to_postid( $post_url );
$post_shorturl = wp_get_shortlink($post_id);
}
It used to work perfectly without any problem for months. However, there must be some changes in contact form 7 plugin or Wordpress, $post_url starts to return url like this:
https://example.com/wp-json/contact-form-7/v1/contact-forms/108/feedback
How to retrieve the short URL of the current post correctly in this case?
Try this code:
function save_cf7_data($cf) {
$submission = WPCF7_Submission::get_instance();
$cf_url = $submission->get_meta( 'url' );
$cf_postid = url_to_postid( $cf_url );
$cf_shorturl = wp_get_shortlink($cf_postid);
}
add_action('wpcf7_before_send_mail', 'save_cf7_data');
See the file mail.php in source code of CF7.
Have a look at the Special Mail Tags, specifically [_post_url] as well.
Then try like this,
add_action('wpcf7_before_send_mail', 'save_cf7_data');
function save_cf7_data($cf)
{
global $wp;
$post_url = home_url(add_query_arg(array(),$wp->request));
$post_id = url_to_postid( $post_url );
$post_shorturl = wp_get_shortlink($post_id);
}
I would use get_permalink() for retrieving post url in a page template. functions.php may load before global $post is set, not too sure.
EDIT:
Oh if you look at the $post_url you got, it's actually a WordPress Rest API url. So I think the form plugin used to submit to the original page but now chooses to use WordPress Rest API.
But your solution is not the correct approach in the first place. Since this is a form submission, you should do a dump of the $_REQUEST. I bet there's form's post url info in there. If not submitted by default, you can probably add a post url (most likely hidden) field in the plugin form.
I am building a bilingual website using WordPress and WPML(the translation plugin WPML), also to handle login and registration I used Theme my login, however since it's not compatible with WPML, I had to find an alternative (tried clean-login ) but still no luck.
The problem I faced with theme my login; it's not supporting templates for both languages only the English part (the other language is Arabic), while using clean-login you can't modify it's templates (while I needed to add some fields to user registration).
Finally I followed the tutorial in this link to build my own login and registration plugin, the problem I'm facing now is that each time a login is made, I'm being redirected to the user profile (which is the correct behavior) but instead of keeping the same language from the login page, it redirects to the user profile with the default language although I was trying to login from the Arabic login page.
I tried several fixes for the language redirection problem; like trying to modify the locale using wp locale filter through a custom function, or by using a conditional on the $_GET['lang'], unfortunately nothing worked.
An example for the various solutions I tried using the $_GET['lang'] parameter;
setting a public variable ($site_lang) in the custom plugin class:
public $site_lang;
public function __construct() {
global $sitepress;
$current_lang = $sitepress->get_current_language();
if ( isset($_GET['lang']) && $_GET['lang'] == 'ar'){
$this->site_lang = 'ar';
}else{
$this->site_lang = 'en_US';
}
}
Another variation using session through the plugin WP_SESSION
global $sitepress;
$current_lang = $sitepress->get_current_language();
$wp_session = WP_Session::get_instance();
$wp_session['act_lang'] = 'ar';
if (!isset($_GET['lang'])){
$wp_session['act_lang'] = 'en';
}else{
$wp_session['act_lang'] = 'ar';
}
$this->site_lang = $wp_session['act_lang'];
and the redirect on registration error function, which is one of many functions that deals with redirect, I'm putting it here as an example on how I'm using the public variable for redirection
public function maybe_redirect_at_authenticate( $user, $username, $password ) {
if( $_SERVER['REQUEST_METHOD'] === 'POST' ) {
$login_url = ($this->site_lang == 'ar') ? site_url( 'تسجيل-الدخول/?lang=ar' )
: home_url( 'member-login' );
$login_url = add_query_arg( array('login' => $error_codes), $login_url );
wp_redirect( $login_url );
}
}
If anyone has a solution on how to keep the language the same after redirection, I really do appreciate the help.
I've lost a day and a half now trying to figure out why Yii is deleting all of the session data after I go to Twitter's OAuth page and back to my redirect.
Here is the main SiteController, where I go to Twitter. Here I am trying to save the oauth_token and token_secret values, so I can use them on the redirect controller.
function actionTwitter()
{
$consumerKey = "";
$consumerSecret = "";
$connection = new TwitterOAuth($consumerKey, $consumerSecret);
$request_token = $connection->oauth("oauth/request_token", array("oauth_callback" => "http://127.0.0.1/yii/?r=redirect&type=twitter"));
$oauth_token=$request_token['oauth_token'];
$token_secret=$request_token['oauth_token_secret'];
Yii::app()->session['token'] = $oauth_token; // This doesn't save!!
Yii::app()->session['token_secret'] = $token_secret; // This does not save!!
$url = $connection->url("oauth/authorize", array("oauth_token" => $oauth_token));
$this->redirect($url);
exit(); // some people have said I need to exit the session first after I redirect, but it doesn't help at all.
}
Here is my RedirectController, which is a separate controller and not in the main SiteController:
public function actionIndex()
{
$type = $_GET['type'];
if ($type == "twitter")
{
$token = Yii::app()->session['token'];
print($token);
}
}
I also have the session autostart set to true in my config file.
Thoughts on why it isn't working / stuff I have read about:
Twitter's site is HTTPS, and I am on localhost (which isn't HTTPS). For some reason that I forget this will make the session lose data when I redirect. If this is the case, how do I fix it without using HTTPS?
When I create new CHttpCookies they do not save either, I can't retrieve the value
I have tried Yii::app()->user->setState instead, which isn't working either.
I found the solution. It did not work because I was using 127.0.0.1 for the redirect, instead of the standard localhost. I changed that and all is working now.
I made one of my menu items only for Guests (not logged in users). ANd If user logs in, it disappears from menu. It works fine...
But I noticed that when I put the link to the Guest menu item in browser, when I'm lnot ogged in , it shows very disgusting jos-Error: You are not authorised to view this resource.
I want to find a way to track such errors and redirect to custom 404 page or some other page.
Please, assist me in this issue.
Thanks
The string You are not authorised to view this resource located in JERROR_ALERTNOAUTHOR language string and fire in includes/application.php function:
public function authorise($itemid)
{
$menus = $this->getMenu();
$user = JFactory::getUser();
if (!$menus->authorise($itemid))
{
if ($user->get('id') == 0)
{
// Redirect to login
$uri = JFactory::getURI();
$return = (string)$uri;
$this->setUserState('users.login.form.data', array( 'return' => $return ) );
$url = 'index.php?option=com_users&view=login';
$url = JRoute::_($url, false);
$this->redirect($url, JText::_('JGLOBAL_YOU_MUST_LOGIN_FIRST'));
}
else {
JError::raiseError(403, JText::_('JERROR_ALERTNOAUTHOR'));
}
}
}
You could alter this function and add redirection or change the text string with your desired message.
Redirect could be done with redirect() method.
An example of redirect syntax:
$app = JFactory::getApplication();
$app->redirect(JRoute::_(JURI::root().'index.php'));
Please make sure that you won't alter application.php without creating an override system plugin.
Hope this helps
I am new to PHP and even newer to SESSIONS
I am working with the Instagram API and I am successfully able to authorize an app, and redirect to a page to display content.
My main folder is called Monkey and it has a sub folder called Instagram.
MY callback url for instagram is success.php located in the instagram folder. When I successfully retrieve an access token from Instagram it redirects to the index file in the Monkey folder.
On my success page, I am creating an array full of data called instaArray. I am trying to pass the array from the success.php in the instagram folder, to the index.php in the monkey folder.
My redirect is simply
header( 'Location: ../index.php' );
Because I am new with sessions, I guess I am doing something wrong. I figured it was straight forward, but I suppose not ha.
On the success.php page, after I build the array I have this
session_start();
$_SESSION['instagram'] = $instaArray;
I thought that should create a session that holds my array InstaArray.
Then, on the index.php page in Monkey, I have this
<?php
session_start();
$get_instagram = $_SESSION['instagram'];
print_r($get_instagram);
?>
But absolutely nothing happens. I've even tried to set the session instagram to a simple numerical value or 1, $_SESSION['instagram'] = 1; and get that on the index page, and it doesn't work either.
Am I doing something horribly, terribly wrong? I've read up on sessions, but because it's new, it's still a little confusing.
Thanks for the help, and I hope I was able to explain everything properly.
EDIT: Here is my success.php page in full
<?php
require 'src/db.php';
require 'src/instagram.class.php';
require 'src/instagram.config.php';
// Receive OAuth code parameter
$code = $_GET['code'];
// Check whether the user has granted access
if (true === isset($code)) {
// Receive OAuth token object
$data = $instagram->getOAuthToken($code);
// Take a look at the API response
$username = $data->user->username;
$fullname = $data->user->full_name;
$id = $data->user->id;
$token = $data->access_token;
$user_id = mysql_query("select instagram_id from users where instagram_id='$id'");
if(mysql_num_rows($user_id) == 0) {
mysql_query("insert into users(instagram_username,instagram_name,instagram_id,instagram_access_token) values('$username','$fullname','$id','$token')");
}
//Set Cookie
$Month = 2592000 + time();
setcookie(instagram, $id, $Month);
// Set user access token
$instagram->setAccessToken($token);
// Retrive Data
$instaData = $instagram->getUserFeed();
// Create Instagram Array
$instaArray = array();
$count = 0;
// For each Instagram Post
foreach ($instaData->data as $post) {
$instaArray[$count]['post_id'] = $post->id;
$instaArray[$count]['name'] = $post->user->username;
$instaArray[$count]['profile_img'] = $post->user->profile-picture;
$instaArray[$count]['img_url'] = $post->images->standard_resolution->url;
$instaArray[$count]['caption'] = $post->caption->text;
$instaArray[$count]['like_count'] = $post->likes->count;
$instaArray[$count]['comment_count'] = $post->comments->count;
$instaArray[$count]['created_time'] = $post->created_time; //Unix Format
$count++;
}
// Start Session For Array
session_start();
$_SESSION['instagram'] = serialize($instaArray);
header( 'Location: ../index.php' ) ;
} else {
// Check whether an error occurred
if (true === isset($_GET['error'])) {
echo 'An error occurred: '.$_GET['error_description'];
}
}
?>
Why not use an ID and then cookies rather than sessions + data (which are usually store on the server in text files in a temporary directory)? And keep all data within a database than allow the client to be accessible to the data. Sessions are also temporary.
Note, do you know if you have "globals" on?!
"Please note when working with sessions that a record of a session is not created until a variable has been registered using the session_register() function or by adding a new key to the $_SESSION superglobal array. This holds true regardless of if a session has been started using the session_start() function."
Reference:
http://www.php.net/manual/en/function.session-register.php
make session_start() first line after php
<?php
session_start();
and remove it from anywhere ele on page.
session_start() should be your first line in index.php also as in success.php
Note: The session_start() function must appear BEFORE the tag:
REF : http://www.w3schools.com/php/php_sessions.asp
I think you need to unserialize() your array in index.php.
$get_instagram = unserialize($_SESSION['instagram']);