Maandelijks archief: oktober 2010

Gravity Forms “datepicker” in het Nederlands

De Gravity Forms “datepicker” maakt gebruik van de jQuery UI datepicker. De teksten in deze datepicker zjin standaard in het Engels. De .po en .mo vertaal bestanden van Gravity Forms vertalen deze teksten helaas niet. Dit heeft te maken met dat de jQuery UI bibliotheek op een andere manier om gaat met vertalingen dan WordPress. Meer informatie hierover is te lezen op de i18n wiki pagina van de jQuery UI bibliotheek. De Nederlandse jQuery UI datepicker vertalingen zijn gelukkig gewoon beschikbaar in de jQuery UI code repository.

Voor het gemak kun je de vertaling ook downloaden vanaf mijn website. Zodra je dit bestand hebt gedownload moet je binnen je WordPress theme nog een koppeling maken naar dit bestand. Hiervoor plaats je het bestand in de volgende locatie binnen je theme:

httpdocs\wp-content\themes\je-eigen-theme-map\scripts\jquery.ui.datepicker-nl.js

De meeste logisch stap is om binnen je header.php bestand een link te maken naar dit bestand.

<script type="text/javascript" src="<?php bloginfo('template_directory'); ?>/scripts/jquery.ui.datepicker-nl.js"></script>

In eerste lijkt dit goed te werken, maar helaas resulteert dit in bepaalde gevallen in JavaScript fouten. Zodra je namelijk een Gravity Forms formulier zonder datepicker gebruikt zul je de volgende foutmelding krijgen.

$.datepicker is undefined
/wp-content/themes/je-eigen-theme-map/scripts/jquery.ui.datepicker-nl.js
Line 4

Dit komt doordat Gravity Forms de datepicker scripts alleen toevoegt zodra deze nodig zijn. Dit is terug te zien in het volgende bestand van de Gravity Forms plugin:

httpdocs\wp-content\plugins\gravityforms\form_display.php

if(self::has_date_field($form)){
	wp_enqueue_script("gforms_ui_datepicker", GFCommon::get_base_url() . "/js/jquery-ui/ui.datepicker.js", array("jquery"), GFCommon::$version, true);
	wp_enqueue_script("gforms_datepicker", GFCommon::get_base_url() . "/js/datepicker.js", array("gforms_ui_datepicker"), GFCommon::$version, true);
	$jquery_enqueued = true;
}

Om die reden kun je beter controleren of de “gforms_ui_datepicker” JavaScript wordt gebruikt. Vervolgens kun je het script met de Nederlandse vertaling toevoegen. In het volgende code fragment is te zien hoe je dit doet:

/**
 * Gravity forms translate datepicker
 */
function custom_translate_datepicker() {
	if(wp_script_is('gforms_ui_datepicker')) {
		wp_enqueue_script('gforms_ui_datepicker_nl', get_bloginfo('template_directory') . '/scripts/jquery.ui.datepicker-nl.js', array('gforms_ui_datepicker'), false, true);
	}
}

add_action('wp_print_scripts', 'custom_translate_datepicker');

Bovenstaande code kun je toevoegen aan het bestand functions.php binnen je theme.

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;'));

De Evernote Site Memory Button

Ik maak al sinds een aantal maanden dankbaar gebruik van Evernote. Evernote is een online dienst waar je notities kunt bewaren. Het plaatsen en lezen van notities kan met behulp van verschillende applicaties. Zo heeft Evernote applicaties voor onder andere de PC, Mac, iPhone, iPad en meer.

Vrijwel alle belangrijke poststukken die ik (nog) ‘analoog’ binnen krijg scan ik in en plaats ik in Evernote. Zo heb ik overal en altijd toegang tot mijn belangrijke documenten. Ik kan zo bijvoorbeeld overal mijn facturen, verzekeringsdocumenten, certificaten, leveringscontracten, etc. raadplegen. Voor het inscannen maak ik gebruik van zeer de eenvoudige en compacte Fujitsu Scansnap S300 scanner. Lifehacker Martijn Aslander heeft over deze combinatie een interessant artikel geschreven: “Kan een scanner ook een lifehack zijn?“.

