Categorieën
Magento PHP

Magento factuurdatum toevoegen aan PDF

Bij Pronamic krijgen we regelmatig de vraag om de factuurdatum toe te voegen aan de Magento factuur PDF. Volgens de belastingdienst moet de factuurdatum immers verplicht op de factuur staan. Op internet zijn gelukkig genoeg pagina’s te vinden waarop wordt uitgelegd hoe je dit voor elkaar kunt krijgen.

Als je de factuurdatum achter het factuurnummer wilt plaatsen in de PDF kun je daarvoor de volgende code gebruiken.

$page->drawText(Mage::helper('sales')->__('Invoice Date') . ': ' . Mage::helper('core')->formatDate($invoice->getCreatedAt(), 'full', false), 285, 780, 'UTF-8');

Deze regel kun je toevoegen aan het bestand ‘app/code/local/Mage/Sales/Model/Order/Pdf’ onder de regel:

$page->drawText(Mage::helper('sales')->__('Invoice # ') . $invoice->getIncrementId(), 35, 780, 'UTF-8');

De datum kan in verschillende formaten worden weergegeven in de PDF. Hier voor wordt gebruik gemaakt van de ‘formatDate’ functie. Binnen de 2e parameter kun je opgeven in welk formaat de datum weergegeven moet worden. Je kunt hiervoor kiezen uit de volgende PHP class constanten uit de Mage_Core_Model_Locale class:

  • Mage_Core_Model_Locale::FORMAT_TYPE_FULL = ‘full’
  • Mage_Core_Model_Locale::FORMAT_TYPE_LONG = ‘long’
  • Mage_Core_Model_Locale::FORMAT_TYPE_MEDIUM = ‘medium’
  • Mage_Core_Model_Locale::FORMAT_TYPE_SHORT = ‘short’

Als je werkt met de Nederlandse Magento dan kun je de volgende output verwachten:

  • ‘full’ = maandag 31 januari 2011
  • ‘long’ = 31 januari 2011
  • ‘medium’ = 31 jan. 2011
  • ‘short’ = 31-01-11

Met de 3e parameter kun je overigens opgeven of je de tijd ook wilt weergeven. Als je hier false opgeeft zal de tijd niet worden weergeven, bij true wel.

Categorieën
PHP WordPress

Gravity Forms custom posts

Via Gravity Form is het mogelijk om je bezoekers (concept)berichten te laten plaatsen. Je kunt hierbij gebruik maken van verschillende vooraf gedefinieerde velden. Helaas is het nog niet mogelijk om custom posts aan te maken. Gelukkig is deze functionaliteit met behulp van filters en hooks wel te realiseren.

function custom_gform_set_post_type($post_data, $form) {
	$classes = explode(' ', $form['cssClass']);

	$items = apply_filters('custom_gform_post_type_classes', array());

	foreach($items as $class => $post_type) {
		if(in_array($class, $classes)) {
			$post_data['post_type'] = $post_type;
		}
	}

    return $post_data;
}

add_filter('gform_post_data', 'custom_gform_set_post_type', 10, 2);

Vervolgens kun je met behulp van de volgende filter CSS classes koppelen aan een custom post type.

function dvt_gform_post_type_classes($ptc) {
	$ptc['opponent-form'] = 'opponent';

	return $ptc;
}

add_filter('custom_gform_post_type_classes', 'dvt_gform_post_type_classes');
Categorieën
PHP WordPress

Gravity Forms custom taxonomies dropdown

Via Gravity Form is het mogelijk om je bezoekers (concept)berichten te laten plaatsen. Je kunt hierbij gebruik maken van verschillende vooraf gedefinieerde velden. Helaas is er nog geen standaard veld beschikbaar voor custom post types en taxonomies. Gelukkig is deze functionaliteit met behulp van filters en hooks wel te realiseren.

Populate dropdown

function custom_gform_dropdown_terms($arguments, $withEmpty = true) {
	$terms = get_terms($arguments);

	$items = array();

	if($withEmpty) {
		$items[] = array('text' => '', 'value' => '');
	}

	foreach($terms as $term) {
		$items[] = array('value' => $term->term_id, 'text' => $term->name);
	}

	return $items;
}

