Wednesday, 19 October 2011

Fix broken links in tt_news text


This relates to tt_news version 3.01, the problem may be present in other versions however.

For some reason the required typoscript processing for the RTE text field is commented out in the static typoscript, you can see it by viewing the template in the template analyser.
This leaves any links and other RTE related functions left unrendered, though the links are the most obvious problem. You'll probably see <link youadderss=""></link> tags in the source of the page which means you defiantly have this problem.

To fix this simply add:
plugin.tt_news.general_stdWrap parseFunc < tt_content.text.20.parseFunc
to a top level template. You may need to clear the cache to see the changes.

Friday, 7 October 2011

Magento External Login and Buy

I needed a way for a user to login to a Magento shop from an external website and have a selected product automatically added to their cart.

First off you'll need to create an API user, there are lots of tutorials out there on how to do this. Make sure this user has access to at least "Customers->Retrieve customer info" and "Category Inventory->Retrieve stock data".

You'll obviously need a form on your external site for the customer to enter their details and select which product they want to buy. The following is just an (untested) example form to give you a rough idea.
Please note: You'll notice I've included an iframe pointing to the shops login page, this is important as it will create the required cookies for the customer to login. If you don't include this iframe any customer who is missing the required cookies won't be logged in and will have nothing added to their cart!

<iframe src="https://shop.address/customer/account/login/" style="position:absolute;height:1px;width:1px;top:-100px;left:-100px;"></iframe>
<form action="your_external_script.php" method="post">
	<div class="login_field">
		<label>Choose your product</label>
		<div class="product_option">
			<label for="product_1">Product #1</label>
			<!-- Value below should be a real product
			in Magento that the user has access to! -->
			<input type="radio" name="login[product_id]" value="1" />
		</div>
		<div class="product_option">
			<label for="product_2">Product #2</label>
			<!-- Value below should be a real product
			in Magento that the user has access to! -->
			<input type="radio" name="login[product_id]" value="2" />
		</div>
		<div class="product_option">
			<label for="product_3">Product #3</label>
			<!-- Value below should be a real product
			in Magento that the user has access to! -->
			<input type="radio" name="login[product_id]" value="3" />
		</div>
	</div>
	<div class="login_field">
		<label for="login_username">Username</label>
		<input id="login_username" type="text" value="" name="login[username]" />
	</div>
	<div class="login_field">
		<label for="login_password">Password</label>
		<input id="login_password" type="password" value="" name="login[password]" />
	</div>
	<div class="login_field">
		<input type="submit" value="Buy selected product" />
	</div>
</form>

Now when the user submits that data you'll have to check three things, that the user exists, the password is correct and that the product is in stock and available.
<?php
/**
 * Validates a Magento password
 *
 * @param	string		$password: The user entered password
 * @param	string		$hash: The password hash from Magento customer info
 * @return	boolean		Returns true if the password is valid
 */
function validatePassword($password, $hash) {
	$hashArr = explode(':', $hash);
	switch(count($hashArr)) {
		case 1:
			return md5($password) === $hash;
		case 2:
			return md5($hashArr[1].$password) === $hashArr[0];
	}
	return false;
}
$apiUrl = 'https://shop.address/api/?wsdl';
$apiUser = 'my_api_user';
$apiKey = 'my_api_key';

if(isset($_POST['login'])) {
	try {
		$client = new SoapClient($apiUrl);
		$session = $client->login($apiUser, $apiKey);
	} catch (Exception $e) {
		echo '<p class="error">Network error, please try again later.</p>';
		exit;
	}
	//Check selected product is in stock...
	try {
		$product = $client->call($session, 'product_stock.list', array(intval($_POST['login']['product_id'])));
	} catch (Exception $e) {
		echo '<p class="error">'.$e->getMessage().'</p>';
		exit;
	}
	if(empty($product)) {
		echo '<p class="error">Product doesn\'t exist.</p>';
		exit;
	} else if($product[0]['is_in_stock'] == 0 || $product[0]['qty'] == 0) {
		//You could possible add the title of
		//the product here, I am lazy though and didn't look up where to get it from
		echo '<p class="error">The product you chose has sold out.</p>';
		exit;
	}
	//Lookup customer record
	list($customer) = $client->call(
		$session,
		'customer.list',
		array(
			array(
				'email' => addslashes($_POST['login']['username'])
			)
		)
	);
	if(is_array($customer)) {
		if(validatePassword($_POST['login']['password'], $customer['password_hash'])) {
			echo '<form action="https://shop.address/login_buy.php" method="post">';
			echo '<input type="hidden" name="login[username]" value="'.$customer['email'].'" />';
			echo '<input type="hidden" name="login[password]" value="'.$customer['password'].'" />';
			echo '<input type="hidden" name="login[product_id]" value="'.intval($_POST['login']['product_id']).'" />';
			echo '<input type="submit" value="Proceed to checkout" />';
			echo '</form>';
			exit;
		} else {
			echo '<p class="error">Invalid login or password</p>';
			exit;
		}
	} else {
		echo '<p class="error">Invalid login or password</p>';
		exit;
	}
}

?>
This then posts data to a custom script you'll have to add to the index of your Magento shop:
<?php
// Load the Magento core
require_once 'app/Mage.php';
umask(0);
Mage::app()->setCurrentStore('default');

Mage::getSingleton("core/session", array("name" => "frontend"));
$session = Mage::getSingleton("customer/session");

//Log out any existing sessions
if(!$session->isLoggedIn()) {
	$session->logout();
}
//Log user in
$login = Mage::getSingleton('core/app')->getRequest()->getPost('login');
$session->login($login['username'], $login['password']);

$productId = $login['product_id'];

header('Location: https://shop.address/checkout/onepage/add?product='.$productId.'&qty=1');
exit;
?>