Fancybox mit Icon Font

Ein Kunde von mir liebt die Fancybox, daher möchte er es gerne in seinen Projekten drin haben. Ich finde sie gut, allerdings gefällt mir nicht, wenn die Blätterpfeile und der Schließen-Button mit Hintergrundbildern gestylt werden. In den Zeiten der Icon Fonts finde ich diese einfach viel besser (und flexibler). Font-Awesome habe ich als Scss eingebunden und kann die Mixins und Variablen davon nutzen.

So kann man das X mit Fontawesome umsetzen:

.fancybox-close {
  position: absolute;
  top: -16px;
  right: -16px;
  width: 32px;
  height: 32px;
  cursor: pointer;
  z-index: 8040;
  background-color: $white;
  border-radius: 16px;
  border: 3px solid $body-font-color;
  color: $body-font-color;
  box-shadow: 0 5px 10px rgba($dark-gray, 0.5);
  font-size: rem-calc(18);
 
  &::before {
    @include fa-icon;
    content: $fa-var-times;
    display: block;
    position: absolute;
    top: 5px;
    left: 6px;
  }
}

Und so werden aus den Icons für die Pfeile ebenfalls Font Icons:

.fancybox-nav span {
  position: absolute;
  top: 50%;
  width: 21px;
  margin-top: -8px;
  cursor: pointer;
  z-index: 8040;
  visibility: visible;
  opacity: 0.5;
  color: $white;
  &::before {
    @include fa-icon;
    font-size: rem-calc(24);
    text-shadow: 0px 0px 5px rgba($dark-gray, 0.5);
  }
}
 
.fancybox-prev span {
  left: 10px;
  &::before {
    content: $fa-var-chevron-left;
  }
}
 
.fancybox-next span {
  right: 10px;
  &::before {
    content: $fa-var-chevron-right;
  }
}

Selbst das Loader Bild kann man ersetzen: mit Css Animations. Das zu testen ist allerdings etwas tricky. Man kann beim Aufruf der Seite das Fancybox-Loading anzeigen lassen.

$(document).ready(function () {
    $.fancybox.showLoading();
});

Weil man denn den Loader endlich mal länger sieht, kann man den entsprechend stylen:

#fancybox-loading {
  position: fixed;
  top: 50%;
  left: 50%;
  margin-top: -22px;
  margin-left: -22px;
  background-position: 0 -108px;
  opacity: 0.8;
  cursor: pointer;
  z-index: 8060;
}
 
#fancybox-loading div {
  width: 44px;
  height: 44px;
  color: $body-font-color;
  &::before {
    @include fa-icon;
    content: $fa-var-spinner;
    font-size: rem-calc(44);
    animation: fa-spin 1s infinite steps(8);
  }
}

TYPO3 Random Content nach Kategorie ausgeben

Für mein aktuelles Projekt musste ich folgende Aufgabenstellung lösen und ich finde, dass ich eine elegante Lösung dafür gefunden habe. Auf der Startseite sollen in mehreren Spalten Inhalte zufällig ausgegeben werden. Damit die Benutzer bestimmen können, in welchen Spalten welche Inhalte ausgegeben werden, habe ich global Kategorien angelegt (ich nenne sie hiermal Kat1, Kat2, Kat3). In einem Storage-Ordner können nun beliebige Inhalte abgelegt werden und einer (oder mehreren Kategorien) zugewiesen werden. Für die Ausgabe habe ich ein Plugin erstellt, dort kann man die Kategorie für die Ausgabe definieren. Nun wird es interessant: Wie kann man die Inhalte einer bestimmten Kategorie im Plugin auslesen und bei der Ausgabe das normale Rendering der TYPO3 Content Elemente nutzen?

Inspiriert von diesem Beitrag Variierender Content anhand der System Kategorien habe ich folgendes Vorgehen gewählt.

In der Show-Funktion des Controllers erstelle ich die Konfiguration und erzeuge die Ausgabe mithilfe des Content Renderers. Ich definieren mein Plugin auf die übliche Art und Weise in ext_localconf.php und ext_tables.php, setze zusätzlich die Storage Pid in den Settings:

plugin.tx_myext {
	persistence {
		storagePid = {$randomContentStorage}
	}
	settings {
		storagePid = {$randomContentStorage}
	}
}