function custom_gform_populate_dropdown($form) {
	$items = apply_filters('custom_gform_taxonomies_classes', array());

	foreach($form['fields'] as &$field) {
		$classes = explode(' ', $field['cssClass']);

		foreach($items as $class => $taxonomy) {
			if(in_array($class, $classes)) {
				$field['type'] = 'select';
				$field['choices'] = custom_gform_dropdown_terms($taxonomy);
			}
		}
	}

	return $form;
}

add_filter('gform_pre_render', 'custom_gform_populate_dropdown');

Koppel CSS class met taxonomy

Vervolgens kun je met behulp van de volgende filter CSS classes koppelen aan een taxonomy.

function dvt_gform_taxonomy_classes($tc) {
	$tc['taxonomy-level'] = 'level';
	$tc['taxonomy-region'] = 'region';

	return $tc;
}

add_filter('custom_gform_taxonomies_classes', 'dvt_gform_taxonomy_classes');

Terms opslaan per post

Tot slot zorgt het volgende code fragment er voor dat de terms worden opgeslagen bij de betreffende post.

function custom_gform_save_terms($lead, $form) {
	// Check if the submission contains a WordPress post
	if(isset($lead['post_id'])) {
		$items = apply_filters('custom_gform_taxonomies_classes', array());

		foreach($form['fields'] as &$field) {
			$classes = explode(' ', $field['cssClass']);

			foreach($items as $class => $taxonomy) {
				if(in_array($class, $classes)) {
					$value = (int) $lead[$field['id']];

					wp_set_object_terms($lead['post_id'], $value, $taxonomy);
				}
			}
		}
	}
}

add_action('gform_post_submission', 'custom_gform_save_terms', 10, 2);

Bronnen

Categorieën
Magento PHP

Magento webwinkel verhuizen

Het verhuizen van een Magento webwinkel naar een nieuwe server / locatie kan een hele klus zijn. Een Magento installatie die al een aantal jaren actief kan al snel 2+ GB aan ruimte in nemen. Met meer dan 100.000+ bestanden en 25.000+ mappen kan het verhuizen veel tijd kosten. Daarnaast is er vaak ook een MySQL database die 500+ MB aan data bevat.

Op de Wiki van Magento staat beschreven hoe je een Magento installatie kunt verhuizen naar een andere locatie. In eerste instantie wordt beschreven dat je een nieuwe Magento installatie moet opzetten en vervolgens de oude data moet importeren. Aan het eind van het artikel wordt echter ook een alternatieve methode toegelicht. In de alternatieve variant hoef je geen nieuwe Magento installatie opzetten. In plaats daarvan moet je daadwerkelijk alle bestanden en de database naar de nieuwe locatie verplaatsen.

Er zijn wel een aantal aandachtspunten waar je rekening mee moeten houden als je een Magento webwinkel op deze manier gaat verhuizen. Zo zul je in veel gevallen het bestand pear.ini (downloader/pearlib/pear.ini) moeten wijzigen om de MagentoConnect manager te laten werken.

Magento heeft hier een eenvoudig script voor beschikbaar gesteld die een nieuw pear.ini bestand voor je kan generen. Ik zelf vond het Magento script niet heel gebruiksvriendelijk en heb daarom een aantal verbeteringen aangebracht.

<?php

define('CR', "\r");
define('LF', "\n");
define('CRLF', CR . LF);

$contents = explode(LF, file_get_contents('pear.ini'));

$data = unserialize($contents[1]);

if(isset($data['bin_dir'])) {
	$oldPath = $data['bin_dir'];
	$newPath = getcwd();

	foreach($data as $key => $value) {
		if(is_string($value)) {
			$data[$key] = str_replace($oldPath, $newPath, $value);
		}
	}
}

?>
<!DOCTYPE html>