Sinds kort biedt Evernote ook een functionaliteit aan om Evernote toe te voegen aan je eigen website. Ik als groot fan van Evernote heb vandaag de zogenaamde “Evernote Site Memory Button” toegevoegd aan mijn website. Met behulp van deze button kun je elk bericht op mijn website eenvoudig in Evernote plaatsen. Hoe ik de button heb toegevoegd kun je nalezen op de website van Evernote. Ben je nog niet overtuigd van Evernote lees dan de 10 Evernote tips op Lifehacking.nl eens door.

Magento links met “active” class

Standaard geeft Magento de product categorieën weer op de plaats van waar je een menu zou verwachten. Deze functionaliteit moet ik vaak aanpassen bij Magento webwinkels die Pronamic ontwikkeld. Er zijn verschillende manieren om een ander menu toe te voegen. Ik beschrijf in dit bericht een nieuwe benadering.

Allereerst voeg ik via het Magento beheerpaneel een nieuw statisch blok toe. Vervolgens kan in de inhoud van dit statische blok gebruikt worden om het menu op te bouwen. Normaliter is een menu niets meer dan een lijst (<ul>) met linkjes (<a>) als items (<li>).

Vervolgens voeg ik aan het bestand cms.xml de volgende XML regel toe in het <default> element.

app\design\frontend\default\custom-theme\layout\cms.xml

<default>
	<reference name="header">
		<block type="cms/block" name="cms_menu_main" template="page/template/menu.phtml">
			<action method="setBlockId"><block_id>menu_main</block_id></action>
		</block>
	</reference>

	...
</default>

Vervolgens kun je in header.phtml de volgende regel opnemen.

app\design\frontend\default\custom-theme\template\page\html\header.phtml

<nav id="main-nav">
	<?php echo $this->getChildHtml('cms_menu_main') ?>
</nav>

Wat jammer is aan deze opzet is dat de menu links niet automatisch worden voor zien van ‘active’ classes. Vaak wil je zodra een menu item actief is dat deze anders wordt weergegeven. Om dit probleem op te lossen kun je de volgende code opnemen in header.phtml.

<nav id="main-nav">
	<?php 

	$html = $this->getChildHtml('cms_menu_main');

	if(!empty($html)) {
		try { 
			$xml = simplexml_load_string($html);

			$currentUrl = Mage::helper('core/url')->getCurrentUrl();
			$currentHost = parse_url($currentUrl, PHP_URL_HOST);
			$currentPath = parse_url($currentUrl, PHP_URL_PATH);
			$currentPath = rtrim($currentPath, '/');

			foreach($xml->xpath('//a') as $anchor) {
				$url = $anchor['href'];

				$host = parse_url($url, PHP_URL_HOST);
				$path = parse_url($url, PHP_URL_PATH);
				if($path != null) {
					$path = rtrim($path, '/');
				}

				$class = (string) $anchor['class'];
				$classes = empty($class) ? array() : explode(' ', $class);

				if($path !== null && ($host == null || $currentHost == $host)) {
					if(strcmp($path, $currentPath) === 0) {
						$classes[] = 'active';
					} else if($path != '/' && strncmp($path, $currentPath, strlen($path)) === 0) {
						$classes[] = 'active-child';
					}
				}

				if(!empty($classes)) {
					$anchor['class'] = implode(' ', $classes); 
				}
			}

			$html = $xml->asXML();
			$html = str_replace('<?xml version="1.0"?>', '', $html);
		} catch (Exception $e) { 
			// Could not add classes, no big deal
		}

		echo $html;
	} else {
		// Show somehting else?
	}

	?>
</nav>

Bovenstaande code zorgt er voor dat actieve linkjes automatisch de class ‘active’ krijgen. Mocht je vragen of opmerkingen hebben laat dan even een reactie achter.

Informatie

@tmobile_webcare werkt chill

Ik heb vandaag mijn iPhone 3G simlock vrij gemaakt. Dit ging echter minder vlot dan ik had gehoopt. De dag nadat ik een nieuwe telefoon had gekocht heb ik direct telefonisch contact opgenomen met de T-Mobile klantenservice. Voordat ik überhaupt iemand aan de lijn had was ik al 5 minuten verder. Vervolgens werd ik weer doorgeschakeld naar iemand die over het simlock vrijmaken ging. Uiteindelijk heb ik alle gegevens doorgegeven en werd mij beloofd dat ik een e-mail zou krijgen met de benodigde gegevens.

