Clientseitige Vorschau mit Plupload

Plupload bietet seit Version 2 die Möglichkeit, eine Vorschau der ausgewählten Dateien bereits vor dem eigentlich Upload, also beispielsweise in der Warteschlange, anzuzeigen.

Leider ist nicht wirklich dokumentiert, welche Methoden man verwenden muss, damit die Magie stattfindet.
Das jQuery UI Queue Widget unterstützt allerdings glücklicherweise Vorschaubilder, wodurch es nicht allzu schwer ist den entsprechenden Abschnitt im Quelltext zu finden.

Ich habe das mal zusammengefasst:

<div id="container">
    <a id="pickfiles" href="javascript:;">[Select files]</a> 
    <div id="preview"></div>
</div>
var uploader = new plupload.Uploader({
	runtimes : 'html5,flash',
	browse_button : 'pickfiles',
	container: 'container',
	url: "upload.php",
	max_file_size : '10mb',
	filters : [
		{title : "Image files", extensions : "jpg,gif,png"}
	],
	flash_swf_url : 'Moxie.swf'
});

uploader.init();

uploader.bind('FilesAdded', function(up, files) {
    $.each(files, function(){
        
		var img = new mOxie.Image();

		img.onload = function() {
			this.embed($('#preview').get(0), {
				width: 100,
				height: 100,
				crop: true
			});
		};

		img.onembedded = function() {
			this.destroy();
		};

		img.onerror = function() {
			this.destroy();
		};

		img.load(this.getSource());        
        
    });
});

Entscheidend ist die Verwendung von m0xie.Image. Diese Klasse bietet die Methode
embed() an, welche einem die ganze Arbeit abnimmt. Denn damit das Ganze in allen Browsern funktioniert, muss schon einiger Aufwand betrieben werden. Diese Methode fügt automagisch eine Vorschau in den gewünschten Ziel-Container ein. In modernen Browsern ist das eine Canvas, in anderen ein Data-URI oder Flash.

Dem Konstruktor der Klasse Image muss eine Datenquelle, also die gerade hinzugefügte Bilddatei, übergeben werden. Hierfür bietet die Klasse File die Methode getSource() an.

Ein funktionierendes Beispiel gibts hier: http://jsfiddle.net/Ec3te/2/
Ich habe dazu auch was auf StackOverflow geschrieben: http://stackoverflow.com/questions/17339899/plupload-html5-preview-after-fileselect

PHPUnit mit Netbeans unter Cygwin

Eine kleine Anleitung und ein paar Scripts um die in Netbeans integrierte PHPUnit-GUI unter Cygwin zum Laufen zu bekommen.

Software:

  • Windows 7
  • Cygwin
  • NetBeans 7.3.1
  • PHP 5.4.11
  • PHPUnit 3.7.21

Ich gehe davon aus, dass Windows, Cygwin (c:\cygwin) und NetBeans bereits installiert sind.

PHP ist momentan nicht im Haupt-Repository von Cygwin enthalten. Es lässt sich aber leicht über apt-cyg installieren.
Wurde apt-cyg installiert (siehe Projekt-Seite für eine Anleitung), kann es verwendet werden um über Cygwinports PHP zu installieren.

apt-cyg -m ftp://ftp.cygwinports.org/pub/cygwinports/ install php

Cygwinports bietet auch eine Reihe von PHP-Erweiterungen an. Hier gibt es eine Liste aller verfügbaren Pakete.

Jetzt sollte der Befehl php auf der Shell verfügbar sein.

Kleine Anmerkung zwischendurch: Ich verwende PHP unter Cygwin übrigens auch im Apache, der auch über Cygwin installiert wurde, und setze nicht mehr auf Distributionen wie XAMPP und co.

Als nächstes installieren wir PHPUnit als PHAR-Datei:

mkdir /usr/share/phpunit
cd /usr/share/phpunit
wget http://pear.phpunit.de/get/phpunit.phar
chmod +x phpunit.phar

# Optional, aber praktisch
ln -s /usr/share/phpunit/phpunit.phar /usr/bin/phpunit

Wurde der letzte optionale Befehl ausgeführt, könnte man seine PHPUnit-Testcases schon jetzt auf der Shell mit dem Befehl phpunit ausführen.

Da wir aber die GUI von NetBeans verwenden wollen, müssen ein paar Dinge beachtet und ein Problem umgangen werden.

Zunächst müssen wir für NetBeans, das ja unter Windows läuft, eine bat-Datei unter /usr/share/phpunit/phpunit.bat anlegen:

@echo off
c:\cygwin\bin\php.exe /usr/share/phpunit/phpunit_cygwin %*