<html lang="en">
	<head>
		<meta charset="utf-8" />

		<title>Magento pear.ini generator | Pronamic</title>
	</head>

	<body>
		<h1>Magento pear.ini generator</h1>

		<h2>Paths</h2>

		<dl>
			<dt>Old path</dt>
			<dd>
				<?php if(isset($oldPath)): ?>
				<code><?php echo $oldPath; ?></code>
				<?php else: ?>
				<em>Unknown</em>
				<?php endif; ?>
			</dd>

			<dt>New path</dt>
			<dd>
				<?php if(isset($newPath)): ?>
				<code><?php echo $newPath; ?></code>
				<?php else: ?>
				<em>Unknown</em>
				<?php endif; ?>
			</dd>
		</dl>

		<h2>pear.ini</h2>

		<?php 

		$pearIni = $contents[0] . LF . serialize($data);

		?>
		<p>
			<textarea name="pear.ini" cols="80" rows="10"><?php echo $pearIni; ?></textarea>
		</p>
	</body>
</html>

Mocht je ooit een Mangeto webwinkel moeten verhuizen dan kan dit script zeker van pas komen!

Categorieën
JavaScript jQuery PHP WordPress

WordPress GEO afstand

Op de website van Pronamic is in de voettekst een leuke GEO functionaliteit opgenomen:

In dit bericht zal ik beschrijven hoe je deze functionaliteit kunt toevoegen aan je eigen WordPress website. Allereerst voegen we aan footer.php de volgende code toe:

<div id="location-footer" class="geolocation">
	<?php get_template_part('geo', 'location-footer'); ?>
</div>

Vervolgens maken we binnen de theme map het bestand geo-location-footer.php aan. Binnen dit bestand plaatsen we de volgende code:

<?php

global $distanceToHQ;

if(isset($distanceToHQ) && $distanceToHQ < 50): ?>

Volgens GeoIP bent u minder dan 50 km verwijderd van een kopje koffie en een goed gesprek over internet. Kom vrijblijvend eens <a href="/contact/">bij ons langs</a>. Volgt u ons trouwens al op <a href="http://www.twitter.com/pronamic">Twitter</a>?

<?php else: ?>

Wij komen graag eens bij u langs voor een goed gesprek. Neem vrijblijvend <a href="/contact/">contact</a> met ons op. Volg ons op <a href="http://www.twitter.com/pronamic">Twitter</a>.
Bekijk onze foto's op <a href="http://www.flickr.com/groups/pronamic/">FlickR</a> of wordt lid van onze groep op <a href="http://pronamic.hyves.nl/">Hyves</a>.

<?php endif; ?>

Vervolgens voegen we de volgende code toe aan functions.php.

/**
 * Calculate distance between to points and return it in the specified unit
 */
function rt_calculate_distance($lat1, $lon1, $lat2, $lon2, $unit) {
	$theta = $lon1 - $lon2; 

	$dist = sin(deg2rad($lat1)) * sin(deg2rad($lat2)) + cos(deg2rad($lat1)) * cos(deg2rad($lat2)) * cos(deg2rad($theta));
	$dist = acos($dist);
	$dist = rad2deg($dist); 

	$miles = $dist * 60 * 1.1515;

	$unit = strtoupper($unit);

	if($unit == 'K') {
		return ($miles * 1.609344);
	} else if($unit == 'N') {
		return ($miles * 0.8684);
	} else {
		return $miles;
	}
}

/**
 * Calculate distance between to points and return it in the specified unit
 */
function rt_distance($location1, $location2, $unit) {
	return rt_calculate_distance($location1->latitude, $location1->longitude, $location2->latitude, $location2->longitude, $unit);
}

////////////////////////////////////////////////////////////

/**
 * AJAX
 */
function rt_ajax_geo() {
	$hqLocation = new stdClass();
	$hqLocation->latitude = 53.056079;
	$hqLocation->longitude = 6.200763;

	$clientLocation = new stdClass();
	$clientLocation->latitude = (float) filter_input(INPUT_POST, 'latitude', FILTER_SANITIZE_NUMBER_FLOAT, FILTER_FLAG_ALLOW_FRACTION);
	$clientLocation->longitude = (float) filter_input(INPUT_POST, 'longitude', FILTER_SANITIZE_NUMBER_FLOAT, FILTER_FLAG_ALLOW_FRACTION);

	global $distanceToHQ;

	$distanceToHQ = rt_distance($hqLocation, $clientLocation, 'K');

	$element = filter_input(INPUT_POST, 'element', FILTER_SANITIZE_STRING);

	get_template_part('geo', $element);

	die();
}

