Anleitung: Spalte "Application" in Oxygen Template Liste
Es gibt eine aktuelle Version dieses Snippets.
Inhalt
Aufgabe
Oxygen ist ein mächtiges Plugin für WordPress, welches das WordPress Theme deaktiviert, die Erstellung eigener Templates mit einem visuellen Builder unterstützt, und damit ein ganz individuelles Design für die Website ermöglicht.
Innerhalb jedes Oxygen Templates wird dabei definiert, unter welchen Bedingungen es genutzt wird, beispielsweise für das Archiv oder den Blog, die Suchergebnisse, Einzeldarstellung von Seiten oder Beiträgen, all das für alle oder nur bestimmte Kategorien, Autoren, usw.
In der Liste der Templates sind diese Bedingungen leider nicht ersichtlich. Beim Einsatz vieler Templates geht daher schnell der Überblick verloren.
Lösung
Also habe ich ein Code Snippet entwickelt, das in der Liste der Oxygen Templates in einer neuen Spalte "Application" die definierten Bedingungen für jedes Template anzeigt.
Entwicklung
In diesem Artikel beschreibe ich, wie ich die Anforderung in einem Code Snippet umgesetzt habe.
Damit will ich einen Einblick geben, wie die Entwicklung abläuft und wie in diesem Fall das Endergebnis zu erreichen ist.
Gewisse Grundkenntnisse in der WordPress Entwicklung sind Voraussetzung zum Verständnis dieses Artikels.
Datenquelle
Zur Lösung dieser Aufgabenstellung muss ich zunächst nachvollziehen, wie die Konditionen für die Verwendung der Templates in der WordPress Datenbank gespeichert werden. Dazu erstelle ich mir ein Template "Demo", bei dem ich alle Konditionen anhake bzw. ausfülle.
In der Tabelle postmeta
schaue ich mir die zugehörigen Metadaten an:
Die Struktur und Bedeutung der Daten ist mir recht schnell klar.
Code Snippet
Meine favorisierte Methode, um Code zu implementieren, ist das Plugin Code Snippets. Hier erstelle ich ein neues Snippet.
Es muß nur in der WordPress Administrations-Ansicht ausgeführt werden. Also setze ich den Schalter "Only run in administration areas".
Neue Spalte in Template Liste
Aus vorherigen Projekten kenne ich bereits das Verfahren, wie man in WordPress zu administrativen Listenansichten neue Spalten hinzufügen und diese mit Daten füllen kann.
Im Grunde sind das zwei Schritte, für die WordPress Filter Hooks zur Verfügung stellt:
- Spalte mit Titel hinzufügen über einen Filter Hook zu
manage_xxx_posts_columns
, wobei dasxxx
ersetzt wird durch den Post Typ Slug (post
,page
, ...) - Spalten-Inhalt hinzufügen für einen Filter Hook zu
manage_xxx_posts_custom_column
, wobei dasxxx
... siehe oben.
Die Oxygen Templates haben den Post Type ct_template
.
So ergibt sich die weltverändernde Formel xxx = ct_template
.
Aus diesen Erkenntnissen kann ich in meinem Code Snippet das Grundgerüst wie folgt erstellen:
<?php // add column title add_filter('manage_ct_template_posts_columns', 'maltmann_oxygen_template_table_head'); function maltmann_oxygen_template_table_head( $columns ) { $columns['maltmann_oxy_tpl_apply'] = 'Application'; return $columns; } // add column content add_action( 'manage_ct_template_posts_custom_column', 'maltmann_oxygen_template_table_content', 10, 2 ); function maltmann_oxygen_template_table_content( $column_name, $post_id ) { if ($column_name != 'maltmann_oxy_tpl_apply') return; echo 'my column content'; }
In Zeile 2 füge ich einen neuen Filter zum Hook manage_ct_template_posts_columns
hinzu.
Hier ist xxx
schon ersetzt durch den korrekten Post Type ct_template
.
Dieser Hook ruft meine eigene Funktion maltmann_oxygen_template_table_head()
auf.
Im Parameter $columns
wird ein Array mit den bisher registrierten Tabellen-Spalten an meine Funktion übergeben.
Ich füge dem Array meine neue Spalte namens maltmann_oxy_tpl_apply
hinzu und gebe das neue Array $columns
zurück.
Danach folgt in Zeile 9 mein Filter für den Hook manage_ct_template_posts_custom_column
, mit dem ich den Spalteninhalt ausgebe.
Ganz zu Beginn prüfe ich in Zeile 11, ob der Hook tatsächlich für meine neue Spalte aufgerufen wurde. Wenn nicht, raus hier!
Danach gebe ich in Zeile 12 den Inhalt der Spalte aus. Machen wir erst mal ein provisorisches "my column content".
Speichern. Testen.
Sieht doch schon akzeptabel aus:
Metadaten ermitteln
Um überhaupt aus meinem Snippet auf die in der Tabelle postmeta
gespeicherten Daten zugreifen zu können, muss ich sie erst mal einlesen.
Das geht ganz gut mit der Funktion get_post_meta()
.
Da diese Funktion zwingend einen Parameter $post_id
haben will, muss ich mir also erst mal Zugriff auf den aktuellen $post
verschaffen.
<?php function maltmann_oxygen_template_table_content( $column_name, $post_id ) { if ($column_name != 'maltmann_oxy_tpl_apply') return; global $post; $meta = get_post_meta($post->ID); }
Das macht mir erst ein bisschen Sorgen, weil die Funktion get_post_meta()
ohne Einschränkung auf bestimmte Meta-Daten erst mal ganz stupide alle Meta-Daten zu diesem $post
aus der Datenbank liest. Da wären auch alle zig Revisionen zu jedem Template dabei. Das will ich natürlich vermeiden.
Per Debugging finde ich allerdings schnell heraus, dass die Metadaten bereits bei der Auflistung der Templates in einem Rutsch gelesen wurden, und ich mit der Funktion get_post_meta()
auf die bereits im Cache befindlichen Daten zugreife. Alles fein.
Spalte beleben: 1) Other
Am einfachsten erscheint mir die Bedingungen für den Abschnitt "Other".
Obwohl die Zuordnung ziemlich klar ist, setze und lösche ich diese Bedingungen abwechselnd, um die Änderungen in der Datenbank bestätigt zu wissen.
Mit diesen Erkenntnissen kann ich die ersten Konditionen in meiner neuen Spalte ausgeben.
<?php function maltmann_oxygen_template_table_content( $column_name, $post_id ) { if ($column_name != 'maltmann_oxy_tpl_apply') return; global $post; // get_post_meta does not fire a db query since post meta is still cached from listing $meta = get_post_meta($post->ID); // --- Others ---------------------------------------------------------- // collect settings for Oxygen's Other section $apply = []; if (@$meta['ct_template_front_page'][0] == 'true') {$apply[] = 'Front Page';} if (@$meta['ct_template_blog_posts'][0] == 'true') {$apply[] = 'Blog Posts Index';} if (@$meta['ct_template_search_page'][0] == 'true') {$apply[] = 'Search Page';} if (@$meta['ct_template_404_page'][0] == 'true') {$apply[] = '404';} if (@$meta['ct_template_inner_content'][0] == 'true') {$apply[] = 'Inner Content';} if (@$meta['ct_template_index'][0] == 'true') {$apply[] = '<i>Catch All</i>';} // Others Final Output if (count($apply)) {echo '<b>Others:</b><br/>' . join('<br/>',$apply).'<br/>';} }
In Zeile 10 definiere ich erst mal ein Array $apply
, in dem ich alle Konditionen speichern werde.
Anschließend kann ich alle zuvor identifizierten Konditionen aus den Metadaten lesen und in verständlicher Form in meinem Array speichern.
WordPress liefert Metadaten als Array. Deswegen muss ich jeweils mit [0]
auf das erste Element im Array zugreifen. Und da nicht sicher gestellt ist, dass ich überhaupt valide Daten zurück bekomme, greife ich mit dem Präfix @
auf die Elemente zu. Das unterdrückt Warnungen im Debug Log.
Im Anschluß prüfe ich in Zeile 18, ob ich im Abschnitt "Other" irgendwelche Konditionen gesammelt habe und gebe diese ein bisschen aufgehübscht aus.
Speichern. Testen.
Ich bin zufrieden:
Spalte beleben: 2) Singular
Nächste Schwierigkeitsstufe sind die Konditionen im Abschnitt "Singular". Hier haben wir die Definition einzelner Post Typen, Taxonomien und Parent IDs. Das wird schon spannender.
In der Datenbank identifiziere ich folgende Verknüpfungen:
Neben den einfachen boolschen Feldern sehe ich hier serialisiert gespeicherte Daten ("a:2:{i:0;s:11...
"). Die muss ich erst mal in Datenstrukturen zurück wandeln. In PHP geht das normalerweise mit unserialize()
. WordPress bietet aber mit maybe_unserialize()
eine noch praktischere Funktion, die nicht gleich mit Fehler auf die Nase fällt, wenn die Daten sich nicht deserialisieren lassen.
Der zusätzliche Code sieht wie folgt aus:
<?php // --- Singular -------------------------------------------------------- // collect settings for Oxygen's Sigular section $apply= []; // Singular Post Types $post_types = []; if (@$meta['ct_template_single_all'][0] == 'true') {$post_types[] = '<i>All</i>';} $specific_post_types = maybe_unserialize(@$meta['ct_template_post_types'][0]); if (is_array($specific_post_types) && count($specific_post_types)) { $post_types = array_merge($post_types,$specific_post_types); } if (is_array($post_types) && count($post_types)) { $apply[] = 'Post Types: ' . join(', ',$post_types); } // Singular Taxonomies if (@$meta['ct_use_template_taxonomies'][0]=='true') { $taxonomies = maybe_unserialize(@$meta['ct_template_taxonomies'][0]); if (is_array($taxonomies) && count($taxonomies)) { $apply[] = 'Taxonomies: ' . join(', ',$taxonomies); } } // Singular Parents if (@$meta['ct_template_apply_if_post_of_parents'][0]=='true') { $parents = maybe_unserialize(@$meta['ct_template_post_of_parents'][0]); $apply[] = 'Parent IDs: '.join(', ',$parents); } // Singular Final Output if (count($apply)) {echo '<b>Singular:</b><br/>'.join('<br/>',$apply).'<br/>';}
Speichern. Testen.
Noch nicht ganz, wie ich mir das vorstelle:
Dann brauche ich wohl noch ein paar Hilfsfunktionen, um Post Typen lokalisiert darzustellen und Taxonomien (Kategorien, Schlagworte) von ID (bzw. Array) in Text umzuwandeln.
<?php //----------------------------------------------------------------------------- // Helper function to translate post type slugs (page, post, ...) to localized names (Seiten, Beiträge, ...) function _maltmann_parse_oxy_post_types($post_types) { $retval = []; if (is_array($post_types) && count($post_types)) { foreach($post_types as $post_type) { $post_type_object = get_post_type_object($post_type); if ($post_type_object) {$retval[] = $post_type_object->label;} } } return $retval; } //------------------------------------------------------------------------------ // Helper function to retrieve term names from term IDs list or Oxygen names,values array function _maltmann_parse_oxy_taxonomies($taxonomies) { $retval = []; if (is_array($taxonomies) && count($taxonomies)) { if (array_key_exists('values',$taxonomies)) {$taxonomies = $taxonomies['values'];} foreach ($taxonomies as $term_id) { $term = get_term($term_id); if ($term) {$retval[] = $term->name;} } } return $retval; }
Die Funktion _maltmann_parse_oxy_post_types()
ermittelt die lokalisierte Bezeichnung für Post Types.
Mit der Funktion _maltmann_parse_oxy_taxonomies()
ermittle ich anhand von IDs die tatsächlichen Namen der Taxonomien, also Kategorien und Schlagworte. Dabei kann der Funktion ein einfaches Array mit IDs übergeben werden, als auch ein Array mit den Feldern names
und values
. Das sind zwei Varianten, in denen Oxygen Taxonomien für verschiedene Konditionen speichert. Der interessierte Leser darf das gerne selbst im Detail debuggen. Für 5 Euro ergänze ich hier die detaillierten Erklärungen.
In beiden Hilfsfunktionen habe ich meines Erachtens ausreichende Fehlerbehandlung implementiert, um für alle Fälle gewappnet zu sein.
Die Funktion maltmann_oxygen_template_table_content()
muss jetzt natürlich noch ein bisschen angepasst werden, um die neuen Funktionen zu implementieren.
<?php // --- Singular -------------------------------------------------------- // collect settings for Oxygen's Sigular section $apply= []; // Singular Post Types $post_types = []; if (@$meta['ct_template_single_all'][0] == 'true') {$post_types[] = '<i>All</i>';} $specific_post_types = maybe_unserialize(@$meta['ct_template_post_types'][0]); if (is_array($specific_post_types) && count($specific_post_types)) { $post_types = array_merge($post_types,_maltmann_parse_oxy_post_types($specific_post_types)); } if (is_array($post_types) && count($post_types)) { $apply[] = 'Post Types: ' . join(', ',$post_types); } // Singular Taxonomies if (@$meta['ct_use_template_taxonomies'][0]=='true') { $taxonomies = maybe_unserialize(@$meta['ct_template_taxonomies'][0]); $taxonomies = _maltmann_parse_oxy_taxonomies($taxonomies); if (is_array($taxonomies) && count($taxonomies)) { $apply[] = 'Taxonomies: ' . join(', ',$taxonomies); } } // Singular Parents if (@$meta['ct_template_apply_if_post_of_parents'][0]=='true') { $parents = maybe_unserialize(@$meta['ct_template_post_of_parents'][0]); $apply[] = 'Parent IDs: '.join(', ',$parents); } // Singular Final Output if (count($apply)) {echo '<b>Singular:</b><br/>'.join('<br/>',$apply).'<br/>';}
Speichern. Testen.
Spalte beleben: 3) Archives
Jetzt wird es lustig. Der Abschnitt "Archives" besteht nun zum größten Teil aus Referenzen zu irgendwas.
Ich suche mir wieder die Referenzen in der Datenbank:
Für Post Types und Taxonomien habe ich schon Hilfsfunktionen. Für Autoren (Benutzer) brauche ich eine neue Hilfsfunktion:
<?php //------------------------------------------------------------------------------ // retrieves author IDs to names function _maltmann_parse_oxy_authors($authors) { $retval = []; if (is_array($authors) && count($authors)) { foreach ($authors as $user_id) { if ($user_id == 'all_authors') {$retval[] = '<i>All</i>'; continue;} $user = get_userdata($user_id); if ($user) {$retval[] = $user->user_login;} } } return $retval; }
Auch diese Hilfsfunktion kann gut mit Fehlern umgehen.
Danach kann ich die Konditionen für den Abschnitt "Archive" mit den bereits vorhandenen Mitteln aufbereiten.
<?php // --- Archive --------------------------------------------------------- // collect settings for Oxygen's Archive section $apply = []; // All Archives if (@$meta['ct_template_all_archives'][0]=='true') {$apply[] = '<i>All</i>';} // Archive Taxonomies if (@$meta['ct_template_apply_if_archive_among_taxonomies'][0]=='true') { $taxonomies = maybe_unserialize(@$meta['ct_template_archive_among_taxonomies'][0]); $taxonomies = _maltmann_parse_oxy_taxonomies($taxonomies); if (is_array($taxonomies) && count($taxonomies)) { $apply[] = 'Taxonomies: ' . join(', ',$taxonomies); } } // Archive Post Types if (@$meta['ct_template_apply_if_archive_among_cpt'][0]=='true') { $post_types = maybe_unserialize(@$meta['ct_template_archive_post_types'][0]); $post_types = _maltmann_parse_oxy_post_types($post_types); if (is_array($post_types) && count($post_types)) { $apply[] = 'Post Types: ' . join(', ',$post_types); } } // Archive Authors if (@$meta['ct_template_apply_if_archive_among_authors'][0]=='true') { $authors = maybe_unserialize(@$meta['ct_template_authors_archives'][0]); $authors = _maltmann_parse_oxy_authors($authors); if (is_array($authors) && count($authors)) { $apply[] = 'Authors: ' . join(', ',$authors); } } // Archive Date if (@$meta['ct_template_date_archive'][0] == 'true') {$apply[] = 'Date Archive';} // Archive Final Output if (count($apply)) {echo '<b>Archive:</b><br/>'.join('<br/>',$apply).'<br/>';}
Speichern. Testen.
DAS sieht gut aus!