Nicht vergessen, die Datei als ausführbar zu setzen:

chmod +x /usr/share/phpunit/phpunit.bat

Wie zu sehen ist rufen wir über PHP eine Script-Datei phpunit_cygwin auf. Diese Datei ist entscheidend. Sie dient als Wrapper um die Pfadangaben zwischen Windows (NetBeans) und Linux (Cygwin) zu konvertieren.

Die Datei /usr/share/phpunit/phpunit_cygwin enthält folgenden Inhalt:

#!/usr/bin/php
<?php

array_shift($argv);

array_walk($argv, function(&$a){

    if(!preg_match('/(.*)([a-z]:\\\\.+)/i', $a, $matches)){
        return;
    }

    $a = $matches[1].'"'.exec('/usr/bin/cygpath -a "'.$matches[2].'"').'"';

});

$args = implode(' ', $argv);

system('/usr/bin/php /usr/share/phpunit/phpunit.phar '.$args);

Es bietet sich an auch diese Datei ausführbar zu setzen:

chmod +x /usr/share/phpunit/phpunit_cygwin

Was passiert hier? NetBeans arbeitet und kennt unter Windows nur das DOS-/Windows-Pfadformat. Da PHP und damit auch PHPUnit unter Cygwin (Linux) ausgeführt werden, müssen die Pfade in ihr entsprechendes Cygwin-Gegenstück konvertiert werden.

Hierzu liefert Cygwin bereits ein Tool namens cygpath mit.
Mein PHP-Script kümmert sich darum, dass jeder übergebene Parameter in das UNIX-Pfadformat konvertiert wird, falls der Parameter auch wirklich einen Windows-Pfad enthält.

Danach wird PHPUnit mit den korrigierten Parametern aufgerufen.

Abschließend müssen wir noch NetBeans einrichten:

PHPUnit-Konfiguration unter NetBeans

Das Ergebnis zeigen folgende Screenshots:

Test-Ergebnis

Ausgabe von PHPUnit

PHP: Aktionen im Hintergrund ausführen

Hi,

ich habe heute etwas an der Performance meines Systems geschraubt.

Vor allem der integrierte PHP-Piwik-Tracker sorgte hier für messbar längere Ladezeiten meiner Seiten, da dieser natürlich erst eine Verbindung zum Piwik-Server aufbauen muss etc.

Meine Idee war also die HTML-Ausgaben an den Client zu schicken, danach die Verbindung zum Client zu trennen und anschließend erst den Seitenaufruf auf dem Server zu tracken, ohne dass der Client etwas davon mitbekommt.

Meine endgültige, funktionierende Lösung:

<?php

// Output-Buffering starten
ob_start();

// Seiteninhalt
echo '<html>... stuff ...</html>';

register_shutdown_function(function(){

	// Client kann Vorgang nicht abbrechen
	ignore_user_abort(true);
	
	// Verbindung schließen
	header('Connection: close');
	header('Content-Length: '.ob_get_length());
	
	// Alle Buffer löschen und ausgeben
	while(@ob_end_flush());
	
	// Daten an den Browser schicken
	flush();
	
	// Besuch tracken
	Piwik::track();
	
	// Session beenden
	session_write_close();
	
});

Geholfen hat mir: http://stackoverflow.com/questions/138374/close-a-connection-early

Patch für Fancybox 1.3.4 für jQuery 1.9+ Support

Kurz und knapp. Habe gerade nen Patch geschrieben, damit Fancybox 1.3.4 auch mit jQuery 1.9+ noch funktioniert.

--- jquery.fancybox-1.3.4.js.orig   2010-11-11 23:31:54.000000000 +0100
+++ jquery.fancybox-1.3.4.js    2013-03-22 23:25:29.996796800 +0100
@@ -26,7 +26,9 @@

        titleHeight = 0, titleStr = '', start_pos, final_pos, busy = false, fx = $.extend($('<div/>')[0], { prop: 0 }),