add_action('wp_ajax_rt_geo', 'rt_ajax_geo');
add_action('wp_ajax_nopriv_rt_geo', 'rt_ajax_geo');

////////////////////////////////////////////////////////////

function rt_geo_scripts() {
	$key = 'INSERT-YOUR-KEY';

	wp_register_script('google-jsapi',
		'https://www.google.com/jsapi?key=' . $key
	);

	wp_register_script('rt-geo-script',
		get_bloginfo('template_directory') . '/js/geo.js' ,
		array('google-jsapi', 'jquery') ,
		'1.0'
	);

	wp_enqueue_script('rt-geo-script');
}

add_action('template_redirect', 'rt_geo_scripts');

Je moet voor het gebruik van deze functionaliteit een Google JS API key aanvragen. Vervang de tekst INSERT-YOUR-KEY in bovenstaande code met je eigen API key.

De laatste stap is om binnen je theme map het JavaScript bestand geo.js aan te maken in de map js met de volgende inhoud:

jQuery(document).ready(function($) {
	if($.isPlainObject(google.loader.ClientLocation)) {
		var clientLocation = google.loader.ClientLocation;

		$(".geolocation").each(function() {
			var element = $(this);

			var data = {
				action: "rt_geo" ,
				element: element.attr("id") ,
				latitude: clientLocation.latitude ,
				longitude: clientLocation.longitude
			};

			$.post("/wp-admin/admin-ajax.php", data, function(response) {
				element.html(response);
			});
		});
	}
});

Als het goed is zou je nu een dynamisch blok in je footer moeten hebben die geüpdate wordt zodra de afstand tussen de bezoeker en je locatie bekend is. Je kunt uiteraard geo-location-footer.php helemaal naar je eigen wens aanpassen. Daarnaast kun je een onbeperkt aantal GEO elementen toevoegen. Mocht je vragen, opmerkingen, verbeteringen, etc. hebben laat dan even een reactie achter!

Categorieën
Linux PHP

OpenSSL fout Twinfield API

Een aantal weken terug wilde ik bij Pronamic wat experimenteren met de Twinfield API. De Twinfield API werkt met behulp van SOAP over een beveiligde HTTP verbinding. Volgens Twinfield zou je op de volgende manier een verbinding moeten kunnen maken:

$soapClient = new SoapClient('https://login.twinfield.com/webservices/session.asmx?wsdl', array('trace' => 1));

Helaas resulteerde het uitvoeren van bovenstaande PHP code in een foutmelding:

Warning: file_get_contents(): SSL operation failed with code 1. OpenSSL Error messages:
error:14092073:SSL routines:SSL3_GET_SERVER_HELLO:bad packet length in Twinfield.php on line 8

Call Stack:
    0.0002     651112   1. {main}() Twinfield.php:0
    0.0003     651896   2. file_get_contents() Twinfield.php:8

Warning: file_get_contents(): Failed to enable crypto in Twinfield.php on line 8

Call Stack:
    0.0002     651112   1. {main}() Twinfield.php:0
    0.0003     651896   2. file_get_contents() Twinfield.php:8