Die die Auswahl der Kategorie im Plugin nehme ich ein Flexform (das ist nur der relevante Teil davon):

<settings.category>
    <TCEforms>
        <exclude>1</exclude>
        <label>LLL:EXT:ewwald/Resources/Private/Language/locallang_db.xlf:ff.category</label>
        <config>
            <type>group</type>
            <internal_type>db</internal_type>
            <allowed>sys_category</allowed>
            <size>1</size>
            <maxitems>1</maxitems>
            <minitems>0</minitems>
            <show_thumbs>1</show_thumbs>
            <wizards>
                <suggest>
                    <type>suggest</type>
                </suggest>
            </wizards>
        </config>
    </TCEforms>
</settings.category>

Dann erstelle ich eine Klasse RandomContentController mit meiner Show Funktion:

public function showAction()
{
    $conf = [
        'table' => 'tt_content',
        'select.' => [
            'pidInList' => $this->settings['storagePid'],
            'join' => 'sys_category_record_mm ON tt_content.uid = sys_category_record_mm.uid_foreign',
            'groupBy' => 'uid',
            'where' => 'sys_category_record_mm.uid_local = '.(int)$this->settings['category'].' AND sys_category_record_mm.tablenames = \'tt_content\'',
            'max' => '1',
            'orderBy' => 'rand()'
        ]
    ];
    $html = $GLOBALS['TSFE']->cObj->cObjGetSingle('CONTENT', $conf);
    return $html;
}

Das wars! Keine Models und Repositories, keine Templates. Die Inhaltselemente werden wie andere Inhaltselemente ausgegeben.

TYPO3 Multisite / Multidomain und Verlinkung untereinander

Ein Kunde hat eine Website laufen in Deutsch unter der Beispieldomain www.beispiel.de. Er möchte gerne eine englische Version dieser Seite unter einer anderen Domain laufen www.beispiel.eu. Die englischen Inhalte entsprechen nicht immer den deutschen, manche Seiten sind direkt übersetzt, manche Seiten existieren nicht. Und so hatte ich folgende Idee: Ich habe dem Seiten-Datensatz ein neues Feld eingefügt (tx_related_page), in das die Seite der anderen Sprache ausgewählt werden kann. Somit kann im Header hinter einem Flaggensymbol der Link zu der Seite in der anderen Sprache aus dem anderen Seitenbaum versteckt werden. Falls in dem Seiten-Datensatz kein Link hinterlegt ist, wird einfach der Link zur Root-Seite des anderen Seitenbaumes generiert. Mal anschaulich:
– ROOT DE
— Unterseite 1 (Gegenseite Subpage 1)
— Unterseite 2 (keine Gegenseite)
– ROOT EN
— Subpage 1 (Gegenseite Unterseite 1)
— Subpage 2 (keine Gegenseite)
Auf ROOT DE ist der Domain Record für www.beispiel.de und auf ROOT EN ist der Domain Record für www.beispiel.eu.
Auf Unterseite 1 wird die englische Flagge auf Subpage 1 verlinkt, Subpage verlinkt auf Unterseite 1. Da Unterseite 2 keine Gegenseite hat, wird ein Link zu ROOT EN generiert. Und von Subpage 2 ein Link zu ROOT DE.

Den Link in TypoScript zu erzeugen funktioniert so:

20 = IMAGE
20.file = {$assets}Public/Images/{$flagIcon}
20.stdWrap.typolink.parameter.data = field:tx_related_page
20.stdWrap.if.isTrue.data = page:tx_related_page
 
30 = IMAGE
30.file = {$assets}Public/Images/{$flagIcon}
30.stdWrap.typolink.parameter = {$oppositeRootPid}
30.stdWrap.if.isFalse.data = page:tx_related_page

Ohne RealURL hat das prima funktioniert. Dann hatte ich RealURL aktiviert und auf einmal bekam ich nur Links auf die Root-Seite des anderen Seitenbaums. Lange hab ich gesucht, was an der Konfiguration nicht stimmt, ich erspare euch das alles. Das ist die funktionierende Lösung:

Im Setup des Templates muss folgendes ergänzt werden, damit Links zwischen Domains generiert werden:
config.typolinkEnableLinksAcrossDomains = 1

Das nicht vergessen:
config.tx_realurl_enable = 1

