Categorieën
WordPress

WordPress network update probleem

Bij het updaten van een WordPress network (multisite) installatie liep ik tegen problemen aan bij het geautomatiseerd updaten van WordPress. Bij het uitvoeren van een update kreeg ik de volgende foutmelding:

Destination directory for file streaming does not exist or is not writable.

De doel map voor het streamen van bestanden bestaat niet of is niet schrijfbaar.

Deze foutmelding werd binnen het bestand class-http.php gecreëerd doordat de te downloaden bestanden niet konden worden opgeslagen.

Dit probleem heb ik uiteindelijk tijdelijk kunnen oplossen door de wp-content map tijdelijk schrijfrechten te geven (chmod 777) zoals ook in een WordPress forum topic werd vermeld.

Categorieën
PHP WordPress

Hoe werkt het WordPress update systeem?

WordPress is uitgerust met een zeer eenvoudig en krachtig update systeem. Dankzij dit systeem kunnen WordPress gebruikers eenvoudig WordPress, plugins en thema’s eenvoudig updaten. Ik als ontwikkelaar was erg benieuwd naar deze opzet van dit systeem. Ik ben daarom de WordPress code ingedoken om de werking van dit systeem te onderzoeken.

Het WordPress update systeem maakt gebruik van een API die op WordPress.org staat. Dankzij deze API is eenvoudig informatie op te vragen over WordPress, plugins en thema’s. Elke WordPress installatie informeert om de 12 uren of er updates beschikbaar zijn via de WordPress.org API. De WordPress.org API kun je benaderen via de volgende URL: http://api.wordpress.org/

Als je de WordPress broncode doorzoekt op deze URL zul je een tiental bestanden vinden waarin hier gebruik van gemaakt wordt. In het bestand wp-includes\update.php is te zien op welke wijze WordPress controleert of er nieuwe versies zijn van WordPress, plugins of thema’s. Ik zal hieronder met enkele code fragmenten laten zien hoe gecontroleerd wordt of er thema update beschikbaar is.

WordPress zal in eerste instantie informatie over de geïnstalleerde WordPress thema’s opvragen met behulp van de get_themes() functie. Vervolgens zal deze informatie met behulp van HTTP POST verzoek worden verstuurd naar de WordPress.org API. Hiervoor wordt gebruik gemaakt van de wp_remote_post() functie die onderdeel is van de WordPress HTTP API. De WordPress.org API zal vervolgens aangeven of er updates beschikbaar zijn voor de geïnstalleerde thema’s.

In onderstaande code fragment is globaal te zien hoe een dergelijk verzoek wordt opgebouwd en uitgevoerd:

$themes = array(
	'twentyten' => array(
		'Name' => 'Twenty Ten' ,
		'Version' => '1.0'
	) ,
	'platform' => array(
		'Name' => 'Platform' ,
		'Version' => '1.0'
	) ,
	'delicate' => array(
		'Name' => 'Delicate' ,
		'Version' => '1.0'
	)
);

$args = array(
	'body' => array('themes' => serialize($themes))
);

$response = wp_remote_post('http://api.wordpress.org/themes/update-check/1.0/', $args);

if(is_wp_error($response)) {
	exit('Sorry, something went wrong.');
}

if(200 != $response['response']['code']) {
	exit('Sorry, something went wrong.');
}

$result = unserialize($response['body']);

Als alles goed gaat dan zal de variabele $result informatie over thema’s bevatten waar updates voor beschikbaar zijn. Hieronder zie je een voorbeeld dump van de $result variabele:

array(3) {
	["twentyten"]=>
	array(3) {
		["new_version"]=>
		string(3) "1.1"
		["url"]=>
		string(44) "http://wordpress.org/extend/themes/twentyten"
		["package"]=>
		string(61) "http://wordpress.org/extend/themes/download/twentyten.1.1.zip"
	}
	["platform"]=>
	array(3) {
		["new_version"]=>
		string(5) "1.3.1"
		["url"]=>
		string(43) "http://wordpress.org/extend/themes/platform"
		["package"]=>
		string(62) "http://wordpress.org/extend/themes/download/platform.1.3.1.zip"
	}
	["delicate"]=>
	array(3) {
		["new_version"]=>
		string(5) "3.4.3"
		["url"]=>
		string(43) "http://wordpress.org/extend/themes/delicate"
		["package"]=>
		string(62) "http://wordpress.org/extend/themes/download/delicate.3.4.3.zip"
	}
}