Warning: file_get_contents(https://login.twinfield.com/webservices/session.asmx?wsdl): failed to open stream: operation failed in Twinfield.php on line 8

Call Stack:
    0.0002     651112   1. {main}() Twinfield.php:0
    0.0003     651896   2. file_get_contents() Twinfield.php:8

Fatal error: SoapClient::SoapClient(): 'uri' option is required in nonWSDL mode in Twinfield.php on line 13

Call Stack:
    0.0002     651112   1. {main}() Twinfield.php:0
    0.0686     653888   2. SoapClient->SoapClient() Twinfield.php:13

Dezelfde code werkte echter wel op een andere server, maar helaas niet op mijn ontwikkel- en test server. Dat was toch wel erg vervelend, daarom ben ik opzoek gegaan naar een oplossing. Ik kwam na een speurtocht al snel anderen tegen met vergelijkbare problemen. Zo rapporteerde ns@centralservices.nl een vergelijkbare fout in PHP’s bug tracking systeem.

Ik kon echter erg lastig een oplossing vinden voor het probleem. Ik las wel op verschillende websites dat het om een probleem in het OpenSSL pakket ging. Op mijn Debian ontwikkel- en test server had ik OpenSSL/0.9.8g geïnstalleerd. Dit is momenteel de stabiele versie van het OpenSSL pakket op het debian platform. Aangezien ik geen concrete oplossing kon vinden voor het probleem heb ik de test versie van OpenSSL geïnstalleerd.

Hiervoor heb ik eerst de volgende regels toegevoegd aan /etc/apt/sources.list.

deb http://ftp.nl.debian.org/debian squeeze main
deb-src http://ftp.nl.debian.org/debian/ squeeze main

Vervolgens heb ik OpenSSL en Apache opnieuw geïnstalleerd met behulp van de volgende commando’s:

# apt-get install openssl

# apt-get install apache

Sindsdien heb ik OpenSSL/0.9.8o draaien op mijn server en is mijn probleem opgelost. Ik kan nu zonder problemen de Twinfield SOAP API aanroepen.

Categorieën
PHP WordPress

Gravity Forms tab index probleem

In één van de websites die ik bij Pronamic heb ontwikkeld had ik een probleem met de tabindex van een aantal Gravity Forms formulieren op 1 pagina. De tabindex van de invoervelden van deze formulieren begonnen namelijk allemaal op 1.

<input name="input_1" id="input_1_1" type="text" value="" class="medium" tabindex="1" />
<input name="input_1" id="input_4_1" type="email" value="" class="small" tabindex="1" />

Hierdoor was het niet mogelijk om met behulp van de TAB toets eenvoudig door de verschillende invoervelden te navigeren. Gelukkig was de oplossing na een zoekopdracht bij Google snel gevonden.

Sinds Gravity Forms v1.3.13 Beta 1 zijn er WordPress filters beschikbaar om de tabindex aan te passen. In de changelog van Gravity Forms is daarover het volgende opgenomen:

- Added filter to define the tab index start value.
	add_filter("gform_tabindex_4", create_function("", "return false;"));

De 4 in gform_tabindex_4 staat voor de unieke Gravity Form id. De volgende code zorgt er voor dat Gravity Form met id 4 begint met een tabindex van 100.

add_filter('gform_tabindex_4', create_function('', 'return 100;'));
Categorieën
Magento PHP

Magento factuur PDF aanpassen

Ik werk bij Pronamic regelmatig met het opensource e-commerce platform Magento. Van veel klanten krijgen we het verzoek om de PDF documenten die Magento genereert aan te passen. Er zijn een aantal plugins die het mogelijk maken om de opmaak van de PDF documenten aan te passen:

Helaas zijn deze plugins vaak beperkt tot het aanpassen van kleuren en lettertypen of richten ze zich op 1 specifieke opmaak. Voor veel van onze klanten zijn deze plugins daarom niet interessant. Gelukkig zijn er ook mogelijkheden om de PDF’s aan te passen met behulp van PHP code. Op internet zijn verschillende artikelen te vinden over hoe dit is aan te passen.

De code die verantwoordelijk is voor het generen van de PDF documenten is o.a. te vinden in de volgende map:

/app/code/core/Mage/Sales/Model/Order/Pdf/

Het is mogelijk om de bestanden in deze map te wijzigen, maar verstandig is dit niet. Alle wijzigingen in deze map gaan namelijk verloren zodra je Magento update. Om dit te voorkomen kun je het beste de bestanden die je wijzigt opslaan in de volgende map:

/app/code/local/Mage/Sales/Model/Order/Pdf/

Ik zal in het kort uitleggen hoe je een voettekst kunt toevoegen aan de PDF document.

Voettekst toevoegen aan Magento PDF

Allereerst moeten de volgende 2 bestanden worden gekopieerd:

/app/code/core/Mage/Sales/Model/Order/Pdf/Abstract.php
/app/code/core/Mage/Sales/Model/Order/Pdf/Invoice.php

naar de volgende locatie:

/app/code/local/Mage/Sales/Model/Order/Pdf/Abstract.php
/app/code/local/Mage/Sales/Model/Order/Pdf/Invoice.php

Hiermee voorkom je dat na een Magento update je wijzigingen verloren gaan.

Vervolgens creëren we in de class Mage_Sales_Model_Order_Pdf_Abstract (in het bestand Abstract.php) een nieuwe functie die verantwoordelijk is voor het genereren van de voettekst. Ik heb deze functie in dit voorbeeld bewust recht toe aan gehouden. Er zijn uiteraard allerlei mogelijkheden om deze functie in te korten en flexibeler te maken.

/**
 * Insert footer
 */
protected function insertFooter(&$page, $store = null) {
	$this->_setFontBold($page);

	$startX = 50;
	$startY = 80;
	$columnWidth = 125;
	$lineY = 10;

	// Footer title
	$page->setFillColor(new Zend_Pdf_Color_GrayScale(0.25));

	$name = 'Deze webwinkel';
	if($store !== null) {
		$name = $store->getFrontendName();
	}

	$text = sprintf('%s is onderdeel van Pronamic', $name);

	$page->drawText($text, $startX, $startY, 'UTF-8');

	$startY = $startY - $lineY - $lineY;

	// Columns
	$page->setFillColor(new Zend_Pdf_Color_GrayScale(0.5));

	// Column 1
	$x = $startX;
	$y = $startY;

	$page->drawText('Pronamic', $x, $y, 'UTF-8');
	$y -= $lineY;
	$page->drawText('Merkebuorren 39a', $x, $y, 'UTF-8');
	$y -= $lineY;
	$page->drawText('9241 GB Wijnjewoude', $x, $y, 'UTF-8');

	// Column 2
	$x += $columnWidth;
	$y = $startY;

	$page->drawText('0516 481 200', $x, $y, 'UTF-8');
	$y -= $lineY;
	$page->drawText('info@pronamic.nl', $x, $y, 'UTF-8');
	$y -= $lineY;
	$page->drawText('pronamic.nl', $x, $y, 'UTF-8');

	// Column 3
	$x += $columnWidth;
	$y = $startY;

	$page->drawText('RABO 12.34.56.789', $x, $y, 'UTF-8');
	$y -= $lineY;
	$page->drawText('IBAN NL64 RABO 0123456789', $x, $y, 'UTF-8');
	$y -= $lineY;
	$page->drawText('BIC RABONL2U', $x, $y, 'UTF-8');

	// Column 3
	$x += $columnWidth;
	$y = $startY;

	$page->drawText('', $x, $y, 'UTF-8');
	$y -= $lineY;
	$page->drawText('KVK 01108446', $x, $y, 'UTF-8');
	$y -= $lineY;
	$page->drawText('BTW NL.1234.56.789.B01', $x, $y, 'UTF-8');
}

Vervolgens moet deze functie nog aangeroepen worden. De aanroep van deze functie voegen we toe in de functie getPdf van de Mage_Sales_Model_Order_Pdf_Invoice class.

		$this->insertFooter($page, $invoice->getStore());
	}

	$this->_afterGetPdf();

	return $pdf;
}

Vervolgens zal je factuur PDF er als volgt uit zien:


Update

Omdat veel lezers moeite hebben met het aanpassen van de bestanden heb ik ze voor het gemak online gezet.

Categorieën
Geen categorie PHP

Waarom PHP niet afsluiten?

Het valt me op dat veel ontwikkelaars PHP bestanden netjes openen met <?php en vervolgens weer afsluiten met ?>. Dit terwijl het afsluiten van PHP met een ?> niet verplicht is. Op internet zijn verschillende meningen te lezen over het wel en niet gebruiken van de afsluitende ?> tag.

Een aantal grote PHP frameworks hebben in hun code conventies opgenomen dat het gebruik van ?> vermeden moet worden. Ik zelf ben ook een groot fan van het vermijden van de afsluitende tag. Mijn motto: hoe korter, hoe simpeler, hoe beter.