In der RealURL Konfiguration müssen die ID der Root-Seiten für die Domains angegeben werden. Ich hatte es zuerst so wie im Beispiel im RealURL FAQ. Das klappt nicht! Aber folgende Konfiguration funktioniert:

$GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['realurl'] = [
    '_DEFAULT' => [...]
];
$TYPO3_CONF_VARS['EXTCONF']['realurl']['www.beispiel.de'] = $TYPO3_CONF_VARS['EXTCONF']['realurl']['_DEFAULT'];
$TYPO3_CONF_VARS['EXTCONF']['realurl']['www.beispiel.de']['pagePath']['rootpage_id'] = 1;
 
$TYPO3_CONF_VARS['EXTCONF']['realurl']['www.beispiel.eu'] = $TYPO3_CONF_VARS['EXTCONF']['realurl']['_DEFAULT'];
$TYPO3_CONF_VARS['EXTCONF']['realurl']['www.beispiel.eu']['pagePath']['rootpage_id'] = 29;

Einträge in Liste in Zweiergruppen ausgeben

Folgende Aufgabenstellung: Mit Fluid sollen für eine Slideshow jeweils zwei Elemente einer Liste gruppiert (also in 1 Div eingeschlossen) ausgegeben werden. Als erstes muss am Anfang ein Slide geöffnet werden, wenn die Liste nicht leer ist. Nach dem letzten Element muss man den Slide schließen, egal wieviele Elemente in der Liste sind. Dazwischen muss man nach zwei Elementen den Slide schließen und wieder öffnen. Und sieht dann der Code aus:

<div class="video-slideshow">
	<div class="slick-slideshow" data-slick='{
			"adaptiveHeight": true
		}'>
		<div class="slide"><!-- Slide BEGIN -->
			<f:for each="{videos}" as="video" iteration="i">
				<f:render section="Video" arguments="{video: video, settings: settings}" />
				<f:if condition="{i.isLast}">
					<f:then>
						</div><!-- Slide END -->
					</f:then>
					<f:else>
						<f:if condition="{i.index} % 2">
							</div><div><!-- Slide -->
						</f:if>
					</f:else>
				</f:if>
			</f:for>
	</div><!-- slick-slideshow -->
</div><!-- video-slideshow -->

Ok, übersichtlich ist was anderes…

News-Liste durch Kategorie-Liste filtern

Wer zu ungeduldig ist, gleich nach unten scollen und den letzten Absatz lesen.

Folgendes Setup: Auf einer Seite befinden sich in einer Spalte das News-Plugin mit der Liste, in der Spalte nebendran das News-Plugin mit der Kategorieliste. Wenn nun eine Kategorie angeklickt wird in der Kategorie-Liste, dann möchten man, dass diese Kategorie in der Liste hervorgehoben wird und dass nur die News dieser Kategorie angezeigt werden. Das erste hat gut geklappt.

Nun hat sich die News-Liste trotz aller meiner Bemühungen von der in der URL gesetzten Ketegorie unbeindruckt gezeigt. Dabei war der Haken bei Disable override demand NICHT gesetzt. Ich hatte auch gegoogelt und nur einen Forums-Beitrag gefunden mit dem gleichen Problem, leider ohne Antwort. Ich hane angefangen mich durch den Code der News-Extension zu debuggen, um rauszufinden, wo der Parameter flöten gehen könnte.

In der Klasse NewsRepository Zeile 52 dann die Antwort:

// If "ignore category selection" is used, nothing needs to be done
if (empty($conjunction)) {
    return $constraint;
}

An dieser Stelle bricht das ab und der Filter wird nicht angewendet.

Lösung: im Backend-Formular des Plugins muss im Category mode am besten “Show items with selected categories (OR)” eingestellt werden. Das ist alles!
2016-06-13 14-58-50

Bestimmt denken sich nun manche: Das steht doch genau so da…, wer lesen kann… , blablabla. Meine Antwort darauf: In erster Linie will ich ja keine Filterung, daher ist die erste Auswahl (zeige alle Kategorien) ja eigentlich richtig. Ich finde es eher verwirrend, dass obwohl ich keine Kategorien initial ausgewählt habe, alle angezeigt werden. Vielleicht ist mit diesem Beitrag doch mal jemandem geholfen.