De WordPress.org API geeft dus aan welke versie nu beschikbaar is en waar deze te vinden is. WordPress zal vervolgens met behulp van de Transient API het updaten van de thema’s kenbaar maken aan de WordPress beheerders. De beheerder krijgen daardoor een melding te zien dat er updates beschikbaar zijn. Zodra de beheerder de update uitvoert zal de package URL gedownload worden en de thema bestanden vervangen worden. Hiervoor gebruikt WordPress onder andere de  Theme_Upgrader PHP klasse.

Categorieën
PHP WordPress

WordPress zoeken sorteren op relevantie en datum

De standaard zoekfunctie van WordPress is erg eenvoudig en beperkt. De zoekresultaten zijn standaard gesorteerd op datum. Het is niet mogelijk om deze te sorteren op bijvoorbeeld relevantie. Daarnaast is het ook niet mogelijk om fancy operators te gebruiken in je zoekopdracht zoals we gewend zijn bij andere zoekmachines. Gelukkig zijn er allerlei plugins die de zoekfunctie van WordPress uitbreid.

Ik wilde op een WordPress website de bezoekers de mogelijkheid geven om te sorteren op relevantie of op datum. Om dit mogelijk te maken heb ik ik de Relevanssi plugin van Mikko Saari (twitter: @msaari) geïnstalleerd en de volgende code toegevoegd aan de search.php template.

<?php 

$url = add_query_arg('s', get_search_query(), home_url('/'));

printf(
	__('ordered by %s | %s', 'text_domain') ,
	sprintf('<a href="%s">%s</a>',
		add_query_arg('orderby', 'relevance', $url) ,
		__('relevance', 'text_domain')
	) ,
	sprintf('<a href="%s">%s</a>',
		add_query_arg('orderby', 'post_date', $url) ,
		__('date', 'text_domain')
	)
);

?>
Categorieën
PHP WordPress

WordPress berichten inclusief bijlagen verwijderen

In mijn vorige blog kon je lezen hoe je WordPress berichten kunt verwijderen die geen gekoppelde media (attachments) hebben. In dit bericht laat ik een code snippet zien waarmee je berichten inclusief gekoppelde bijlagen kunt verwijderen. Als je een bericht binnen de WordPress beheerpaneel verwijderd blijven de gekoppelde bijlagen staan. Als je ook de gekoppelde bestanden wilt verwijderen kun je gebruik maken van de volgende code:

$query = new \WP_Query();
$query->query(array(
	'post_type' => 'project' ,
	'tax_query' => array(
		array(
			'taxonomy' => 'phase' ,
			'field' => 'slug' ,
			'terms' => 'test'
		)
	) ,
	'posts_per_page' => -1
));

while($query->have_posts()) {
	$query->the_post();

	$postId = get_the_ID();

	echo $postId, ' - ', get_the_title(), '<br />';

	$attachmentsQuery = new \WP_Query();
	$attachmentsQuery->query(array(
		'post_type' => 'attachment' ,
		'post_status' => 'inherit' ,
		'post_parent' => $postId
	));

	while($attachmentsQuery->have_posts()) {
		$attachmentsQuery->the_post();

		$attachmentId = get_the_ID();

		echo '- ', $attachmentId, ' - ', get_the_title(), '<br />'; 

		$result = wp_delete_attachment($attachmentId, true);
	}

	$result = wp_delete_post($postId, true);
}

Je kunt uiteraard de WP_Query->query() parameters helemaal naar wens aanpassen.

Categorieën
PHP WordPress

Verwijder WordPress berichten zonder bijlagen

Bij Pronamic zetten we regelmatig websites om naar WordPress. We maken hierbij gebruik van verschillende importeer technieken. Helaas gaat er bij het importeren ook wel eens iets fout. Zo gebeurd het soms dat de bijlagen bij een bericht niet goed worden overgenomen. We moeten dan alle berichten zonder gekoppelde bijlagen verwijderen. Binnen het beheerpaneel van WordPress is dit niet eenvoudig te realiseren. Daarom hebben we een simpel scriptje ontwikkeld waarmee we deze berichten kunnen verwijderen.

$query = new WP_Query();
$query->query(array(
	'post_type' => 'post' ,
	'posts_per_page' => -1
));

