Einführung: Serendipity-Plugins schreiben
Was bringt die Rohform, wenn man nicht weiß, wie man etwas damit anfangen kann? Ich will hier zeigen, wie Serendipity-Plugins funktionieren und ein paar der typischen Probleme ansprechen. Gedacht ist das als verfrühtes kleines Weihnachtsgeschenk, für die langen und ruhigen Weihnachtsfeiertage ;)
Ein Wort zu PHP: Davor sollte man keine Angst haben. Es mag in manchen Kreisen als Skriptkiddie-Sprache gelten, warum auch immer. Wahrscheinlich spielt da rein, dass es nicht die schönste aller Sprachen ist, mit Semikolon-Zwang, der Mischung aus funktionaler und objektorientierter Programmierung und $-Variablenbezeichnung. Aber es tut seinen Job, und wer etwas Bash und eine beliebige objektorientierte Sprache kennt wird sich zurechtfinden. Bash mit Objekten beschreibt PHP ganz gut.
Diese Anleitung ist als Ergänzung/Erklärung zur umfassenden Auflistung der Plugin-API auf s9y.org zu verstehen.
Funktionsweise
Ein Plugin kann nur dort ansetzen, wo das Mutterprogramm es zulässt. Bei Serendipity sind dafür eine Vielzahl von Eintrittspunkten definiert, die Event Hooks (oder einfach Events). An bestimmten Punkten im Programmablauf, z.B. jedes Mal wenn im Adminbereich ein Kommentar gelistet wird, wird solch ein Event geworfen. Ein Plugin kann das Event fangen, die geworfenen Daten manipulieren und zurückgeben.
Das bedeutet aber nicht, dass keine echte eigene Funktionalität möglich ist. Ein Plugin kann problemlos eigene Seiten anlegen und auf denen machen, was es will.
Es muss noch unterschieden werden zwischen den Seitenleistenpugins (serendipity_plugin) und den Event-Plugins (serendipity_event). Letztere sind die "echten" Plugins mit voller Funktionalität. Die Seitenleistenplugins sind nur dazu da, Informationen in der Seitenleiste auszugeben und nicht dafür, alle möglichen Events zu fangen. Der grundsätzliche Aufbau ist aber gleich.
Aufbau
Ein Plugin ist ein PHP-Objekt, das von serendipity_event erbt. In den folgenden Abschnitten wird dann die Funktionalität eingebaut.
Definitionen
Die Funktion introspect(&$propbag) definiert die ganzen grundlegenden Eigenschaften des Plugins, inklusive den Konfigurationsoptionen und den genutzten Events. Was hier nicht angelegt wird, kann später nicht genutzt werden.
function introspect(&$propbag) { global $serendipity; $propbag->add('name', PLUGIN_EVENT_PLUGINNAME_NAME); $propbag->add('description', PLUGIN_EVENT_PLUGINNAME_DESC); $propbag->add('author', 'Your Name'); $propbag->add('version', '0.1'); $propbag->add('requirements', array( 'serendipity' => '0.8')); $propbag->add('event_hooks', array('frontend_display' => true)); $propbag->add('configuration', array('directblock')); }
Mittels $propbag->add() werden die verschiedenen Eigenschaften des Plugins definiert. Man sieht die ganzen grundlegenden Beschreibungen wie $propbag->add('name', 'PLUGIN_EVENT_PLUGINNAME_NAME'), wovon es natürlich noch einige weitere gibt. Wichtig und an sich selbsterklärend sind die Voraussetzungen zur Installation des Plugins.
Am allerwichtigsten ist jedoch das Hinzufügen der Events, die später genutzt werden sollen. $propbag->add('event_hooks', array('frontend_display' => true)) fügt den Event frontend_display hinzu, das Plugin soll also immer dann etwas tun, wenn eine normale Blogseite (wie dieser Artikel) aufgerufen wird.
Doch zuerst zur Konfiguration: $propbag->add('configuration', array('directblock')) fügt die Option 'directblock' hinzu, jedoch ohne anzugeben, was das für eine Art von Option ist. Das folgt im nächsten Abschnitt.
Konfiguration
Alle angelegten Optionen werden in der Funktion introspect_config_item($name, &$propbag) ausgewertet und mit Funktionalität versehen.
function introspect_config_item($name, &$propbag) { global $serendipity; switch($name) { case 'directblock': $propbag->add('type', 'boolean'); $propbag->add('name', PLUGIN_EVENT_PLUGIN_DIRECTBLOCK); $propbag->add('description', PLUGIN_EVENT_PLUGIN_DIRECTBLOCK_DESC); $propbag->add('default', false); break; default: return false; } return true; }
Mit der switch/case-Anweisung werden alle Optionen durchgegangen. Hier gibt es nur eine, directblock, also wird nur diese gefunden. Ihr wird ein Typ vergeben, der Typ Boolean, ein Name, Beschreibung und ein Standardwert. Je nach Typ wird in der grafischen Konfiguration ein anderes Objekt daraus, das hier wird ein Radiobutton mit "Nein" und "Ja" zur Auswahl.
Im folgenden Verlauf des Programms können die Optionen dann jederzeit mit $this->get_config('option', 'default') abgefragt werden, aber meistens wird es so sein, dass sie direkt bei der Abfrage der Events gebraucht werden - dort wird die Funktionalität spezifiziert.
Events
Um die Events kümmert sich die Funktion event_hook($event, &$bag, &$eventData, $addData = null). Ein Beispiel:
function event_hook($event, &$bag, &$eventData, $addData = null) { global $serendipity; $hooks = &$bag->get ( 'event_hooks' ); if (isset ($hooks[$event])) { switch ($event) { case 'external_plugin' : switch ($eventData) { case 'learncomment': //do something break; } return true; break; default : return false; break; } } else { return false; } }
Zuerst wird geguckt, ob überhaupt Events vorhanden sind. Wenn dem so ist und das Event 'external_plugin' (eines der wichtigsten, da freien Events, dazu später mehr) eintritt und dann dort der mitgegebene String '$eventData' 'learncomment' lautet, dann macht das Plugin irgendwas.
Statt 'external_plugin' hätte man auch das Event von oben, 'frontent_display', fangen können. An diesem sieht man etwas besser, wie $event_Data verwendet wird:
case 'frontend_display': foreach ($this->markup_elements as $temp) { if (serendipity_db_bool($this->get_config($temp['name'], true)) { $element = $temp['element']; $eventData[$element] = $this->_s9y_markup($eventData[$element]); } }
Das ist ein Auszug aus dem Code des s9ymarkup-Plugins, das standardmäßig in jedem Serendipity-Blog aktiviert ist. Wichtig ist hier nur, wie die Ausgabe angefasst wird. $eventData[$element] steht z.B. für 'body', verweist also auf den Blogtext eines Eintrags. Der wird manipuliert und dann wieder $eventData[$element] zugewiesen, also überschrieben. Und schon ist das Prinzip einer Auszeichnungssprache umgesetzt, das Plugin hat die eigentlichen Daten von Serendipity verändert.
Natürlich gibt es mehr als nur diese beiden Events.
Internationalisierung
Alle Texte, de ausgegeben werden sollen, können natürlich nicht einfach so ins Plugin geschrieben werden. Schließlich soll Serendipity nicht nur auf Englisch verfügbar sein. Daher die Nutzung der Variablen wie PLUGIN_EVENT_PLUGINNAME_NAME. Diese werden aus den Sprachdateien wie lang_de.inc.php eingebunden:
// Probe for a language include with constants. Still include defines later on, if some constants were missing $probelang = dirname(__FILE__) . '/' . $serendipity['charset'] . 'lang_' . $serendipity['lang'] . '.inc.php'; if (file_exists($probelang)) { include $probelang; } include dirname(__FILE__) . '/lang_en.inc.php';
In diesen Dateien kann dann der eigentliche Wert angegeben werden:
@define('PLUGIN_EVENT_PLUGINNAME_NAME', 'Spamschutz (Bayes)');
Typische Problemstellungen
Dateien bereitstellen
Viele Plugins haben Dateien, die sie im Laufe ihres Ablaufs benötigen. Das kann die Grafik sein, die neben einem Button angezeigt werden soll. Da wir nicht wissen, wo unser Plugin liegt, wissen wir vorher nicht, wie dieses Bild eingebunden werden kann. Daher fragen wir uns am besten selbst:
$serendipity['baseURL'] . 'index.php?/plugin/spamblock_bayes.spam.png'
Mit diesem Code lösen wir unser eigenes Event aus, das uns dann das Bild zurückgeben kann. Schlüssel hierzu ist das 'external_plugin'-Event:
switch ($event) { case 'external_plugin' : switch ($eventData) { case 'spamblock_bayes.spam.png': header('Content-Type: image/png'); echo file_get_contents(dirname(__FILE__). '/img/spamblock_bayes.spam.png'); break; } }
So kann zuverlässig das Bild ausgegeben werden. Mit dem gleichen Prinzip kann natürlich auch etwas anderes ausgegeben werden, beliebiges - dieses Event ist die Grundlage alle Ajax-Abfragen. Hiermit redet das Javacript, um Daten zu übermitteln oder zu bekommen.
Diese Methode hat jedoch einen Nachteil: Sie ist teuer, da wir ganz Serendipity nur dazu nutzen, eine mickrige Datei auszugeben. Und an sich wird sie nur selten gebraucht, die meisten Plugins liegen ja doch einfach unter /plugins/. Daher sollten Plugins eine 'path'-Option anbieten, die dann so genutzt wird:
$path = $this->get_config('path', ''); if (!empty($path) && $path != 'default' && $path != 'none' && $path != 'empty') { $path_defined = true; $imgpath = $path . 'img/'; } else { $path_defined = false; $imgpath = $serendipity['baseURL'] . 'index.php?/plugin/'; }
Statt dann blind auf den Event-Hook zu verweisen, nutzen wir den vorgegeben Pfad:
<img src="'. $imgpath . 'spamblock_bayes.spam.png" />
Abhängigkeiten
Es sind insbesondere Javascript-Frameworks wie jQuery, auf die andere Plugins aufbauen, wobei natürlich auch oft ein Event- mit einem Seitenleistenplugin gekoppelt wird. jQuery soll natürlich nicht von jedem Plugin, das dies benötigt, selbst eingebunden werden. Es gibt ein jQuery-Plugin, das dann in introspect(&$propbag) als Abhängigkeit eingetragen werden sollte:
$this->dependencies = array('serendipity_event_jquery' => 'remove');
Daten übermitteln und abfragen
Will man von JavaScript aus mit dem Server reden, bastelt man eine Ajax-Anfrage und redet mit 'external_plugin'. Dies sendet z.B. eine id (eines Kommentars) an das Plugin:
function spam(id) { if (window.XMLHttpRequest) { // Mozilla, Safari, Opera, IE7 httpRequest = new XMLHttpRequest(); } else if (window.ActiveXObject) { // IE6, IE5 httpRequest = new ActiveXObject("Microsoft.XMLHTTP"); } httpRequest.onreadystatechange = setMessage; lastID = id; httpRequest.open('POST', '<?php echo $serendipity ['baseURL'] . 'index.php?/plugin/learncomment'; ?>', true); httpRequest.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded; charset=<?php echo LANG_CHARSET; ?>'); httpRequest.send('id='+id+'&category=spam'); // Start request setLoadIndicator(id); }
Um dann die id zu holen greift man auf die üblichen PHP-Mittel zurück:
switch ($event) { case 'external_plugin' : switch ($eventData) { case 'learncomment': if (!serendipity_checkPermission('adminComments')) { break; } $category = $_REQUEST ['category']; $id = $_REQUEST ['id']; ....
Falls etwas dem Javascript zurückgegeben wird, kann das mit einem echo ausgegeben werden und in setMessage() gefangen werden:
function setMessage() { if (httpRequest.readyState == 4 && httpRequest.status == 200) { var response = httpRequest.responseText; } }
Schlusswort
Natürlich gibt es noch viel mehr zu zeigen und zu erklären. Genauso kann es Dinge geben, die hier falsch oder nicht optimal gezeigt wurden oder Dinge, die einfach fehlen. Dann freue ich mich über einen Hinweis.
Generell gilt der Ratschlag von s9y.org: Wenn etwas unklar ist, einfach mal in ein anderes Plugin schauen und gucken, wie es dort gemacht wurde.
Google: Open Systems Win
Google erschlägt gerade in einem (durchaus werbeähnlichen, aber inhaltlich wertvollen) Blogbeitrag (via) den ganzen Themenkomplex um Googles Kontroll- und Datenkrakenvorwürfen.
Today's open source goes far beyond the "patent pooling" of the early auto manufacturers, and has led to the development of the sophisticated software components — Linux, Apache, SSH, and others — upon which Google is built. In fact, we use tens of millions of lines of open source code to run our products. We also give back: we are the largest open source contributor in the world, contributing over 800 projects that total over 20 million lines of code to open source, with four projects (Chrome, Android, Chrome OS, and Google Web Toolkit) of over a million lines of code each. We have teams that work to support Mozilla and Apache, and an open source project hosting service (http://code.google.com/hosting) that hosts over 250,000 projects. These activities not only ensure that others can help us build the best products, they also mean that others can use our software as a base for their own products if we fail to innovate adequately.
Dabei benutzt Google die Apache-Lizenz, die der BSD-Lizenz entspricht und GPL 3-kompatibel ist. Im Kampf zwischen proprietärer und freier Software steht Google auf unserer Seite.
Der Hauptvorwurf gegen Google ist aber nicht der in Bezug auf die Freiheit der Software, sondern der in Bezug auf die Kontrolle über die Informationen. Welchen Schaden könnte ein allwissender und allkontrollierender Internetkonzern mir Googles Ausmaßen auch anrichten!
Aber schaut man sich an, was Google sagt und wie Google handelt, sind die Sorgen da noch berechtigt?
Finally, we must always give control to the user. If we have information about a user, as with IBA, it should be easy for the user to delete that information and opt-out. If they use our products and store content with us, it's their content, not ours. They should be able to export it or delete it at any time, at no cost, and as easily as possible. Gmail is a great example of this since we offer free forwarding to any address. The ability to switch is critical, so instead of building walls around your product, build bridges. Give users real options.
Google macht das nicht aus Altruismus, immerhin ist ein kapitalistischer Großkonzern. Es ist an sich (für uns) viel besser: Es ist ein Geschäftsprinzip.
Reward = (Total value added to the industry) * (Our share of industry value)
Diese Sicht auf den eigenen Gewinn ist es, der Google dazu bringt, eben nicht unser Feind zu sein. Als großer und mächtiger Mitspieler in einem offenen und florierenden System sind sie es, die von dem Wachstum profitieren. Und genau das stimmt für Google ja auch: Ohne ein lebendiges Internet würde die Suchmaschine kaum genutzt, ohne Nutzung der Suchmaschine könnte die Werbung nicht dort platziert bzw auf die gefundenen Seiten gebracht werden, bzw würden es weniger Leute sehen. Google hat und muss ein Interesse daran haben, offene Systeme mit Kontrollmöglichkeiten der Nutzer zu schaffen.
Genau hier liegt auch der Punkt, der unsere Medienindustrie dazu bringt, Google als Feind zu betrachten (und warum Apple und Google gebrochen haben):
Closed systems are well-defined and profitable, but only for those who control them. Open systems are chaotic and profitable, but only for those who understand them well and move faster than everyone else.
Damit schreibt Google mal eben die kapitalistische Wirtschaftstheorie um und unterstützt dabei freie Software und offene Systeme gegen die Kontrolleure. Soviel zu "Google is evil".
Rohform eines Serendipity-Plugins
Die Rohform eines Serendipity-Event-Plugins benötigt man immer wieder. Natürlich kann man dafür eines der kleineren Plugins wie s9ymarkup nehmen und dann rauslöschen, was man nicht braucht. Aber im Grund läuft es auf diesen Code hinaus:
<?php if (IN_serendipity !== true) { die ("Don't hack!"); } @serendipity_plugin_api::load_language(dirname(__FILE__)); class serendipity_event_pluginname extends serendipity_event { var $title = PLUGIN_EVENT_PLUGINNAME_NAME; function introspect(&$propbag) { global $serendipity; $propbag->add('name', PLUGIN_EVENT_PLUGINNAME_NAME); $propbag->add('description', PLUGIN_EVENT_PLUGINNAME_DESC); $propbag->add('stackable', false); $propbag->add('author', 'Your Name'); $propbag->add('version', '0.1'); $propbag->add('requirements', array( 'serendipity' => '0.8' )); $propbag->add('event_hooks', array('frontend_display' => true)); $propbag->add('groups', array('MARKUP')); } function generate_content(&$title) { $title = $this->title; } /*function introspect_config_item($name, &$propbag) { }*/ function event_hook($event, &$bag, &$eventData, $addData = null) { global $serendipity; $hooks = &$bag->get('event_hooks'); if (isset($hooks[$event])) { switch($event) { case 'frontend_display': //do something return true; break; default: return false; } } else { return false; } } function debugMsg($msg) { global $serendipity; $this->debug_fp = @fopen ( $serendipity ['serendipityPath'] . 'templates_c/pluginname.log', 'a' ); if (! $this->debug_fp) { return false; } if (empty ( $msg )) { fwrite ( $this->debug_fp, "failure \ " ); } else { fwrite ( $this->debug_fp, print_r ( $msg, true ) ); } fclose ( $this->debug_fp ); } } /* vim: set sts=4 ts=4 expandtab : */ ?>
Spamblock-Bayes 0.2: Update in Spartacus
Die neue Version wird erst im Laufe der Zeit alle Funktionen aktivieren - genauer, mit Serendipity 1.6. Das ist zwar noch etwas hin, aber es ist ja nicht so, als wären gar keine Änderungen enthalten, die man sofort bemerken könnte.
Die Kontrollen wurden in die normale Kommentarseite integriert, sodass kein eigener Menüpunkt dafür mehr vorhanden ist. Das schöne daran: Es spart Wartungsarbeit, und die Kontrolle über die Kommentare ist an einem Platz.
Bei den Kommentaren wird nun der Spamfaktor angezeigt. Bei einer Version vor 1.6 ist diese Anzeige unten bei den Kontrollen. Wenn die überarbeitete Kommentarseite integriert wird, wechselt die Position in die obere rechte Ecke.
Bei den Arbeiten daran ist mir aufgefallen, dass das Berechnen dieses Wertes bei bestimmten Kommentaren einen Fehler warf, das wurde behoben.
Mit Serendipity 1.6 wird die Email verändert, und zwar wird sie ergänzt durch Links, um Kommentare direkt zu löschen und als Spam zu bewerten oder sie zu bewilligen und als validen Kommentar zu lernen. Im Gegensatz zur Kommentarseite ist der Code dafür in 1.6 schon integriert.
PS: An der Performance der Ratingabfragen wurde massiv gearbeitet - und es wird wahrscheinlich auch noch weitere Verbesserungen geben.
Serendipity 1.5 draußen
Garvin hat gerade die Version 1.5 veröffentlicht. Was gibts neues? Sha1 für Logins bedeutet mehr Sicherheit, Xinha (der WYSIWYG-Editor) ist an die eigenen Vorstellungen anpassbar und Templates können Plugins in sich integrieren, wenn sie wollen, außerdem sollte PHP 5.3 weniger Probleme verursachen - das sind die Hauptänderungen.
Was man im Betrieb nicht unbedingt sofort bemerken sollte, aber einfach die Nutzung erleichtert, sind die kleinen Verbesserungen:
- Der Adminbereich vergibt Seitentitel, sodass im Tab nicht immer nur "Serendipitys Verwaltungsoberfläche", sondern z.B. "Kommentare - Serendipitys Verwaltungsoberfläche" steht.
- Plugins scrollen die Seite, wenn man sie an den oberen oder unteren Bildschirmrand zieht.
- Die Kommentarseite hat auch unten eine Navigation, und Thema Nutzerführung:
- Wurde eine Aktion in der Mediendatenbank durchgeführt landet man nicht mehr auf einer quasi leeren Seite, sondern in der Gallerie und bekommt die Meldungen dort zu sehen.
- Die Suchfunktion findet bei der Suche nach "Haus" nun auch direkt "Haus"meister, wenn die normale Suche zu wenige Ergebnisse liefert - und die Sortierung ist anpassbar, es muss nicht mehr nur nach Datum geordnet sein, sondern das Ordnen nach Relevanz ist möglich.
Es gibt noch viele andere große und kleine Änderungen. So können sich mehrere Installationen die gleiche Domain teilen, da sie jeweils eigene Sessions zugewiesen bekommen (was das in der Praxis bedeutet, weiß ich nicht) und das Bookmarklet funktioniert mit Chrome und ... - die Liste der Änderungen ist unheimlich lang.
Ach, und nebenbei: Mit 1.5 kann man das commentedit-Plugin aktivieren. Ein eigenes neues Lieblingsfeature schon entdeckt?
Performanceprinzip: Gemeinsam statt einzeln
Häufig baut man bei einem neuen Programm erst eine Funktion, die eine Aktion durchführt. Klappt das mit dem Beispiel, ruft man diese jeweils mit jedem Eingabewert auf. Die Funktion getRating(id) z.B. würde einen Wert abfragen und weiterleiten, und klappt das mit der Testid, kommt ein getAllRatings() dazu, das erstmal alle ids sammelt und dann getRating aufruft:
function getAllRatings() { ... for(var i=0; i<ids.length;i++) { getRating(ids[i]); } }
Ein an sich sinnvoller Weg, um die Teilprobleme Schritt für Schritt zu lösen. Nur: Es kann der unperformante Ansatz sein.
getRating sei nun eine Funktion, die per httpRequest mit einem Server redet, den zugehörigen Kommentar zur id aus der Datenbank holt und schließlich die Bewertung (den Spamfaktor) des Kommentars berechnen lässt. Für jede id, also schlimmstenfalls für jeden Kommentar, der je im Blog abgespeichert wurde, wird bisher jede dieser Aktionen einzeln durchgeführt. Bei der Datenbankabfrage und auch bei der Serverkommunikation ist das äußerst ungünstig, da sich so die Wartezeiten addieren bzw. die Datenbank die Abfrage nicht optimieren kann.
Deshalb besser:
function getAllRatings() { ... getRating(ids); }
Wobei getRating nun erkennen muss, dass der Parameter id ein Array ist und es dieses in einem Schwung zum Server geben kann:
function getRating(id) { ... if (id.constructor == Array) { id = id.join(';'); } httpRequest.send('id='+id); }
Auf der Serverseite die ids gesammelt der Datenbank zu geben ist dann kein Problem mehr. Überhaupt ist hier die Umsetzung nicht das Problem und den meisten Programmierern die Mechanismen dahinter auch klar: Ajax-Aufrufe und die Datenbankabfragen minimieren. Trotzdem ist es für einen von "divide and conquer" geprägten Programmierer ein Schritt zu erkennen, dass der an sich logisch aufgebaute Code hier nochmal umgebaut werden sollte.
getElementByClass - Javascript
jQuery mag das können, aber mir erscheint es gerade nicht passend, das einzubinden. Daher manuell und zum Nachschlagen:
function getElementByClass(class) { var allHTMLTags = document.getElementsByTagName("*"); classes = new Array(); var length = allHTMLTags.length; for (var i=0; i < length; i++) { if (allHTMLTags[i].className.indexOf(class) != -1) { classes.push(allHTMLTags[i]); } } return classes; }
Das indexOf(class) müsste man eigentlich mit einem regulären Ausdruck ersetzen, damit die Suche nach 'test' nicht bei 'tester' anschlägt. Leider funktionieren diese Ausdrücke in diesem Fall bei mir nicht, auch nicht die von Beispielen. Aufbauend via.
Spamblock-Bayes: Kommentare auf der Kommentarseite einordnen
Das manuelle Einordnen der Kommentare im Spamblock-Bayes-Plugin lief ja bisher über eine eigene Oberfläche, die einfach die abgespeckte Kommentarseite mit anderen Buttons war. Es wurde mehrfach gewünscht, die Funktion mit der normalen Kommentarseite zu vermischen und dort einfach die zusätzlichen Buttons einzubauen. Genau das macht die neue Version, die ich bei ausbleibenden Problemberichten in ein paar Tagen in Spartacus hochladen werde. Außerdem wird das Lernen weniger fehleranfällig, weil der Kommentar nicht mehr von der Kommentarseite eingelesen, sondern direkt aus der Datenbank geholt wird.
Was noch nicht gelöst ist, ist das Anzeigen, ob ein Kommentar schon bewertet wurde. Ich musste mehrmals erklären, dass das an sich keine gute Idee ist, weil Kommentare ja initial schon bewertet sein können bzw man sie mehrfach bewerten kann, um den einzelnen Wörtern einen höheren Spamfaktor zuzuordnen. Dass dies nicht sofort klar ist liegt natürlich daran, dass nicht jedem Nutzer klar sein kann, wie der Spamfilter funktioniert. Die in diesem Wunsch resultierende Unsicherheit auszuräumen wäre natürlich toll. Meine Lösungsidee: Man könnte auf der Kommentarseite bei jedem Kommentar die derzeitige Bewertung anzeigen. Bei einer neuen Bewertung sollte dieser Wert sich direkt erhöhen bzw verringern. Nebenan sollte ein kleiner Hilfeknopf stehen, der auf einen Text verweist, der die Bedeutung dieser Zahl erklärt. Würde das die Funktionsweise deutlicher machen?
Banner der Nachdenkseiten
Die Nachdenkseiten sind wichtig. Um besser auf sie hinzuweisen, haben sie jetzt Banner zum Einbinden freigegeben:
Chrome: Autoscroll, Vor/Zurück und PDF
Chromes Beta unter Linux hat durchaus noch Probleme, die ich hier nicht ganz unerwähnt lassen möchte.
Autoscroll
Autoscroll per mittlerer Maustaste ist unter Linux einfach nicht dabei. Glücklicherweise ist es mit einem Addon nachrüstbar.
PDFs
Leider stolpert man im Netz immer wieder über PDFs. Diese werden von Chrome scheinbar nicht mit dem PDF-Programm angezeigt, sondern können nur heruntergeladen werden. Als Workaround kann eine Erweiterung dienen, die die PDFs per Google Docs anzeigt.
Vor/Zurück per Maus
Mit der vierten und fünften Maustaste zurückzugehen (bzw. vor) scheint ebenfalls problematisch zu sein. Das geht zwar eigentlich, nicht aber, wenn man für Firefox die Buttons 8 und 9 mit 6 und 7 vertauscht hatte. Das scheint aber auch für Firefox nicht mehr nötig zu sein, per
xmodmap -e "pointer = 1 2 3 4 5 6 7 8 9"
rückzuwechseln auf die Standardeinstellungen behebt diesen "Fehler".
Doktorarbeit eines Kreationisten
Doktorarbeit von Kent E. Hovind, laut Wikileaks ein bekannter Kreationist, die Arbeit ist das Papier nicht wert, von dem sie abfotografiert wurde. Highlights?
Evolution is purely a religion. There is no scientific evidence ar all to back up any form of macro-evolution.
For instance, if we released hundreds of rabbits in an area with cold winters, only the animals with the heavier fur would survive. So within a few years, the population would have a little heavier fur than the earlier populations. ... . There has been no change in the genetic material of the rabbit.
Satan is the mastermind behind this false doctrine.
Another evidence that the earth is stilly young is the fact that there are still meteors and comets flying through.
Chrome Beta unter Linux
Gestern wurde die Beta-Version von Google Chrome für Linux freigegeben. Den Browser hatte ich mir kurz angeguckt, als er für Windows herauskam, wobei ich ihn vielversprechend fand. Nun ist die Entwicklung deutlich weiter, was ein Grund ist, sich die Linuxversion genauer anzuschauen.
Konzept
Gleichgeblieben ist das Konzept des Browsers. Es ist immer noch ein schneller Browser mit schlanker Oberfläche, mit all den sinnvollen Brüchen vom bisherigen Konzept wie der Integration der Such- in die URL-Leiste oder dem Anzeigen dieser unter der Tableiste. Trotzdem gibt es ein paar Anpassungen für die Linuxversion: So ist es beispielsweise möglich, das GTK-Design zu übernehmen und der Firefox-Import funktioniert.
Google ist die Standardsuchmaschine und wird auch für URL-Vervollständigung genutzt. Beim ersten Start wird jedoch nachgefragt, ob wirklich Google der Standard sein soll. Vorbildlich. So wie auch die sonstigen Datenschutzeinstellungen, ein eigener Unterpunkt im Menü.
Es bleibt dabei, dass Chrome ein paar neue Ideen mitbringt.
Geschwindigkeit
Firefox wurde für seine (Javascript-)Performance unter Linux immer wieder gescholten. Ein kurzer Vergleich unter Hardy(!) zwischen dem dortigen Standard Firefox 3.0.15 bestätigt die Geschwindigkeit Chromes:
Ein fairer Vergleich wäre jedoch einer zwischen Chrome und der aktuellen Firefoxversion. Subjektiv fühlt sich Chrome im normalen Betrieb sehr schnell an.
Erweiterungen
Chrome war anfangs kein vollwertiger Ersatz zu Firefox, schon weil viele Funktionen fehlten, die der Fuchs durch sein Erweiterungssystem erhält. Dies gilt nicht mehr. Das Erweiterungssystem ist integriert und wird genutzt, so sind alle für mich unabdingbaren Erweiterungen (Mausgesten, Adblocker und PwdHash) verfügbar. Ganz den Kinderkrankheiten entwachsen ist das System nicht, so funktioniert nicht jede Erweiterung auch unter Linux und sind diese auch generell noch nicht so zuverlässig wie die Firefox-Pendants. Aber die notwendigen Funktionen werden nachgerüstet, womit Chrome endgültig ein vollwertiger Browser ist.
Fazit
Chrome ist nicht länger nur ein halber Firefoxersatz. Mit dem Schritt auf alle Plattformen und dem gelungenen Erweiterungssystem gibt es kaum noch ein Argument gegen diesen Browser, abgesehen von der Google-Hysterie. Schnell, schön und mit allen Funktionen ausgestattet, die man braucht, wird Chrome sich einen festen Platz im Browsermarkt erkämpfen, vorausgesetzt die Entwicklung geht weiter und in der Final werden die durchaus vorhandenen Bugs gefixt - auch unter Linux.
Bananenbrot
Komisch, sowas nach dem Aufwachen im Ohr zu haben. Muss man sich da Sorgen machen?
Gesellschaftlich komformes Verhalten
Wir Bürger der freien Welt leben in einer seltsamen Gesellschaft. Unfähig, die Probleme des Kapitalismus in den Griff zu bekommen. strauchelt das System von einer Krise in die nächste, die Lücke zwischen Arm und Reich wird beständig größer, Sozialsysteme werden vom Staat abgeschafft und die Sicherheitsgesetze ermöglichen de facto schon heute ein Vorgehen des Militärs gegen Demonstranten.
Wie im großen so im kleinen:
Ich erkläre mich hiermit damit einverstanden, dass alle Ergebnisse, die im Rahmen des Praktikums x entstehen, ausschließlich von der Fraunhofer-Gesellschaft verwertet werden dürfen. Ich übertrage hiermit der Fraunhofer-Gesellschaft das wirtschaftliche und geistige Eigentum an den Arbeitsergebnissen.
...
Die wissenschaftliche Verwertung aller Ergebnisse z.B. in Form von Publikationen darf nur von der Fraunhofer-Gesellschaft vorgenommen werden.
Diese Einverständniserklärung sollen Bachelor-Informatikstudenten unterzeichnen, die im Rahmen ihres Studium ein Praktikum beim Fraunhofer-Institut machen. Bei der Arbeit in einem Unternehmen, bei dem das Unternehmen den Arbeiter ja genau dafür anstellt, solche Ergebnisse zu produzieren, mag so eine Erklärung angemessen. In diesem Fall jedoch bekommen die Studenten kein Geld, nur für das Institut wertlose CP, und die Studenten sind mehr oder weniger dazu verpflichtet solche Praktikas zu machen (mit der Wahl zwischen aufwändigen Projekt- und einfachen Praktikas). Anstatt hier einfach nur ein unabhängiges Nutzungsrecht zu vereinbaren, also legitim die Arbeitskraft der Studenten zu nutzen, direkt alle Rechte einzufordern und selbst wissenschaftliche Ergebnisse zu sichern, die Studenten also komplett auszunehmen, ist für ein staatlich mitfinanziertes Gebilde ganz schön dreist - und bezeichnet den Staat, in dem wir leben.
Jabber.org mit fragwürdiger Kommunikation
(20:33:18) jabber.org: The jabber.org IM service will migrate to new server software on Monday, December 14. Please visit http://www.jabber.org/index.php/2009/08/xmpp-server-migration/ for all the details.
(20:38:47) jabber.org: once
(20:43:20) jabber.org: Visit http://www.jabber.org/index.php/2009/12/server-migration-coming-soon/ for all the server migration details!!!!!!!!!!!!!!!
Erst den falschen Link ausgeben, dann unklare Botschaften senden (was sollte das "once"), schließlich die Nutzer mit Ausrufezeichen bombardieren und dabei die ganze Zeit auf eine Seite verweisen, die unter der Last des Ansturms gerne 500er produziert. Das geht doch besser...