Es gibt zahlreiche Plugins für Wordpress, die dafür sorgen, dass Kommentare von Besuchern per AJAX eingetragen werden. Die meisten davon schaffen es nicht lange mit den aktuellen Versionen von Wordpress kompatibel zu bleiben und diejenigen die es schaffen, sind so überladen mit Schnickschnack und hässlichen Frontendgestaltungen, dass ich soviel Zeit brauche alles nach meinen Wünschen anzupasssen, da kann ich gleich alles von vornerein neu machen.
Zuerst mal brauche ich dafür ein PHP-Skript, dass nicht nur die Formulardaten verarbeitet und bei erfolgreicher Überprüfung ein neues Kommentar anlegt, sondern auch eine Ausgabe erzeugt, die man mit Javascript leicht verarbeiten kann.
Die normale Kommentarverarbeitung in Wordpress 2.8+
Ohne AJAX übernimmt die Datei wp-comments-post.php, die sich im Wordpress-Stammverzeichnis befindet, das Verarbeiten des Kommentarformulars. Kann ein Kommentar, aus welchen Gründen auch immer, nicht eingetragen werden, gibt das Skript über die Wordpress-interne Funktion wp_die() eine kleine HTML-Seite mitsamt Fehlerbeschreibung aus. Sollte nichts gegen das Erstellen des Kommentars sprechen, wird es angelegt und mit der ebenfalls Wordpress-internen Funktion wp_redirect() wird auf die zum Kommentar gehörende Artikelseite weitergeleitet.
Die Vermeidung von Doppelpostings und Flooding wird nicht direkt innerhalb von wp-comment-post.php geregelt, sondern im Zuge der Funktion wp_new_comment(). Die per Filter im Laufe der Funktion angewandten Funktionen wp_allow_comment() und check_comment_flood_db() (beide definiert in wp-includes/comment.php) sind dafür verantwortlich und liefern bei positivem Befund ebenfalls eine HTML-Seite per wp_die(). Aber hier haben die Wordpress-Entwickler bereits Vorarbeit geleistet: Ist eine Konstante namens DOING_AJAX definiert, wird bei Doppelpostings und Flooding stattdessen direkt die() verwendet und die Fehlermeldung als reiner Text ausgegeben.
Los gehts…
Ich nehme mir also die Datei wp-comments-post.php als Vorlage für mein PHP-Skript zur Formularverarbeitung mit AJAX. Dazu erstelle ich eine Kopie der Datei unter dem Namen new-comment.php und speichere diese im Unterverzeichnis ajax meines Themeordners. Falls bei einem zukünftigen Update von Wordpress die wp-comments-post.php verändert wird und ich somit meine new-comment.php neu erstellen muss, markiere ich vorsichtshalber alle geänderten Stellen im Quelltext um die Anpassungen später besser nachvollziehen und wiederholen zu können. Ich habe auf Wordpress.org auch bereits den Vorschlag gemacht, diese Anpassungen von Haus aus in wp-comments-post.php zu integrieren um die Implementierung von AJAX Kommentaren in Zukunft zu vereinfachen.
Damit das Skript innerhalb der Wordpress-Umgebung ausgeführt wird, muss der Pfad zu wp-load.php ziemlich am Anfang der Datei angepasst werden. Direkt davor füge ich define('DOING_AJAX',1); ein. Damit anstatt der HTML-Fehlerseiten reine Textnachrichten ausgegeben werden, ersetze ich alle Vorkommen von wp_die() durch die Funktion die(). Die letzten 3 Zeilen der Datei, die verantwortlich für den Redirect sind, kommentiere ich aus. In der Variablen $comment ist nun das Objekt des eben erstellten Kommentars gespeichert. Nachdem ich den Content des Kommentars gefiltert und das Datum formatiert habe, verwandle ich das Objekt in einen JSON-String und gebe ihn per die() aus. Das PHP-Skript liefert nun also im Fehlerfall eine Textmeldung zurück und im Erfolgsfalls das eben erstellte Kommentar.
Meine new-comment.php sieht jetzt so aus:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 | /**----------------------------------------------------------------------------- * start /wp-comments-post.php * changes start with //## and end with //^^ */ if ( 'POST' != $_SERVER['REQUEST_METHOD'] ) { header('Allow: POST'); header('HTTP/1.1 405 Method Not Allowed'); header('Content-Type: text/plain'); exit; } /** Sets up the WordPress Environment. */ //## require( dirname(__FILE__) . '/wp-load.php' ); define('DOING_AJAX',1); require '../../../../wp-load.php' ; //^^ nocache_headers(); $comment_post_ID = (int) $_POST['comment_post_ID']; $status = $wpdb->get_row( $wpdb->prepare("SELECT post_status, comment_status FROM $wpdb->posts WHERE ID = %d", $comment_post_ID) ); if ( empty($status->comment_status) ) { do_action('comment_id_not_found', $comment_post_ID); exit; } elseif ( !comments_open($comment_post_ID) ) { do_action('comment_closed', $comment_post_ID); //## wp_die( __('Sorry, comments are closed for this item.') ); die( __('Sorry, comments are closed for this item.') ); //^^ } elseif ( in_array($status->post_status, array('draft', 'pending') ) ) { do_action('comment_on_draft', $comment_post_ID); exit; } else { do_action('pre_comment_on_post', $comment_post_ID); } $comment_author = ( isset($_POST['author']) ) ? trim(strip_tags($_POST['author'])) : null; $comment_author_email = ( isset($_POST['email']) ) ? trim($_POST['email']) : null; $comment_author_url = ( isset($_POST['url']) ) ? trim($_POST['url']) : null; $comment_content = ( isset($_POST['comment']) ) ? trim($_POST['comment']) : null; // If the user is logged in $user = wp_get_current_user(); if ( $user->ID ) { if ( empty( $user->display_name ) ) $user->display_name=$user->user_login; $comment_author = $wpdb->escape($user->display_name); $comment_author_email = $wpdb->escape($user->user_email); $comment_author_url = $wpdb->escape($user->user_url); if ( current_user_can('unfiltered_html') ) { if ( wp_create_nonce('unfiltered-html-comment_' . $comment_post_ID) != $_POST['_wp_unfiltered_html_comment'] ) { kses_remove_filters(); // start with a clean slate kses_init_filters(); // set up the filters } } } else { if ( get_option('comment_registration') || 'private' == $status->post_status ) //## wp_die( __('Sorry, you must be logged in to post a comment.') ); die( __('Sorry, you must be logged in to post a comment.') ); //^^ } $comment_type = ''; if ( get_option('require_name_email') && !$user->ID ) { if ( 6 > strlen($comment_author_email) || '' == $comment_author ) //## wp_die( __('Error: please fill the required fields (name, email).') ); die( __('Error: please fill the required fields (name, email).') ); //^^ elseif ( !is_email($comment_author_email)) //## wp_die( __('Error: please enter a valid email address.') ); die( __('Error: please enter a valid email address.') ); //^^ } if ( '' == $comment_content ) //## wp_die( __('Error: please type a comment.') ); die( __('Error: please type a comment.') ); //^^ $comment_parent = isset($_POST['comment_parent']) ? absint($_POST['comment_parent']) : 0; $commentdata = compact('comment_post_ID', 'comment_author', 'comment_author_email', 'comment_author_url', 'comment_content', 'comment_type', 'comment_parent', 'user_ID'); $comment_id = wp_new_comment( $commentdata ); $comment = get_comment($comment_id); if ( !$user->ID ) { $comment_cookie_lifetime = apply_filters('comment_cookie_lifetime', 30000000); setcookie('comment_author_' . COOKIEHASH, $comment->comment_author, time() + $comment_cookie_lifetime, COOKIEPATH, COOKIE_DOMAIN); setcookie('comment_author_email_' . COOKIEHASH, $comment->comment_author_email, time() + $comment_cookie_lifetime, COOKIEPATH, COOKIE_DOMAIN); setcookie('comment_author_url_' . COOKIEHASH, esc_url($comment->comment_author_url), time() + $comment_cookie_lifetime, COOKIEPATH, COOKIE_DOMAIN); } //## // $location = empty($_POST['redirect_to']) ? get_comment_link($comment_id) : $_POST['redirect_to'] . '#comment-' . $comment_id; // $location = apply_filters('comment_post_redirect', $location, $comment); // wp_redirect($location); $comment->comment_content = apply_filters('comment_text',$comment->comment_content); $comment->comment_date = date('l, j. F Y G:i',strtotime($comment->comment_date)); die(json_encode($comment)); //^^ /**----------------------------------------------------------------------------- * end /wp-comments-post.php */ |
Jetzt da ich eine Datei habe, die meine AJAX-Anfrage verarbeiten und beantworten kann, kann ich das zugehörige Javascript basteln. Ein div wird beim Formular eingefügt um den Ladestatus oder eine Fehlermeldung anzuzeigen. Der Rest ist ein simpler jQuery.ajax()-Aufruf, der beim success-Event den zurückgelieferten Text als Fehlermeldung in besagtem div anzeigt oder, sollte der Text mit einem { beginnen, ihn als JSON-Objekt des neuen Kommentars behandelt. Aus den Kommentardaten lasse ich ein HTML-Konstrukt erstellen, das dem Kommentarlisteneintrag meines Templates entspricht, und füge es in dann in die Liste ein. Sollte es auf der Seite noch keine Kommentare geben, wird der gesamte notwendige Bereich mitsamt Überschrift erstellt und eingefügt. Den HTML-Teil und das Einreihen in das DOM sollte man natürlich an sein jeweiliges Theme anpassen. Bei mir sieht das dann so aus:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 | jQuery(document).ready( function() { // ajax comments if( jQuery('#commentform').length>0 ) { var ac = { form: jQuery('#commentform'), response: jQuery('<div id="response" />').hide() }; ac.form.before(ac.response).submit(function(){ jQuery.ajax({ type: 'POST', url: TPLDIR+'ajax/new-comment.php', dataType: 'text', data: ac.form.serialize(), beforeSend: function() { ac.response.empty().removeClass('error success').addClass('loading').slideDown('fast'); }, success: function(r){ if( r.substr(0,1)!='{' ) ac.response.empty().removeClass('loading').addClass('error').text(r); else { ac.form.find('textarea').val('') ac.response.empty().removeClass('loading').addClass('success').text('Dein Kommentar wurde erfolgreich eingetragen.'); c = eval('('+r+')'); var comment_count = jQuery('ol.commentlist li').length; var comment_html = '<li id="comment-'+c.comment_ID+'" class="comment'+(c.user_id>0?' user':'')+(comment_count==0?' first':'')+'">' +'<div class="comment-count">'+(parseInt(comment_count)+1)+'</div>' +'<div class="comment-author">'+(c.comment_author_url!=''?'<a href="'+c.comment_author_url+'">'+c.comment_author+'</a>':c.comment_author)+'</div>' +'<div class="comment-date" title="'+c.comment_date+'">vor einer Sekunde'+(c.comment_approved==0?' (noch nicht freigeschaltet)':'')+'</div>' +'<div class="entry">'+c.comment_content+'</div>' +'</li>'; if( comment_count == 0 ) jQuery('<div class="comments"><h3 class="post-title">Bisher eine Reaktion</h3><ol class="commentlist">'+comment_html+'</ol></div>').hide().insertBefore('#respond').slideDown('medium'); else { jQuery('#comments h3').text('Bisher '+(parseInt(comment_count)+1)+' Reaktionen') jQuery(comment_html).hide().appendTo('ol.commentlist').slideDown('medium'); } } jQuery('#submit').blur(); }, error: function(XMLHttpRequest, textStatus, errorThrown){ ac.response.empty().removeClass('loading').addClass('error').text("Fehler: Die AJAX-Anfrage ist fehlgeschlagen ("+textStatus+")"); jQuery('#submit').blur(); } }); return false; }); } }); |
Damit alles noch akzeptabel aussieht, braucht es noch eine Loading-Animation, die ich mir bei Ajaxload.info erstelle, und eine Priese CSS:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | /*----- ajax comments -----*/ #response { height:3em; } #response.loading { background: transparent url('images/loading.gif') top left no-repeat; } #response.success { color:#222222; } #response.error { color:#aa1010; } |
Fertig.
Die Kommentare werden nun per AJAX eingetragen, es werden Ladestatus, Fehler- bzw. Erfolgsmeldungen am Formular angezeigt und das neue Kommentar wird direkt in die Kommentarliste eingefügt. Verschachtelte Kommentare habe ich in diesem Skript nicht beachtet, da ich sie bisher nicht nutze. Da aber im JSON-Objekt des Kommentars auch die ID des Elternkommentars enthalten ist, dürfte es ein Leichtes sein, die entsprechende Stelle im DOM zu finden und es dort einzufügen.
schön
ist das auch ein ajax kommentar
ja :)
Hallo Simon!
Tolles Script, gratulation – aber leider ist dieses Script wie viele andere im Web… Ab so 20 Kommentaren wird die Seite unendlich lange.
Dann muss man erst mal ewig lange ans Ende scrollen um die Eingabemaske zu finden.
Es sollte umgekehrt stattfinden. Die Kommentarbox ganz oben, darunter die Kommentare – das neueste zuerst. Nach 10 Kommentaren gibt es einen Link “Weitere 10 Kommentare einblenden”. Drückt man drauf holt Freund Ajax neue Komentare ab. Wenn keine weiteren Kommentare mehr vorhanden sind, verschwindet auch der “10 Komentare mehr” Button.
Was hälst du von dieser Erweiterung?
Liebe Grüße und Danke für dein tolles Script,
Bernhard
Hallo Bernhard,
danke fürs Lob und deine Vorschläge…
Deine Vorschläge sind allerdings weniger eine Erweiterung des bestehenden Scripts, sondern viel mehr ein völlig neues, unabhängiges Script… :)
Das Script hier kümmert sich ausschließlich um das Eintragen und unmittelbare Anzeigen des neuen Kommentars. Was du benötigst, ist ein Javascript, dass per AJAX die bestehenden Kommentare lädt und so eine Art Blätter-Funktion ermöglicht.
Wenn du das Kommentarformular oberhalb der Kommentare haben möchtest, hat das nichts mit dem AJAX-Kommentare-Script zu tun. Dazu musst du nur in deinem Template den HTML-Teil des Formulars vor den HTML-Teil der Kommentare setzen.
Um nun per AJAX durch die Kommentare zu blättern, brauchst du nur eine weitere PHP-Datei, die die gewünschten Kommentare als JSON-Objekt oder HTML-Schnipsel liefert. Diese muss natürlich – wie auch die new-comment.php hier im Beitrag – innerhalb der Wordpress-Umgebung geladen werden (durch Einbinden von wp-load.php). Und die von diesem Script gelieferten Daten werden dann per jQuery in die Kommentarliste eingereiht bzw ersetzen sie. Eigentlich relativ easy – man muss es eben an das jeweilige Theme anpassen…
Das hier vorgestellte Script ist allerdings in anderer Hinsicht noch ausbaufähig. Es beachtet weder verschachtelte Kommentare noch Kommentare die auf mehrere Seiten verteilt sind. Vielleicht komm ich irgendwann mal noch dazu zu zeigen wie das geht… :)
Gruß
Simon
Cool, funktioniert das auch noch mit der aktuellen WordPress Version?
Ja. Sollte sich die
wp-comments-post.phpgeändert haben, muss eine neuenew-comment.phpentsprechend der Beschreibung erstellt werden. Aber grundsätzlich funktioniert diese Methode mit jeder Wordpress-Version, auch zukünftigen.Dann werde ich das auch mal ausprobieren. :-)
Ich hätte da noch eine Frage. Wie hast Du das mit den Zeitangaben gemacht? Also zum Beispiel “vor einer viertel Stunde”, “vor 4 Minuten” usw.
Siehe pretty_date :)