Globale Sprachdatei für sämtliche Plugins einer TYPO3-Extension

Ich entwickle gerade meine TYPO3-Extension sm_employeelist neu, um sie um einige Funktionen zu erweitern, an TYPO3 4.x anzupassen und meine inzwischen gewachsenen Programmierkenntnisse einfließen zu lassen 😉

Als erstes habe ich heute getestet, wie ich in mehreren Plugins, die meine Extension bieten wird, eine einzige (quasi “globale”) Sprachdatei verwenden kann. Normalerweise hat nämlich jede Extension eine eigene locallang.xml und die gesamte Extension noch einmal eine locallang_db.xml, in der jedoch nur die Backend-Texte enthalten sind und die im Frontend (wo die Plugins ausgeführt werden) nicht (oder nur umständlich) zugreifbar ist.

Ich habe zu diesem Thema diese schöne Seite gefunden: TYPO3-Spickzettel von Pi-Phi-Productions. Dort wird vorgeschlagen, die Methode pi_loadLL() zu überschreiben und eine eigene Sprachdatei einzubinden. Dies müsste ich allerdings in jeder Plugin-Klasse (extkey_pi1, extkey_pi2 etc.) tun, was mir eindeutig missfällt.

Doch da wir ja objektorientiert programmieren, ist die Lösung recht einfach: Ich erstelle mir eine abstrakte Basisklasse für all meine Plugins, die von tslib_pibase abgeleitet ist und die Methode pi_loadLL() wie beschrieben überschreibt. Meine Plugin-Klassen leite ich dann wiederum von meiner eigenen Basisklasse ab und fertig ist die Laube. Zusätzlich kann ich diese Basisklasse dann noch verwenden um Funktionen zu implementieren, die auch in allen Plugin-Klassen benötigt werden (z.B. GET-Parameter prüfen, Flexform-Konfiguration auslesen etc.).

Quelltext

Zunächst einmal verwende ich die folgende Projektstruktur:

Projektstruktur für globale Extension-Sprachdateien in TYPO3

Wie sieht das Ganze nun im Code aus?