Na twee weken wachten had ik echter nog steeds geen e-mail binnen. Collega Pim Vellinga wees me er toen op dat je T-Mobile ook via Twitter kunt benaderen. Ze hebben een speciaal Twitter account voor al hun klanten en vragen. Dat was geen slecht idee, want binnen 2 dagen was het geregeld.

@remcotolsma op 28 september 2010
2 weken terug werd beloofd dat mijn simlock zou worden verwijderd, ik zou hier over gemaild worden, helaas nog niks binnen.

@remcotolsma op 28 september 2010
Toestel: Apple iPh-003 – IMEI-nummer: *************** – E-mailadres: info@remcotolsma.nl – Geboortedatum: **-**-**** – Postcode: **** **

@tmobile_webcare op 28 september 2010
Sorry, ik vergat om je 06-nummer te vragen. Kun je dat ook nog even sturen? ^AdW

@remcotolsma op 28 september 2010
Mijn 06-nummer is: 06 ********

@tmobile_webcare op 29 september 2010
Ik heb je gegevens doorgestuurd naar de afdeling Simunlock. Zij informeren je zo spoedig mogelijk verder. ^AdW

@tmobile_webcare op 30 september 2010
Hallo Remco, de procedure en de simunlockcode worden vandaag via e-mail naar je toegestuurd. ^AdW

Direct na deze tweet kwam de e-mail met de iPhone simunlock procedure binnen rollen. Dus bel niet met de T-Mobile klantenservice, maar regel je zaken via Twitter! Dat gaat wel zo eenvoudig en snel :).

iPhone 3g simlock vrij dankzij @tmobile_webcare :).

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.

WordPress Google Maps

Er zijn al een flink aantal Google Maps plugins te vinden in WordPress Plugin Directory. De zoekopdracht “google maps” in de WordPress Plugin Directory levert al 212 resultaten op. Toch vonden we bij Pronamic deze plugins niet altijd even handig. De beschikbare plugins hebben namelijk vaak een aantal minpunten:

  • Google Maps API versie 2

    Veel bestaande Google Maps plugins werken nog met versie 2 van de Google Maps API. Om deze API te gebruiken moet je een zogenaamde API sleutel aanvragen. Deze sleutel moet vervolgens weer worden verwerkt in de plugin configuratie. In versie 3 van de Google Maps API is dit echter niet meer nodig.

  • WordPress Shortcode API

    Een groot deel van de plugins werken met de WordPress Shortcode API. Dit is een erg krachtige techniek, maar niet altijd even gebruiksvriendelijk. In de inhoud van een bericht of pagina is namelijk een shortcode te zien:

    [google-map-sc external_links="true" zoom="3"]

    De notatie en syntax van shortcodes zijn niet altijd voor iedereen even duidelijk.

  • Extra tabellen in database

    Enkele plugins creëren extra tabellen in je WordPress database om data in op te slaan. In veel gevallen is dat volgens mij nergens voor nodig en wordt daarmee alleen de database vervuilt. WordPress biedt genoeg functionaliteiten om extra data op te slaan bij een bericht of pagina.

  • Custom Post Types

    Bijna alle plugins zijn ontwikkeld voordat WordPress 3 was gelanceerd. Daardoor zijn de plugins niet altijd even goed aangepast op de nieuwe functionaliteiten van WordPress 3. Zo zijn veel bestaande plugins alleen gericht op berichten en pagina’s, maar niet op andere zelf gedefinieerde post typen.

Om die reden hebben we zelf een nieuwe Google Maps plugin ontwikkeld. Deze plugin maakt gebruik van de Google Static Maps API en de nieuwe Google Maps JavaScript V3 API. Hierdoor is het niet nodig om een API sleutel aan te vragen en zal ook zonder JavaScript ondersteuning een map weergegeven worden.

Wat met name leuk is aan deze plugin dat je per post type kunt aangeven of de Google Maps functionaliteit aan of uit moet staan. Als je als ontwikkelaar gebruik maakt van de register_post_type functie van WordPress is deze plugin erg interessant. Het is namelijk heel eenvoudig om de Google Maps functionaliteit toe te voegen aan je eigen gedefinieerde post type.