TYPO3 Überschrift als RTE formatieren

Manchmal muss man auch Überschriften mit dem RTE formatieren können – Text fett oder kursiv auszeichnen z.B.

Dazu erstmal das TCA des Header-Felder auf Textarea mit RTE umstellen. Dieser Code wird in die Datei my_extension/Configuation/TCA/Overrides/tt_content.php eingefügt.

$GLOBALS['TCA']['tt_content']['columns']['header']['config']['type'] = 'text';
$GLOBALS['TCA']['tt_content']['columns']['header']['config']['cols'] = 40;
$GLOBALS['TCA']['tt_content']['columns']['header']['config']['rows'] = 3;
$GLOBALS['TCA']['tt_content']['columns']['header']['defaultExtras'] = 'richtext[]';
$GLOBALS['TCA']['tt_content']['palettes']['header'] = str_replace(
    'header;LLL:EXT:frontend/Resources/Private/Language/locallang_ttc.xlf:header_formlabel,',
    'header;LLL:EXT:frontend/Resources/Private/Language/locallang_ttc.xlf:header_formlabel;;richtext:rte_transform[mode=ts_css],',
    $GLOBALS['TCA']['tt_content']['palettes']['header']
);
 
$GLOBALS['TCA']['tt_content']['palettes']['headers'] = str_replace(
    'header;LLL:EXT:frontend/Resources/Private/Language/locallang_ttc.xlf:header_formlabel,',
    'header;LLL:EXT:frontend/Resources/Private/Language/locallang_ttc.xlf:header_formlabel;;richtext:rte_transform[mode=ts_css],',
    $GLOBALS['TCA']['tt_content']['palettes']['headers']
);

Damit wird der Header zu einem “normalen” RTE Feld mit Standard-Konfiguration. Wenn das zu viel ist (und das nehme ich an), kann für das Header-Feld eine spezielle RTE-Konfiguration angelegt werden. Das muss ins PageTsConfig:

RTE.config.tt_content.header {
	showButtons (
		bold, italic, chMode
	)
	RTEHeightOverride = 100
}

Dann muss man nur dafür sorgen, dass die Formatierung auch ausgegeben wird. Falls man mit Fluid Styled Content arbeitet (absolut empfehlenswert), dann muss man im Partial Header/Header.html nur folgende Änderung vornehmen:

<h1><f:link.typolink parameter="{link}"><f:format.raw>{header}</f:format.raw></f:link.typolink></h1>

Fluid Select mit Objekten in Hierarchie darstellen

Folgendes Datenmodell: Objekte vom Typ ‘Items’ und Kategorien. Eine Kategorie hat eigentlich nur einen Titel, kann aber über das Feld ‘parent’ einer anderen Kategorie als Kind zugewiesen werden, so dass eine Art Baum entsteht. So ist z.B. in Kategorie A.1 ist Kategorie A als Parent gesetzt.

- A
-- A.1
-- A.2
--- A.2.I
--- A.2.II
-- A.3
- B

Nun will ich in einem Select-Feld die Kategorien ausgeben, dabei würde ich gerne den vollen Pfad zur Kategorie im Select ausgeben und nicht nur den Titel der Kategorie. So würde dann die Auswahl im Select-Feld so aussehen:

A
A > A.1
A > A.2
A > A.2.I
[...]

Und das ist meine (wie ich finde elegante) Lösung: das Select-Feld in Fluid wird dann so definitert:

<f:form.select property="category" options="{categories}" prependOptionLabel="Bitte wählen" prependOptionValue="" optionLabelField="titlePath"/>

Die Eigenschaft titlePath exisitert in der Kategorie natürlich nicht, aber Fluid bedient sich da des Getters, also muss nur der entsprechende Getter definiert werden. In dem Getter ruft man dann die gleiche Funktion vom Parent auf. That’s it!

/**
 * @return string
 */
public function getTitlePath() {
    if($this->parent == null) {
        return $this->title;
    }
    return $this->parent->getTitlePath().' - '.$this->getTitle();
}

Fluid Formular für neues Objekt mit Select und leerer Option