while($query->have_posts()) {
	$query->the_post();

	$id = get_the_ID();

	$attachmentsQuery = new \WP_Query();
	$attachmentsQuery->query(array(
		'post_type' => 'attachment' ,
		'post_status' => 'inherit' ,
		'post_parent' => $id ,
		'posts_per_page' => -1
	));

	if($attachmentsQuery->post_count == 0) {
		wp_delete_post($id, true);
	}
}
Categorieën
PHP WordPress

WordPress media bestanden met rare tekens

Onlangs hebben we een nieuwe WordPress website gelanceerd bij Pronamic. De opdrachtgever heeft deze website netjes gevuld op de ontwikkel- en testomgeving van Pronamic. Vervolgens hebben we deze verplaatst naar de productieomgeving. Hierbij liepen we echter tegen een probleem aan. Veel geüploade bestanden hadden in de bestandsnaam rare tekens, zoals copyright (©) tekens. Op de ontwikkel- en testomgeving leverde dit geen problemen op, maar helaas op de productieomgeving wel.

Aangezien het om een flink aantal bestanden ging was het handmatig aanpassen of opnieuw uploaden niet een optie. Ik kon helaas ook geen geschikte WordPress plugin vinden die dit probleem kon verhelpen. Om die reden heb ik zelf maar even een oplossing ontwikkeld. Allereerst heb ik een MySQL query bedacht die alles © tekens vervangt met een gewone c teken.

UPDATE wp_postmeta SET meta_value = REPLACE(meta_value, '©', 'c') WHERE meta_key = '_wp_attached_file';
UPDATE wp_postmeta SET meta_value = REPLACE(meta_value, '©', 'c') WHERE meta_key = '_wp_attachment_metadata';
UPDATE wp_posts SET guid = REPLACE(guid, '©', 'c') WHERE post_type = 'attachment';

Vervolgens moesten de bestanden in de WordPress uploads mag ook gewijzigd worden. Hiervoor heb ik eenvoudig PHP script geschreven en uitgevoerd in de uploads map:

<?php

$rdi = new RecursiveDirectoryIterator('./');
$rii = new RecursiveIteratorIterator($rdi);

foreach($rii as $file) {
	$name = $file->getPathname();

	$copyrightPosition = strpos($name, '©');

	if($copyrightPosition !== false) {
		$newName = str_replace('©', 'c', $name);

		$renamed = rename($name, $newName);

		echo $name, ' = ', $newName, ' = ', ($renamed ? 'renamed' : 'failed'), '<br />';
	}
}

Uiteindelijk heb ik voor de zekerheid alle afbeelding en bijbehorende thumbnails opnieuw laten generen met de Regenerate Thumbnails plugin. Mocht je ook ooit een dergelijke probleem hebben dan hoop ik dat je met bovenstaande code fragmenten dit snel kunt oplossen. Eventueel kan Pronamic ook een plugin voor je ontwikkelen die geautomatiseerd je probleem kan verhelpen. Mocht je nog vragen, opmerkingen en/of tips hebben laat dan gerust een reactie achter.

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
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
WordPress

WordPress Shopp plugin vertalingen (nl_NL)

Voor een opdracht bij Pronamic hebben we gebruik gemaakt van de WordPress Shopp plugin. Deze plugin tovert elke WordPress installatie om naar een eenvoudige webwinkel.
De plugin is ontwikkeld door Ingenesis Limited (weblog) een bedrijf uit Springfield (Ohio, Verenigde Staten).

De plugin is standaard alleen in het Engels beschikbaar. Gelukkig kan de plugin dankzij het WordPress vertaalmechanisme eenvoudige worden vertaald. Hiranthi
van Illutic WebDesign uit Enschede heeft al een Nederlandse vertaling op de Shopp website geplaatst.

Helaas was deze vertaling ietwat gedateerd en ontbrak het .PO bestand.
Gelukkig kunnen .MO bestanden eenvoudig omgezet worden naar .PO bestanden.
Als je gebruik maakt van Poedit kun je hiervoor
het volgende commando gebruiken:

C:\Program Files\Poedit\bin
msgunfmt Shopp-nl_NL.mo > Shopp-nl_NL.po

Vervolgens kun je binnen Poedit dit .PO bestand bijwerken naar het
recentste .POT bestand van de Shopp plugin.

Voor het gemak heb ik de .PO en .MO bestanden online gezet:

Helaas zijn de vertalingen nog niet volledig en up-to-date:

67% vertaald, 1256 teksten (216 onzeker, 0 ongeldige symbolen, 188 niet vertaald