In a wordpress themed-plugin for e-commerce I have two ajax/php scripts setting cookies (both in the same directory) The ajax calls are set from the same "cart.js" The first script sets/updates an anonymous cart cookie when a cart is either created or updated. The second checks if customer/user exists, or creates one anew and --in either case -- logs them in if not already, before the cart gets passed to PayPal. As such, upon returning from paypal the customer/user (now logged in) is presented with an overview / review of the status of their orders (new and old).
On my WAMP develpment stack, this works flawlessly, while on the hosted (linux) installation
the cart_cookie script works as expected, while the checkout/customer_cookie throws...
[14-May-2013 02:08:50]
PHP Warning: session_start()
[<a href='function.session-start'>function.session-start</a>]:
Cannot send session cache limiter - headers already sent
(output started at /home2/alternam/public_html/demo/wp-content/themes/AM_Wallaby_Kids/checkout.php:2)
in /home2/alternam/public_html/demo/wp-content/plugins/cat-man/catalog-manager.php on line 23
Subsequently, the user is NOT logged in, and the cart is not converted (updated with relevant customer data). Wish I saw a way to pare this down to a minimum, but as I have no earthly idea why the two scripts behave so differently across platforms I'll apologize in advance for the lengthy post and include them both in their entirety below, and ask if anyone can see some obvious reason for the disparity? Thanks for your patience.
P.S. Both WAMP and LAMP stacks running php 5.2
cart_add.php (works both WAMP && LAMP)
<?php
ob_start();
require_once(preg_replace("/wp-content.*/","wp-load.php",__FILE__));
ob_end_clean();
$ud_cart = $product_name = $product_url = $reset = "";
$_POST = filter_input_array(INPUT_POST, FILTER_SANITIZE_STRING);# Sanitize Post Input
foreach ($_POST as $key => $val)
{ if(!is_array($val)) $$key = html_entity_decode($val,ENT_QUOTES);
else $$key = $val;
}
if($ud_cart)
{ $reset =1;
$amt_cart[0] = $cart_id;
if(#$items) foreach($items as $key => $item )$amt_cart[] = $item;
$amt_cart = serialize($amt_cart);
}
if($reset)
{ // Initiated from the cart (tpl_cart.php on page load) to remove out-of-stock items
// from OLD CARTS -- where items have gone out-of-stock since cart created -- OR
// to simply update/remove cart items upon user request (user clicks Update|Remove)
if($amt_cart)
{ $amt_cart = stripslashes($amt_cart);
setcookie(AMART_CART, $amt_cart, time()+60*60*24*90, COOKIEPATH, COOKIE_DOMAIN);
}
exit;
}
// Create Cart, and add, update, or remove Cart-Items from within the catalog gallery && product detail pages
$add = array("product_id" => $product_id, "product_name" => $product_name, "product_type" => $product_type,"product_url" => $product_url,"qty" => $qty);
$update = "";
if( isset( $_COOKIE[AMART_CART] ) )
{ $amt_cart = stripslashes($_COOKIE[AMART_CART]);
$amt_cart = unserialize($amt_cart);
foreach($amt_cart as $key => $item)
{ if($key == 0 ) $amt_cart_id = $item;
else
{ foreach($item as $attr => $value)
{ if($product_id != $value) continue;
else
{ $update = 1;
if($qty == 0 )
{ unset($amt_cart[$key]);
break;
} else $amt_cart[$key]['qty'] = $qty;
}
}
}
}
if(!$update) $amt_cart[] = $add;
setcookie(AMART_CART, serialize($amt_cart), time()+60*60*24*90, COOKIEPATH, COOKIE_DOMAIN);
}
else
{ unset($_SESSION[STORE_ID]['dest_zip'], $_SESSION[STORE_ID]['dest_ctry']);
$amt_cart[0] = uniqid(AMART_CART);
$amt_cart[] = $add;
setcookie(AMART_CART, serialize($amt_cart), time()+60*60*24*90, COOKIEPATH, COOKIE_DOMAIN);
}
?>
checkout.php (works on WAMP, fails on LAMP)
<?php
// TPL CART POSTS VIA AJAX CALL IN cart.js
ob_start();
require_once(preg_replace("/wp-content.*/","wp-load.php",__FILE__));
ob_end_clean();
$_POST = filter_input_array(INPUT_POST, FILTER_SANITIZE_STRING);
global $current_user, $wpfx;
$buyer_address1 = $buyer_address2 = $buyer_city = $buyer_region = $buyer_postal_code = $buyer_country = $buyer_ctry_code ="";
foreach ($_POST as $key => $val) $$key = $val;
foreach($buyer as $key => $val) $$key = $val;
$user_is_admin = current_user_can('manage_options');
if(!is_user_logged_in() || $user_is_admin )
{ if($userID = email_exists($email))
{ $user_info = $user_info = get_userdata($userID);
$user_login = $user_info->user_login;
$display_name = $user_info->display_name;
$welcome = "Welcome Back $display_name!";
}
if(#$welcome)
{ if(!$user_is_admin )
{ if(!$user_cnfm) die($welcome);
$auth = get_object_vars(wp_authenticate($user_login, $user_pass));
if(array_key_exists('errors',$auth)) die("Password Error");
wp_set_auth_cookie( $userID, true);
wp_set_current_user($userID, $user_login);
}
update_user_meta( $userID, 'customer', 1);
}
else
{ $buyer_name = "$buyer_first $buyer_last";
$ship_to_name = "$first_name $last_name";
if($ship_to_self)
{ foreach ( $ctry_opts as $key=>$value ) if (strcasecmp($country, $value) == 0) $buyer_country = $key;
$buyer_address1 = $address1;
$buyer_address2 = $address2;
$buyer_city = $city;
$buyer_region = $state;
$buyer_postal_code = $zip;
$buyer_ctry_code =strtolower($country);
} else foreach ( $ctry_opts as $key=>$value ) if ($buyer_ctry_code == $value) $buyer_country = $key;
$userdata = $user_cookie = array(
'user_login' => $email,
'user_email'=> $email,
'user_pass'=>$user_pass,
'first_name'=>$buyer_first,
'last_name'=>$buyer_last,
'display_name' =>$buyer_name,
'address1' => $buyer_address1,//null if not ship to self
'address2' => $buyer_address2,//null if not ship to self
'city' => $buyer_city,//google guess if not ship to self
'region' => $buyer_region,//google guess if not ship to self
'postal_code' => $buyer_postal_code,//null if not ship to self
'country' => $buyer_country,//google guess if not ship to self
'ctry_code' => $buyer_ctry_code,//google guess if not ship to self
'customer' => '1'
);
$userID = wp_insert_user( $userdata );
if(!$user_is_admin)
{ wp_set_auth_cookie( $userID, true);
wp_set_current_user($userID, $email);
}
unset($user_cookie['user_login'],$user_cookie['user_pass'],$user_cookie['display_name']);
setcookie('AMART_CUSTOMER', serialize($user_cookie), time()+60*60*24*180, COOKIEPATH, COOKIE_DOMAIN);
}
}
if(is_user_logged_in())
{ if(!$user_is_admin) $userID = $current_user->ID;
$cart_id = $item_name;
$cart = $wpdb->get_row("SELECT * FROM {$wpfx}amt_carts WHERE cart_id = '$cart_id'", ARRAY_A);
if( $cart['host_checkout'] && isset($store_options->paypal_live) && $store_options->paypal_live !=='false')
$host_checkout = true;
$ship_to = serialize( array('first_name' => $first_name,'last_name' => $last_name,'address1' => $address1,'address2' => $address2,'city' => $city, 'state' => $state,'postal_code' => $zip,'country' =>$country));
$attributes = array('ship_to' => $ship_to, 'customer_id'=>$userID, 'checkout_date'=>$now);
$where = array('cart_id' => $cart_id);
$wpdb->update("{$wpfx}amt_carts", $attributes,$where);
}
?>
This is, I believe, due to a difference in PHP configuration. I cannot tell you more without knowing the full list of POST variables, besides broad guidelines.
This is what I assume is causing the issue:
You're assigning $_POST[key] to $key. This is fine
You're then looping through $buyers. If $_POST["buyers"] was not set or not an array, this will throw a notice.
A notice is an echo. You are not in an output buffering context, which therefore causes the headers to also be sent. If the headers are sent and you later send a cookie or other header info, you get the warning you are getting.
One of your dev environments probably has error_reporting set to E_NONE. Check this in the PHP information. If set to E_NONE, your code will work. If not, you will get that message. Consider always checking for the existence and correct type of your functions. A good way to do so concisely is as follows:
foreach (((array)$buyers) as $v) {
if $buyers is undefined, you will get an empty array. If buyers had one element you will have an array with one element. Otherwise, you will get the array you had.
Okay...
As much as Sébastien's answer made good sense (and was even correct in some regard), and grateful as I am to him for pointing out that Notice level errors are essentially an echo causing headers to be sent, and for demonstrating a new way (to me) of ensuring an array exists in a foreach loop... the problem actually appears to stem from the differences between Linux and Windows newline characters, which I discovered when opening the hosted "cart_add.php" (from FileZilla ftp ) for editing in Notepad++. I was surprised to find extra newlines for every line of code. (Not the way the file was written). On a whim, I highlighted the first doubled newline, and replaced all with \n. Uploading the file (which had worked before on either stack) cart_add.php subsequently returned an error indicating something like no such function - phpob_start.
Aha!
On my windows box, coding in Notepad++ and comparing files in Beyond Compare, the hosted (Linux) files appear to be exact copies of the local source files, however file sizes tell a different story (local / windows files invariably larger) which I suspect is owed to the difference in newline characters (\n\r) in windows, and (\n) on linux.
Now, the problem I face is
Gaining an understanding of why replacing (in Notepad++) all doubled newlines with \n (Linux standard) cased the script to break.
Establishing a means of safeguarding against this issue in the future
but perhaps that is a matter for a separate post?
Related
I am currently working on an automated site creation function on my local installation of wordpress. In essence, a form is filled out on an already existing site and that info is pulled to create a new site automatically via an endpoint that activates some queries. So far I have been able to successfully pull the information into an array and then pass that to the wpmu_create_blog function. The issue is that the $domain isn't being called correctly(that is to say, how I intend it to be called), the '/' is lost between 'localhost' and 'wordpress'.
public function create_endpoint($request) {
$key = $request['key'];
if ($this->validate_key($key)) {
$newsite = array (
$title = $request['name'],
$path = $request['slug'],
$admin_user = $request['admin_user'],
);
$domain = 'localhost/wordpress';
$site_id = get_blog_id_from_url($domain, $path);
$user_id = get_user_by('login', $admin_user);
if ( !empty($title) and !empty($domain) and !empty($path) and empty($user_id) ) {
return wpmu_create_blog($domain, $path, $title, $user_id, $site_id);
}
else {
return "Not enough information";
}
}
else {
return $this->invalid_key_message;
}
}
Everything but the domain being called as intended is working as intended. This is also just the static prototype, my end goal is that this is entirely dynamic including the $domain variable.
I'm just totally lost on where to go from here. I've tried some appendage stuff and moving syntax around in all types of ways but keep hitting a wall. Any input or suggestions are happily accepted.
I have a solution implemented. In the array $path has been changed to $slug (use of slug is specific to my code). The $domain now only calls 'localhost' and I create $path using plain text the 'wordpress' and then add the slug via a $request.
$newsite = array (
$title = $request['name'],
$slug = $request['slug'],
$admin_user = $request['admin_user'],
);
$domain = 'localhost';
$path = 'wordpress/'.$request['slug'];
I am working with solr 6.6.0 using solr PHP client. I am adding the docs using below code and it is working properly :
foreach ($data as $key => $value) {
$docs['doc_no'.$i]['id'] = $value['id'];
$docs['doc_no'.$i]['name'] = $value['name'];
$docs['doc_no'.$i]['sub_title'] = strip_tags($value['sub_title']);
$docs['doc_no'.$i]['small_image'] = $value['small_image'];
$docs['doc_no'.$i]['project_type'] = $value['project_type'];
$docs['doc_no'.$i]['project_status'] = $value['project_status'];
$docs['doc_no'.$i]['logo'] = $value['logo'];
$docs['doc_no'.$i]['price'] = $value['price'];
$docs['doc_no'.$i]['url'] = $value['url'];
$docs['doc_no'.$i]['flat_type_desc'] = $value['flat_type_desc'];
$docs['doc_no'.$i]['project_config'] = $value['project_config'];
$docs['doc_no'.$i]['address'] = $value['address'];
$docs['doc_no'.$i]['location'] = $value['location'];
$i++;
}
//print_r($docs);exit;
$documents = array();
foreach($docs as $item => $fields) {
$part = new Apache_Solr_Document();
foreach ( $fields as $key => $value ) {
if ( is_array( $value ) ) {
foreach ( $value as $data ) {
$part->setMultiValue( $key, $data );
}
}
else{
$part->$key = $value;
}
}
$documents[] = $part;
}
try {
$solr->addDocuments( $documents );
$solr->commit();
$solr->optimize();
}
catch ( Exception $e ) {
echo $e->getMessage();
}
After executing the above code I have to manually restart the solr through cmd line and then it gets reflected, I want to ask that is every time when I add any docs in solr then I have to restart the solr manually ? Is there any other way to restart the solr automatically as soon as I have the data in docs.
Any help will be appreciated Thanks in advance.
For the submitted documents to be visible in the index, you have to issue a commit - and ask for a new reader to be opened (this is usually handled for you, so that's not usually necessary. How you do exactly that in the Drupal framework I have no idea about, but I'm guessing your Solr client has a commit method or something similar. I tried searching for the API docs, but came up empty except for the _Document class.
After a commit has been issued the index changes will be visible within a few seconds, or in the case of a soft commit (where the changes aren't persisted to disk before later) almost instantly.
You can also ask for a commitWithin interval when submitting documents, but that would also depend on how the client you're using works for how you include that parameter.
I'm currently developing a CiviCRM extension, where I need to replace CiviCRM-Tokens (used in pdf and mailing generation) in html code.
I did a little bit of research in the core files and tried to recreate the behaviour in the PDFLetterCommon.php (/civicrm/CRM/Contact/Form/Task/PDFLetterCommon.php) where it replaces the tokens in the postProcess function.
Here is the original CiviCRM Code:
list($formValues, $categories, $html_message, $messageToken, $returnProperties) = self::processMessageTemplate($form);
$skipOnHold = isset($form->skipOnHold) ? $form->skipOnHold : FALSE;
$skipDeceased = isset($form->skipDeceased) ? $form->skipDeceased : TRUE;
foreach ($form->_contactIds as $item => $contactId) {
$params = array('contact_id' => $contactId);
list($contact) = CRM_Utils_Token::getTokenDetails($params,
$returnProperties,
$skipOnHold,
$skipDeceased,
NULL,
$messageToken,
'CRM_Contact_Form_Task_PDFLetterCommon'
);
...
}
And here is my version for testing:
(this code is located inside an api function in my extension)
$messageToken = CRM_Utils_Token::getTokens($params["html"]);
$returnProperties = array();
if (isset($messageToken['contact'])) {
foreach ($messageToken['contact'] as $key => $value) {
$returnProperties[$value] = 1;
}
}
$skipOnHold = FALSE;
$skipDeceased = TRUE;
$tokenParams = array("contact_id" => 67450);
list($contact) = CRM_Utils_Token::getTokenDetails($tokenParams,
$returnProperties,
$skipOnHold,
$skipDeceased,
NULL,
$messageToken,
'CRM_Contact_Form_Task_PDFLetterCommon'
);
I'm using the default values for $skipOnHold (false) and $skipDeceased (true) and also just passing one (existing) user id into the $params array ($tokenParams in my code).
Here is my problem:
My $messageToken and $returnProperties variables are being filled correctly via CiviCRM's core functions but when I pass them all into CRM_Utils_Token::getTokenDetails() the returned $contact variable holds an empty array.
I'm really out of ideas, I've been looking into CRM/Utils/Token.php where getTokenDetails() is located, but have been unsuccessful in finding the problem with my code.
Thanks in advance for any help!
I'm trying to integrate an API builder to my control panel through a form or post data. I can't figure out how to put the post data as the value for the array.
I tried using print_r($_POST['VALUE']) with and without quotes.
I tried using just $_POST['VALUE'] with and without quotes.
I also tried to set $value = $_POST['VALUE'] then using $value with and without quotes but that caused an error 500.
Here is the code I am trying to use:
$res = $api->remoteCall('requestLogin', array(
'type' => 'external',
'domain' => 'print_r($_POST['domain'])',
'lang' => 'en',
'username' => 'print_r($_POST['uname'])',
'password' => 'print_r($_POST['pass'])',
'apiUrl' => '127.0.0.1',
'uploadDir' => '/web/'.print_r($_POST['domain']).'/public_html',
I apologize as I am new to PHP, but thank you in advance.
I'm not sure what other logic is being done there, how the post variables are being sent to the script your sample code is running on, or any of the other details which might point towards a more complete solution but here are some basic tips to help you troubleshoot.
The post variables should be formatted like this:
$res = $api->remoteCall('requestLogin', array(
'domain' => $_POST['domain'],
You can dump the entire post array to the screen by doing
print_r($_POST);
This should output your array to the screen so you can verify that you're receiving the post data in the code and should help you fix any typos or misnamed post variables. If the array has the key as $_POST['domainName'] and you're echoing $_POST['domain']
You're calling code (the "form or post data") should have the post fields in place and named correctly in order for them to be sent to the script
<input type="text" name="domain">
You should be performing some basic validation on your post fields before adding them to something that's going to be stored anywhere or sent off to a third-party. At the most minimal you'll want to check that there is a value being set for the essential fields (required fields) and I'd look to make sure the values are matching requirements of the API you're passing them off to.
Several things may go wrong when using api. POST values, input values, API call or connection or maybe api response. So not only at the time of implementation and coding but also when integrating api call script with the application there should be some sort of testing and error handling in place. A simple script can be like this
$error = array();
$request = array();
$request['type'] = 'external';
if (isset($_POST['domain']) && !empty($_POST['domain'])) {
$request['domain'] = $_POST['domain'];
$request['uploadDir'] = "/web/{$_POST['domain']}/public_html";
} else {
$error[] = "Domain is empty";
}
if (isset($_POST['uname']) && !empty($_POST['uname'])) {
$request['username'] = $_POST['uname'];
} else {
$error[] = "Username is empty";
}
if (isset($_POST['pass']) && !empty($_POST['pass'])) {
$request['password'] = $_POST['pass'];
} else {
$error[] = "Username is empty";
}
$request['lang'] = 'en';
$request['apiUrl'] = '127.0.0.1';
if (count($error) > 0) {
echo implode( "<br>" , $error );
} else {
try{
$res = $api->remoteCall('requestLogin',$request);
} catch ( Exception $e ) {
print_r($e);
exit();
}
}
I have this code:
<?php
foreach($items as $item) {
$site = $item['link'];
$id = $item['id'];
$newdata = $item['data_a'];
$newdata2 = $item['data_b'];
$ch = curl_init($site.'updateme.php?id='.$id.'&data1='.$newdata.'&data2='.$newdata2);
curl_exec ($ch);
// do some checking here
curl_close ($ch);
}
?>
Sample input:
$site = 'http://www.mysite.com/folder1/folder2/';
$id = 512522;
$newdata = 'Short string here';
$newdata = 'Another short string here with numbers';
Here the main process of updateme.php
if (!$id = intval(Tools::getValue('id')))
$this->_errors[] = Tools::displayError('Invalid ID!');
else
{
$history = new History();
$history->id = $id;
$history->changeState($newdata1, intval($id));
$history->id_employee = intval($employee->id_employee);
$carrier = new Carrier(intval($info->id_carrier), intval($info->id_lang));
$templateVars = array('{delivery}' => ($history->id_data_state == _READY_TO_SEND AND $info->shipping_number) ? str_replace('#', $info->shipping_number, $carrier->url) : '');
if (!$history->addWithemail(true, $templateVars))
$this->_errors[] = Tools::displayError('an error occurred while changing status or was unable to send e-mail to the employee');
}
The site will always be changing and each $items will have atleast 20 data inside it so the foreach loop will run atleast 20 times or more depending on the number of data.
The target site will update it's database with the passed variables, it will probably pass thru atleast 5 functions before it is saved to the DB so it could probably take some time too.
My question is will there be a problem with this approach? Will the script encounter a timeout error while going thru the curl process? How about if the $items data is around 50 or in the hundreds now?
Or is there a better way to do this?
UPDATES:
* Added updateme.php main process code. Additional info: updateme.php will also send an email depending on the variables passed.
Right now all of the other site are hosted in the same server.
You can have a php execution time problem.
For your curl timeout problem, you can "fix" it using the option CURLOPT_TIMEOUT.
Since the cURL script that calls updateme.php doesn't expect a response, you should make updateme.php return early.
http://gr.php.net/register_shutdown_function
function shutdown() {
if (!$id = intval(Tools::getValue('id')))
$this->_errors[] = Tools::displayError('Invalid ID!');
else
{
$history = new History();
$history->id = $id;
$history->changeState($newdata1, intval($id));
$history->id_employee = intval($employee->id_employee);
$carrier = new Carrier(intval($info->id_carrier), intval($info->id_lang));
$templateVars = array('{delivery}' => ($history->id_data_state == _READY_TO_SEND AND $info->shipping_number) ? str_replace('#', $info->shipping_number, $carrier->url) : '');
if (!$history->addWithemail(true, $templateVars))
$this->_errors[] = Tools::displayError('an error occurred while changing status or was unable to send e-mail to the employee');
}
}
register_shutdown_function('shutdown');
exit();
You can use set_time_limit(0) (0 means no time limit) to change the timeout of the PHP script execution. CURLOPT_TIMEOUT is the cURL option for setting the timeout, but I think it's unlimited by default, so you don't need to set this option on your handle.