-       isIE6 = $.browser.msie && $.browser.version < 7 && !window.XMLHttpRequest,
+       isIE = !+"\v1",
+       
+       isIE6 = isIE && window.XMLHttpRequest === undefined,

        /*
         * Private methods 
@@ -322,7 +324,7 @@
            loading.hide();

            if (wrap.is(":visible") && false === currentOpts.onCleanup(currentArray, currentIndex, currentOpts)) {
-               $.event.trigger('fancybox-cancel');
+               $('.fancybox-inline-tmp').trigger('fancybox-cancel');

                busy = false;
                return;
@@ -389,7 +391,7 @@
                        content.html( tmp.contents() ).fadeTo(currentOpts.changeFade, 1, _finish);
                    };

-                   $.event.trigger('fancybox-change');
+                   $('.fancybox-inline-tmp').trigger('fancybox-change');

                    content
                        .empty()
@@ -612,7 +614,7 @@
            }

            if (currentOpts.type == 'iframe') {
-               $('<iframe id="fancybox-frame" name="fancybox-frame' + new Date().getTime() + '" frameborder="0" hspace="0" ' + ($.browser.msie ? 'allowtransparency="true""' : '') + ' scrolling="' + selectedOpts.scrolling + '" src="' + currentOpts.href + '"></iframe>').appendTo(content);
+               $('<iframe id="fancybox-frame" name="fancybox-frame' + new Date().getTime() + '" frameborder="0" hspace="0" ' + (isIE ? 'allowtransparency="true""' : '') + ' scrolling="' + selectedOpts.scrolling + '" src="' + currentOpts.href + '"></iframe>').appendTo(content);
            }

            wrap.show();
@@ -912,7 +914,7 @@

        busy = true;

-       $.event.trigger('fancybox-cancel');
+       $('.fancybox-inline-tmp').trigger('fancybox-cancel');

        _abort();

@@ -957,7 +959,7 @@
            title.empty().hide();
            wrap.hide();

-           $.event.trigger('fancybox-cleanup');
+           $('.fancybox-inline-tmp, select:not(#fancybox-tmp select)').trigger('fancybox-cleanup');

            content.empty();


Patch zum Download
jquery.fancybox-1.3.4.js gepatcht zum Download

Siehe auch Stackoverflow: http://stackoverflow.com/a/15580985/1171368

Fork des Bounce-Plugins für Roundcube Webmail 0.8.x

Ich habe heute mal schnell nen kleinen Fork des Bounce-Plugins für Roundcube Webmail erstellt. Er ermöglicht die Nutzung des Plugins mit der neuen Version 0.8.x. Es werden beide standardmäßig enthaltenen Themes, das neue “larry” und das alte “classic” unterstützt.

Zu finden bei meinen GitHub Repos: https://github.com/sabl0r/rcplugin_bounce

Mac OS X: Zugriff auf Windows 7 Freigaben langsam

Gestern Abend wollte ich von einem iMac mit Mac OS X Lion (10.7.3) aus auf Ordnerfreigaben eines PCs mit Windows 7 Professional zugreifen.

Mac hat die Freigaben auch sofort gefunden und konnte diese mounten. Allerdings fiel sofort auf, dass die Auflistung der Dateien und Unterordner in den Freigaben langsam war. Als ich dann eine Datei kopieren wollte, stellte ich fest, dass es sogar extrem langsam war (ein paar kb/sek).

Als Gegencheck kopierte ich testweise mal eine Datei vom PC zum Mac. Dabei hatte ich volle Gigabit-Geschwindigkeit.

Nach längerer Suche und nachdem ich einige “Tipps” ausprobiert hatte, bin ich letztendlich auf die Lösung gestoßen.

Kurze Anleitung:
Systemsteuerung->Netzwerk- und Freigabecenter->Adaptereinstellungen ändern->Rechtsklick auf LAN-Verbindung->Konfigurieren->Erweitert

Dort “Abladung großer Übertragung V2″ deaktivieren.

Opera: CSS-Transitions für border-color

Opera (aktuell 11.62) hat ein Problem damit, CSS-Transitions auf die Eigenschaft border-color (und vermutlich auch andere) anzuwenden.

Funktioniert nicht

-o-transition: border-color 1s linear;

Demo: http://jsfiddle.net/gaby/bcn5c/1/

Funktioniert

-o-transition:
    border-top-color 1s linear,
    border-right-color 1s linear,
    border-bottom-color 1s linear,
    border-left-color 1s linear;

Demo: http://jsfiddle.net/ds5bM/

Dieses Problem ist in den aktuellen Snapshots von Opera 12 gefixt:

Our CSS Transitions support has also been updated. With CSS Transitions, rendering changes that normally happen instantly can be made to gradually happen over time instead. Among other things we have updated color transitions, enabled transitions on background positions, box shadows and more, and added the ability to transition through one or more abrupt steps.

Quelle: http://my.opera.com/desktopteam/blog/2012/03/26/html5-css-64bit

E-Mail-Validierung für IDN-Domains in HTML5, PHP und Javascript

Tach,

ich habe mich heute mal (wieder) informiert, wie man am besten E-Mail-Adressen auf korrektes Format validiert.

Die Situation

Bisher habe ich in PHP immer den mit Version 5.2.0 eingeführten E-Mail-Filter und in Javascript einen regulären Ausdruck verwendet:

PHP:

function checkEmail($email){
	return filter_var($email, FILTER_VALIDATE_EMAIL) !== false;
}

Javascript:

function checkEmail(email){
	return email.match(/^[-a-z0-9~!$%^&*_=+}{\'?]+(\.[-a-z0-9~!$%^&*_=+}{\'?]+)*@([a-z0-9_][-a-z0-9_]*(\.[-a-z0-9_]+)*\.(aero|arpa|biz|com|coop|edu|gov|info|int|mil|museum|name|net|org|pro|travel|mobi|[a-z][a-z])|([0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}))(:[0-9]{1,5})?$/i);
}

HTML5:

Auch HTML5 bietet die Möglichkeit, ein Eingabefeld des Typs E-Mail zu erzeugen:

<!DOCTYPE html>
<html lang="de">
    <head>
        <title>E-Mail-Validierung</title>
    </head>
    <body>   
        <form>
            <input type="email">
            <input type="submit">
        </form>   
    </body>
</html>

Demo für HTML5 und Javascript.

Das Problem

Das Problem ist nun, dass keine der drei Lösungen IDN-Domains bei E-Mail-Adressen akzeptiert (siehe [1] und [2]). Das heißt Adressen wie foo@bär.de, die durchaus gültig sind, werden als ungültig erkannt.

Die Lösung

Als Lösung muss man also den Domain-Teil in Punycode konvertieren, um danach das Format prüfen zu können.

PHP:

Für PHP kann man hierzu auf das Modul intl bzw. speziell auf die Funktion idn_to_ascii
zurückgreifen.

function checkEmail($email){
	$parts = explode('@', $email);
	return count($parts) == 2 && filter_var($parts[0].'@'.idn_to_ascii($parts[1]), FILTER_VALIDATE_EMAIL) !== false;
}

Javascript:

Für Javascript bin ich auf eine tolle Bibliothek von Mathias Bynens gestoßen. Damit lässt sich das Problem analog zu PHP lösen.

function checkEmail(email){
	var parts = email.split('@');
	if(parts.length != 2){
		return false;
	}
	var converted = parts[0] + '@' + punycode.toASCII(parts[1]);

	return converted.match(/^[-a-z0-9~!$%^&*_=+}{\'?]+(\.[-a-z0-9~!$%^&*_=+}{\'?]+)*@([a-z0-9_][-a-z0-9_]*(\.[-a-z0-9_]+)*\.(aero|arpa|biz|com|coop|edu|gov|info|int|mil|museum|name|net|org|pro|travel|mobi|[a-z][a-z])|([0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}))(:[0-9]{1,5})?$/i);
}

Das Fazit

Mit etwas mehr Aufwand ist es in PHP und Javascript also durchaus möglich IDN-E-Mail-Adressen korrekt zu erkennen. Meiner Meinung nach sollten die PHP-Entwickler ihren E-Mail-Filter erweitern, damit er IDN-Domains akzeptiert.

Für HTML5 gibt es dafür keine Lösung, solange die Browser-Entwickler es nicht berücksichtigen. Eingabefelder vom Typ “url” können übrigens mit IDN-Domains umgehen. Warum also nicht auch E-Mail-Felder?

Da davon auszugehen ist, dass sich “Umlautdomains” (wie man so schön sagt) in Zukunft weiter verbreiten werden, wäre es wichtig, dass Browser jetzt schon im vollen Umfang darauf vorbereitet wären.

[1] http://www.php.net/manual/en/filter.filters.validate.php#102398
[2] http://barrow.io/posts/email-validation-of-double-byte-domains/

Google Web Fonts – Mehrere Schriftstile im IE8 und älter laden

Die Google Web Fonts sind toll und funktionieren in der Regel auch sehr gut. Aber heute bin ich auf ein Problem im Internet Explorer 6, 7 und 8 gestoßen.

Definiert man in einer Anfrage mehrere Schriftstile gleichzeitig, dann liefert Google nur den Standard-Stil zurück, wenn man einen IE < = 8 benutzt.

<link href="http://fonts.googleapis.com/css?family=Yanone+Kaffeesatz:200,300" rel="stylesheet" />

Die Lösung ist trivial. Einfach separate Anfragen stellen:

<link href="http://fonts.googleapis.com/css?family=Yanone+Kaffeesatz:200" rel="stylesheet" />
<link href="http://fonts.googleapis.com/css?family=Yanone+Kaffeesatz:300" rel="stylesheet" />

Es gibt bei Google auch nen Bug-Report dazu. Der ist allerdings schon fast zwei Jahre alt und es ist wohl fraglich, ob der jemals behoben wird.

Danke an Richard Fink, dessen Blogpost mir sehr geholfen hat.