Ich implementiere gerade eine Extension mit (vereinfacht) folgendem Aufbau: Objekte vom Typ ‘Items’ können jeweils einer Kategorie zugeordnet werden. Die Kategorien sind wiederum in der Datenbank als Datensätze hinterlegt. So etwas kommt häufig vor, das beliebteste Beispiel ist wahrscheinlich ein Blog-Eintrag und die dazugehörige Kategorie.

Erstellt man diese Extension z.B. über den Extension Builder und lässt sich eine new/create-Action anlegen, kann man im Frontend nun auch Objekte vom Typ ‘Item’ anlegen. Nun hatte ich folgende Hürde zu meistern: das Feld Kategorie ist ein Pflichtfeld, trotzdem soll in der Kategorieauswahl im Frontend eine zusätzliche leere Option angezeigt werden. Da höre ich schon den Schrei der Empörung: “Warum brauchst du eine leere Option? Das Feld ist doch ein Pflichtfeld!”. Weil dann der Benutzer gezwungen ist, sich für eine Option bewusst zu entscheiden und nicht einfach die erste Option angenommen wird.

Eine leere Option im Formular dranzuhängen ist einfach:

<f:form.select property="category" options="{categories}" prependOptionLabel="Bitte wählen" prependOptionValue="" optionLabelField="title" />

Im Model das Feld als Pflichtfeld deklarieren:

/**
 * @var \MY\MyExtension\Domain\Model\Category
 * @validate NotEmpty
 */
protected $category = null;

Wenn man nun das Formular im Frontend testet, passiert folgendes: beim Absenden des Formulars ohne etwas auszuwählen gibt es eine Exception: Exception while property mapping at property path "": PHP Catchable Fatal Error: Argument 1 passed to MY\MyExtension\Domain\Model\Item::setCategory() must be an instance of MY\MyExtension\Domain\Model\Category, null given. Das erwartete Verhalten wäre aber, dass der Validator greift und eine Fehlermeldung ausgibt.

Die Lösung ist das Setzen eines Default-Wertes im Setter:

/**
 * Sets the category
 *
 * @param \MY\MyExtension\Domain\Model\Category $category
 * @return void
 */
public function setCategory(\MY\MyExtension\Domain\Model\Category $category = null)
{
    $this->category = $category;
}

TYPO3 7.6. Indexed Search – Ausgabe optimieren

Lange habe ich mich nicht an die zweite Implementierung der Indexed Search rangetraut. Der Hinweis “experimental” lädt auch nicht gerade ein, das Plugin einzusetzen. Mittlerweile ist das “experimental” dem “Extbase/Fluid based” gewichen und suggeriert, dass es nun stabil sei. Die Vorteile von dem Plugin gegenüber dem Original liegen auf der Hand – endlich saubere Templates! Wenn ich nur daran denke, welche Konfiguration und CSS-Gefrickel teilweise vonnöten war, um das gewünschte Layout des Designers mit dem alten Plugin umzusetzen. Nun soll es damit ja vorbei sein. Wenn es doch so einfach wäre.

Neu in TYPO3 ist, dass man nicht alle Templates überschreiben muss, sondern einen Template-Order angeben kann, in dem man einige der Dateien ablegt und der Rest wird als Fallback aus der Extension geholt. Das funktioniert in Indexed Search nicht so gut, dazu hatte ich ja schonmal was geschrieben: Templates Indexed Search überschreiben. Die Konfiguration muss wie folgt angepasst werden, damit man in den Konstanten den Pfad zu den überschreibenden Dateien angeben kann.

plugin.tx_indexedsearch {
	view {
		templateRootPath >
		templateRootPaths {
			0 = EXT:indexed_search/Resources/Private/Templates/
			1 = {$plugin.tx_indexedsearch.view.templateRootPath}
		}
 
		partialRootPath >
		partialRootPaths {
			0 = EXT:indexed_search/Resources/Private/Partials/
			1 = {$plugin.tx_indexedsearch.view.partialRootPath}
		}
	}
}

So weit so gut. Nächstes Problem – der Suchbegriff wird in der Anzeige der Suchtreffer nicht hervorgehoben. Natürlich könnte man es dann auch mit einem ViewHelper lösen, ABER der Mechanismus zum Hervorheben des Suchbegriffs sorgt auch dafür, dass der relevante Abschnitt der Zusammenfassung in der Trefferliste angezeigt wird. Soll heißen: Wenn der Suchbegriff unten auf der Seite gefunden wird, ich aber die ersten 200 Zeichen des Textes bekomme, dann bringt mir der ViewHelper auch nichts.