Meine abstrakte Plugin-Basisklasse
require_once(PATH_tslib.'class.tslib_pibase.php'); abstract class sm_testextension_pibase extends tslib_pibase { private $globalLocallangLoaded = false; function pi_loadLL() { parent::pi_loadLL(); if (!$this->globalLocallangLoaded) { $basePath = t3lib_extMgm::extPath($this->extKey).'locallang_global.xml'; $tempLOCAL_LANG = t3lib_div::readLLfile($basePath, $this->LLkey); //array_merge with new array first, so a value in locallang (or typoscript) can overwrite values from locallang_global.xml $this->LOCAL_LANG = array_merge_recursive($tempLOCAL_LANG, is_array($this->LOCAL_LANG) ? $this->LOCAL_LANG : array()); if ($this->altLLkey) { $tempLOCAL_LANG = t3lib_div::readLLfile($basePath, $this->altLLkey); $this->LOCAL_LANG = array_merge_recursive($tempLOCAL_LANG, is_array($this->LOCAL_LANG) ? $this->LOCAL_LANG : array()); } $this->globalLocallangLoaded = true; } } }

Eine Plugin-Klasse
require_once(t3lib_extMgm::extPath('sm_testextension').'class.sm_testextension_pibase.php'); class tx_smtestextension_pi2 extends sm_testextension_pibase { // übliche Plugin-Implementierung }

Meine globale Sprachdatei locallang_global.xml
<?xml version="1.0" encoding="utf-8" standalone="yes" ?> <T3locallang> <meta type="array"> <type>module</type> <description>Global language labels for the plugins of extension 'sm_testextension'</description> </meta> <data type="array"> <languageKey index="default" type="array"> <label index="smte_test1">Test1 global</label> </languageKey> <languageKey index="de" type="array"> <label index="smte_test1">Test1 global deutsch</label> </languageKey> </data> </T3locallang>

Firefox-Bookmarks aus Foxmarks anzeigen (JSON-Datei)

Aufgrund eines Kommentars von Steffen zu meinem Beitrag Foxmarks Plugin v2.0.34 für Firefox habe ich mir mal das kleine Python-Script aus dem Foxmarks-Wiki zum Anzeigen der Datei foxmarks.json angeschaut und auch zum Laufen bekommen. Allerdings habe ich es noch ein wenig erweitert, damit man es direkt als (valide) Website ausführen kann (siehe Download).

Beim Anschauen des Quelltextes ist mir dann aufgefallen, dass das Script gar nicht so lang/kompliziert ist, und habe das Ganze zusätzlich in PHP umgesetzt. Da PHP eingebaute JSON-Funktionen beinhaltet (ich benötige nur json_decode), beschränkt sich die Ausgabe der Bookmarks auf ein wenig PHP-Code drumherum. Zusätzlich habe ich dann noch ein kleines Java-Script und ein wenig CSS-Code eingebaut, damit die Bookmarks so aussehen wie hier: XSLT-Datei zum Anzeigen von Firefox-Bookmarks aus Foxmarks.

Download

  • Python-Script

    Enthält auch das benötigte Paket simplejson und kann wie folgt im Apache als Website veröffentlicht werden:

    DirectoryIndex index.py
    SetHandler mod_python
    PythonHandler index
    PythonDebug On
    PythonPath "['/path/to/script'] + sys.path"
  • PHP-Script
    Enthält auch grundlegende CSS-Definitionen und zwei kleine Grafiken für die Ordner.
    Das Script wurde am 06.12.2009 aktualisiert!

Vergleich der Makro-Performance zwischen Excel 2003 und 2007

Ich setze häufig Excel-Makros ein, wenn ich “mal schnell” CSV-Dateien bearbeiten, vergleichen oder auswerten muss. Ein Beispiel ist die Prüfung von Berechnungsergebnissen in mehreren großen CSV-Dateien. Dabei werden die CSV-Dateien eingelesen und zeilenweise in ein Excel-Arbeitsblatt eingefügt, das dann bestimmte Berechnungen durchführt und deren Ergebnisse zurückgibt. Diese Ergebnisse werden dann wieder in die Ausgangsdateien zurückgeschrieben.

Nun war ich seit der Umstellung von Excel 2003 auf 2007 irgendwie der Meinung, dass die Makro-Performance nachgelassen hat. Und heute konnte ich dies auch durch einen kleinen Test bestätigen. Die folgende Grafik zeigt die von Excel 2003 bzw. 2007 benötigten Zeiten für oben beschriebenes Makro mit unterschiedlicher Anzahl an Datensätzen. Mein Ergebnis: Das Makro läuft unter Excel 2007 auf baugleicher Hardware fast 50% langsamer als unter Excel 2003. Naja, dafür habe ich jetzt 16 Millionen Zeilen zur Verfügung und verbringe mehr Zeit mit dem Suchen der Menüeinträge als mit dem produktiven Arbeiten…

Vergleich der Makro-Performance zwischen Excel 2003 und 2007

Kleiner Scherz! 🙂 Die GUI von Excel 2007 ist schon sehr gut, wenn man sich erstmal daran gewöhnt hat. Und für alle Umsteiger gibt es ja auch das interaktive Referenzhandbuch von Microsoft, in dem man nachschauen kann, wo sich die alten Menüeinträge im neuen Excel 2007 verstecken: Interaktives Referenzhandbuch: Befehle in Excel 2003 und Excel 2007 im Vergleich.

NetSendAll Teil 3: PowerShell-Script NetSendAll

Es folgt der letzte Artikel meiner kleinen Reihe zum PowerShell-Skript NetSendAll: das eigentliche Script, das die Nachricht versendet. Es nutzt die beiden vorgestellten Funktionen PingPC und ReadTextFile um die aus der Textdatei eingelesenen PCs anzupingen. Bei Erfolg wird die Nachricht gesendet bzw. der Nachrichtendienst gestartet, falls dieser nicht läuft. In letzterem Fall wird dann später erneut versucht, die Nachricht zu senden.

# Sends a message to every PC in a text file via "net send" # ---------------------------------------------------------------------------- # # the text file containing the PCs $filePCsOnline = "PCs.txt"; # ---------------------------------------------------------------------------- # # the script needs only the message as a parameter if ($args.count -ne 1) { write-host ("Usage: .\" + $MyInvocation.MyCommand + " ""this is the message""") -foregroundcolor "red"; exit; } $message = $args[0]; # these arrays will be filled with PCs to ping/notify $pcsOnline = ReadTextFile $filePCsOnline; $pcsTryAgain = @(); write-host ("Sending the following message to " + $pcsOnline.length + " PCs:`n " + $message); # find out if PCs are online and notify them resp. enable messenger if disabled foreach ($pc in $pcsOnline) { if (PingPC($pc.Name)) { $svc = gwmi win32_service -computername $pc.Name | ? { $_.name -eq "Messenger" } if ($svc.state -eq "Running") { write-host ("Messenger is running. Sending message to " + $pc.Name) -foregroundcolor "green"; net send $pc.Name $message } else { write-host ("Messenger is not running on " + $pc.Name + ". Starting it...") -foregroundcolor "magenta"; [void]$svc.startservice(); $pcsTryAgain += $pc; } } else { write-host ($pc.Name + " is offline") -foregroundcolor "red"; } } if ($pcsTryAgain.length -gt 0) { write-host ("" + $pcsTryAgain.length + " PCs with disabled Messenger will be tried again in 5 seconds"); for ($i = 1; $i -le 5; $i++) { write-host $i; sleep(1); } foreach ($pc in $pcsTryAgain) { if (PingPC($pc.Name)) { $svc = gwmi win32_service -computername $pc.Name | ? { $_.name -eq "Messenger" } if ($svc.state -eq "Running") { write-host ("Messenger is running. Sending message to " + $pc.Name) -foregroundcolor "green"; net send $pc.Name $message } else { write-host ("Messenger is not running on " + $pc.Name + ".") -foregroundcolor "red"; } } else { write-host ($pc.Name + " is offline") -foregroundcolor "red"; } } }

Beispielausgabe des PowerShell-Scripts NetSendAll

Download

Das Script inkl. der beiden benötigten Funktionen gibt es hier zum Download: NetSendAll.ps1

NetSendAll Teil 2: PowerShell-Funktion ReadTextFile

Der heutige Artikel ist der zweite in meiner kleinen Reihe zum PowerShell-Skript NetSendAll, mit dem ich allen PCs unseres Netzwerkes über unterschiedliche Subnets hinweg mittels net send eine Nachricht schicken kann.

Gestern habe ich die Funktion PingPC vorgestellt, die einen einzelnen PC anpingt. Doch woher bekomme ich nun die Info, welche PCs überhaupt angepingt werden sollen? Ganz einfach: Aus einer simplen Textdatei. Diese kann z.B. mittels eines weiteren PowerShell-Skripts mit den PC-Namen aus dem Active Directory gefüllt, oder händisch angelegt werden. Es könnte bspw. so aussehen:

#columns;Name;IP;MAC
macke;192.168.100.1;ab-cd-ef-ab-cd-ef
macke2;192.168.100.2;ab-cd-ef-ab-cd-f0

Die erste Zeile definiert mir dabei die Informationen, die in jeder folgenden Zeile kommasepariert enthalten sind (PC-Name, IP und MAC). Aus jeder folgenden Zeile der Datei erstellt meine Funktion ReadTextFile jetzt ein anonymes Objekt mit Attributen, deren Bezeichner den in der ersten Zeile definierten entsprechen, also z.B.:

$pc # das anonyme Objekt
$pc.Name # ein Attribut "Name", das für die zweite Zeile "macke" enthalten würde
$pc.IP
$pc.MAC

So kann ich komfortabel auf die in der Textdatei enthaltenen Werte zugreifen und sie weiterverarbeiten. Sollte die erste Zeile fehlen, werden die Spalten einfach nach dem Schema col1, col2 etc. durchnummeriert.

# Reads the content of a textfile and returns the lines as anonymous objects with named attributes. # Lines starting with "#" are ignored (comments). # If the first line starts with "#columns" the following values are taken as attribute names for the anonymous objects, # otherwise they will be named col1, col2, etc. function ReadTextFile($filename) { if (!(test-path $filename)) { write-host ("File not found: " + $filename) -foregroundcolor "red"; exit; } $contents = @(); $lineCounter = 0; $c = get-content $filename; foreach ($line in $c) { if ($line -ne $null -and $line.trim() -ne "") { $lineCounter++; # read column headers if ($lineCounter -eq 1) { if ($line.startsWith("#columns;")) { $headers = $line.substring("#columns;".length, ($line.length - "#columns;".length)).split(";"); } else { $colCounter = $line.split(";").count; $headers = @(); for ($i = 0; $i -lt $colCounter; $i++) { $headers += ("col" + $i); } } } # create anonymous object from line if (!$line.startsWith("#")) { # create object with header names as properties $l = "" | select-object $headers; $c = $line.split(";"); for ($i = 0; $i -lt $headers.count; $i++) { $l.($headers[$i]) = $c[$i]; } $contents += $l; } } } return $contents; }

Beispielausgabe der PowerShell-Funktion ReadTextFile

Morgen geht es dann weiter mit der letztlichen Funktion NetSendAll.

NetSendAll Teil 1: PowerShell-Funktion PingPC

Heute und in den nächsten zwei Tagen werde ich hier ein PowerShell-Script vorstellen, das es mir erlaubt, alle PCs in unserem Netzwerk per net send mit einer Nachricht zu versorgen, obwohl sich diese in unterschiedlichen Subnets befinden. Laut diesem Beitrag in der Microsoft Knowledge Base ist das nämlich weder mit net send * noch mit net send /domain:Domäne möglich: Der Windows Nachrichtendienst.

Fangen wir zunächst einmal damit an, einen Ping an die PCs abzusetzen, um zu kontrollieren, ob diese online sind. Dazu habe ich mir eine kleine Funktion geschrieben, die genau dies tut und außerdem prüft, ob der angepingte PC überhaupt zu unserem Netzwerk gehört (oder fälschlicherweise z.B. als Internetadresse aufgelöst wurde).

# Pings the given PC to find out whether it is online. # Checks the IP address for the correct subnet. function PingPC($pcname) { trap { write-host ($pcname + " threw an error: " + $_.exception.message) -foregroundcolor "red"; continue; } # change this value to your subnet $subnet = "192.168."; if ($pcname.trim -ne "") { $pingsender = new-object system.net.networkinformation.ping; # timeout = 500ms $ping = $pingsender.send($pcname, 500); if ($ping.status -ne "Success") { write-host ($pcname + " does not respond to ping") -foregroundcolor "red"; return $false; } else { if (!$ping.address.tostring().startswith($subnet)) { write-host ($pcname + " is not in subnet " + $subnet) -foregroundcolor "magenta"; return $false; } else { write-host ($pcname + " is online") -foregroundcolor "green"; return $true; } } } }

Beispielausgabe der PowerShell-Funktion PingPC

Morgen geht es dann weiter mit einer Funktion, die Informationen über PCs aus einer Textdatei ausliest und diese als anonyme Objekte bereitstellt.

Beispielimplementierung des LZW-Algorithmus in Java

Im Rahmen meines Studiums (Software-Engineering) durfte ich im letzten Präsenzblock die Vorlesung Multimedia besuchen. Dort haben wir auch den Lempel-Ziv-Welch-Algorithmus angesprochen, einen bekannten Algorithmus zur Entropiekodierung. Um die ganze Theorie dahinter (so viel ist es aber eigentlich gar nicht) besser zu verstehen, habe ich eine kleine Implementierung des Algorithmus in Java erstellt. Wen es interessiert, das Progrämmchen gibt es hier zum Download: Beispielimplementierung des LZW-Algorithmus in Java.

Das Ergebnis des Programms für den String, der auch in der Wikipedia verwendet wird, ist im folgenden Screenshot zu sehen.

Beispielaufruf des LZW-Algorithmus in Java

MediaWiki-Extension: SVNIntegration

Gestern habe ich meine erste “offizielle” MediaWiki-Extension auf mediawiki.org eingestellt: SVNIntegration. Ich habe zwar schon einige Sachen für unser Wiki entwickelt (z.B. ein Berechtigungskonzept und eine Art ToDo-Liste für die Mitarbeiter) aber die waren sehr speziell und nicht unbedingt für die Öffentlichkeit interessant (jedenfalls meiner Meinung nach).

Eine Subversion-Integration, wie es sie z.B. auch für DokuWiki gibt (Plugin Websvn), wäre jedoch vielleicht auch für den ein oder anderen interessant, sodass ich meine Erweiterung wie gesagt veröffentlicht habe. Sicherlich kann man auch darauf verzichten und gleich Trac verwenden, aber wenn sich ein Wiki einmal etabliert hat, möchte ich es ungern durch eine neue Software ablösen.

Was kann die Erweiterung denn nun so alles? Bislang lediglich drei Aufgaben:

  • Dateiinhalte aus Subversion (SVN) auslesen und anzeigen (inkl. Syntax-Highlighting)
  • Eine kleine Info-Tabelle für eine Datei anzeigen (letzte Änderung, Autor, Revision etc., siehe Beispiel unten)
  • Die Änderungshistorie einer Datei ausgeben

Dabei werden übrigens Commit-Messages, die MediaWiki-Markup enthalten, in HTML umgewandelt und entsprechend formatiert (Siehe Beispiel unten. Dass der zweite Aufzählungspunkt nicht umgewandelt wird, liegt daran, dass ich das falsche Markup (nämlich das von Trac) verwendet habe 🙂 ).

Selbstverständlich funktionieren alle Parameter, die man Subversion auf der Kommandozeile übergeben kann. Hier ist ein Beispiel, das zu dem im Bild gezeigten Ergebnis führt:

<SVNFileInfo username="user" password="pass" revision="67">http://svn.intranet/Sourcecodes/Natural/V-SYSTEM/VOLLVST.nat</SVNFileInfo>

Beispiel für einen SVNIntegration-Befehl: SVNFileInfo

Download

Wie auch auf der MediaWiki-Seite verlinkt, gibt es die Extension hier zum Download: Download von SVNIntegration.

CSS-Unterstützung im Internet Explorer 7

Wie ich heute festgestellt habe, funktionieren einige CSS-Selektoren im Internet Explorer 7 nur, wenn im HTML-Quelltext ein DOCTYPE (strict, loose etc., völlig egal welcher) gesetzt ist. Nicht, dass ich das nicht ohnehin bei jeder Website mache, die ich gestalte, aber es gibt durchaus einige “Generatoren” für Websites, die diesen Standard nicht befolgen. Und schon funktioniert z.B. der Attribut-Selektor nicht, z.B.:

input[readonly]

CSS2 Test Suite: 5.8.1 Attribute Selectors

Natural-Workfiles mit dynamischer Zeilenlänge erstellen

Natural-Workfiles sind bei uns ein beliebtes Hilfsmittel zum Datenex- und -import zwischen den einzelnen Systemen. Leider haben die “normalen” Workfiles die Eigenart mit fixen Zeilenlängen zu arbeiten, was kurze Strings z.B. mit Leerzeichen auffüllt (wordurch die Datengröße drastisch steigt).

Das folgende kleine Natural-Programm zeigt, wie man Workfiles mit dynamischer Zeilenlänge füllt. Man beachte das H'0A' in der WRITE WORK-Zeile: Das ist ein manueller Zeilenumbruch.

DEFINE DATA * LOCAL * Zeilenvariable für das Workfile 01 #WORK1 (A) DYNAMIC * END-DEFINE * * Definition des Workfiles DEFINE WORK FILE 1 '$HOME/SomeWorkFile.txt' TYPE 'UNFORMATTED' * #WORK1 := 'Test' * WRITE WORK 1 VARIABLE #WORK1 H'0A' CLOSE WORK 1 * END