Nachdem ich Google mehrfach bemüht habe, um nur ansatzweise jemanden mit dem gleichen Problem zu finden, wurde ich doch fündig auf Stackoverflow. Im Indexed Search Controller ist in Zeile 452 folgendes zu finden:

$resultData['description'] = $this->makeDescription(
    $row,
    (bool)!($this->searchData['extResume'] && !$headerOnly),
    $this->settings['results.']['summaryCropAfter']
);

Damit der Suchbegriff hervorgehoben wird, muss der zweite Parameter false sein. Ich habe über diese Stelle meditiert, um zu verstehen, was diese Abfrage macht und in welchem Fall sie mal false ist. Aller Einstellungen zum Trotz ist es nie der Fall. Interessanterweise wird der Suchbegriff hervorgehoben, wenn ich bei gleichen Konfiguration das Classic-Plugin für die Ausgabe nutze. Verrückt, oder?

Lösung: XCLASS! Extbase-Style geht das so: in ext_localconf.php die Klasse angeben, die den Controller überschreibt.

$GLOBALS['TYPO3_CONF_VARS']['SYS']['Objects']['TYPO3\\CMS\\IndexedSearch\\Controller\\SearchController'] = array(
    'className' => 'NP\\MyExtension\\Controller\\SearchController'
);

Dann im eigenen Controller die Funktion compileSingleResultRow übernehmen und überschreiben:

$resultData['description'] = $this->makeDescription(
    $row,
    false,
    $this->settings['results.']['summaryCropAfter']
);

Yeah, der Suchbegriff wird hervorgehoben. Damit das auch gut aussieht, muss man im Template die Description als RTE parsen:

<f:format.html>{row.description}</f:format.html>

Nächstes Problem: Falls der Suchbegriff in einem Link vorkommt, dann ist der Link schrott. Beispiel: Auf der gesuchten Seite exisitert der Link http://www.test.de, der Suchbegriff ist test. Die Funktion zum Hervorheben wrappt den Suchbegriff in einen Tag (span mit Klasse dran, egal), Ergebnis: http://www.<span class=”…”>test</span>.de. Bei der Ausgabe geht der RTE Parser dahin, aha, ein Link und macht folgendes: <a href=”http://www.”>http://www.</a><span class=”…”>test</span>.de. Es entsteht ein völlig verkrüppelter Link. Bis ich rausgefunden habe, dass es der RTE macht, waren 3 Tassen Kaffee verbraucht. Lösung: <f:format.raw>{row.description}</f:format.raw> statt dem RTE Parser.

Wenn jemand weiß, wie das alles weniger kompliziert gemacht werden kann, ich bin gerne für Vorschläge offen.

TYPO3: Inhaltselemente nummerieren

Für die Umsetzung von diesem Template müssen die Inhaltselemente mit einer Klasse und einer Id versehen werden, die die Position des Inhaltselements in der Spalte enthält – die Elemente werden durchgezählt.

Gewünschtes Ergebnis:

<!-- OPEN - LEFT PART -->
<div class="ms-left animated-middle">
	<div class="ms-section" id="left1">
		INHALTSELEMENT
	</div>
	<div class="ms-section" id="left2">
		INHALTSELEMENT
	</div>
	<div class="ms-section" id="left3">
		INHALTSELEMENT
	</div>
	<div class="ms-section" id="left4">
		INHALTSELEMENT
	</div>
</div>
<!-- CLOSE - LEFT PART -->

Und das erreicht man durch folgendes TypoScript:

lib.contentLeft < styles.content.get
lib.contentLeft {
    renderObj = COA
    renderObj {
        10 = LOAD_REGISTER
        10 {
            Counter.cObject = TEXT
            Counter.cObject.data = register:Counter
            Counter.cObject.wrap = |+1
            Counter.prioriCalc = intval
        }
 
        20 = TEXT
        20 {
            insertData = 1
            value = <div class="ms-section" id="left{register:Counter}">
        }
 
        40 < tt_content
        50 = TEXT
        50.value = </div>
    }
}

Ich liebe TYPO3!

Geschrieben in typo3 | Kommentare deaktiviert für TYPO3: Inhaltselemente nummerieren