Custom Upvote / Like Funktion für WordPress Posts

Julian Lang
Julian Lang

12. Jul, 2022 | 7 Min. Lesezeit

Neulich habe ich für jadento.de eine Like / Upvote Funktion eingebaut. Ein kleiner Button um einen Post hochzuvoten.

jadento.de – Upvote / Like Button auf Card

Der Upvote Button speichert die Anzahl der Upvotes pro post_id in eine eigene Tabelle (meta_upvotes). Damit ein Visitor nicht ständig den gleichen Beitrag oder Post hochvoten kann, speichern wir die visitor_id (guid) zusätzlich pro post_id in einer eigenen Tabelle (meta_visitor_upvotes).

Wichtig: Bevor du hier weitermachst lese dir meinen Beitrag: Website Besucher identifizieren via Cookie und Token. durch und füge den Code wie dort beschrieben vorher in dein Theme oder Plugin, damit wir den visitor_token als Cookie verfügbar haben.

Tabelle: Upvotes

Um für jeden Post die Upvotes zu erfassen, legen wir die Tabelle meta_upvotes an.

namespace JL\Database\Upvotes\Upvotes;

class Db
{

    public static $table_name = 'meta_upvotes';

    public static function setup_database() {
        global $wpdb;

        $table_name = $wpdb->prefix . self::$table_name;

        $queries = [];

        $charset_collate = $wpdb->get_charset_collate();

        $queries[] = "
        CREATE TABLE $table_name (
        id bigint(20) NOT NULL AUTO_INCREMENT,
        time datetime DEFAULT NOW(),
        post_id bigint(20) NOT NULL,
        vote_count bigint(20) DEFAULT NULL,
        PRIMARY KEY  (id)
        ) $charset_collate;
        ";

        include_once ABSPATH . 'wp-admin/includes/upgrade.php';
        dbDelta($queries);
    }

    /**
     * Einzelnen Eintrag aus der Tabelle erhalten
     */
    public static function get( $field, $value, $fieldtype ) {

        global $wpdb;
        $table_name = $wpdb->prefix . self::$table_name;
        
        if ( $fieldtype = 'int' ) {
            $sql = "SELECT * FROM $table_name WHERE $field = %d";
        }
        if ( $fieldtype = 'float' ) {
            $sql = "SELECT * FROM $table_name WHERE $field = %f";
        }
        if ( $fieldtype = 'string' ) {
            $sql = "SELECT * FROM $table_name WHERE $field = %s";
        }

        $dataset = $wpdb->get_row($wpdb->prepare($sql, $value));
        
        return $dataset;
    }

    /**
     * Etwas in die upvotes Tabelle packen
     */
    public static function insert(array $args = []) {
    
        $defaults = [
            'post_id' => null,
            'time' => gmdate('Y-m-d H:i:s'),
        ];

        $values = wp_parse_args($args, $defaults);

        if ( is_null($values['post_id']) ) {
            return new \WP_Error("Es wurde keine post_id angegeben");
        }

        if ( $existing = self::get( 'post_id', (int) $values['post_id'], 'int') ) {
            return $existing;
        }

        global $wpdb;
        $table_name = $wpdb->prefix . self::$table_name;

        $wpdb->insert($table_name, $values);

        return $wpdb->insert_id;
    }


    /**
     * Etwas in der upvotes Tabelle updaten
     */
    public static function update(array $args = []) {
        
        $defaults = [
            'post_id' => null,
            'time' => gmdate('Y-m-d H:i:s'),
        ];
    
        $where = wp_parse_args($args, $defaults);
    
        if ( is_null($where['post_id']) ) {
            return new \WP_Error("Es wurde keine post_id angegeben");
        }

        $already_exist = self::get('post_id', (int) $where['post_id'], 'int' );
        
        if ( !empty($already_exist) ) {
            
            global $wpdb;
            $table_name = $wpdb->prefix . self::$table_name;
            $wpdb->update($table_name, $where, ['post_id' => $where['post_id']]);
            
        } else {
            do_action( 'upvotes/insert', $where );
        }

    }

}

Außerdem erstellen wir 2 Actions (update, insert) und initialisieren die Tabelle

add_action( 'init', [ '\JL\Database\Upvotes\Upvotes\DB', 'setup_database' ] );
add_action('upvotes/insert', [ '\JL\Database\Upvotes\Upvotes\DB', 'insert' ]  );
add_action('upvotes/update', [ '\JL\Database\Upvotes\Upvotes\DB', 'update' ]  );

Tabelle: Visitor Upvotes

In der Tabelle meta_visitor_upvotes speichern wir die abgegbenen Votes anhand der Visitor ID die jeder Webseitenbesucher erhält und als Cookiewert verfügbar ist um später zu überprüfen ob ein Visitor bereits einen Post hochgevoted / geliked hat.

namespace JD\Database\Upvotes\Visitorupvotes;

class Db
{

    public static $table_name = 'meta_visitor_upvotes';

    public static function setup_database() {
        global $wpdb;

        $table_name = $wpdb->prefix . self::$table_name;

        $queries = [];

        $charset_collate = $wpdb->get_charset_collate();

        $queries[] = "
        CREATE TABLE $table_name (
        id bigint(20) NOT NULL AUTO_INCREMENT,
        time datetime DEFAULT NOW(),
        post_id bigint(20) NOT NULL,
        guid varchar(255) DEFAULT NULL,
        PRIMARY KEY  (id)
        ) $charset_collate;
        ";

        include_once ABSPATH . 'wp-admin/includes/upgrade.php';
        dbDelta($queries);
    }

    /**
     * Einzelnen Eintrag aus der Tabelle erhalten
     */
    public static function get( $field, $value, $fieldtype ) {

        global $wpdb;
        $table_name = $wpdb->prefix . self::$table_name;
        
        if ( $fieldtype = 'int' ) {
            $sql = "SELECT * FROM $table_name WHERE $field = %d";
        }
        if ( $fieldtype = 'float' ) {
            $sql = "SELECT * FROM $table_name WHERE $field = %f";
        }
        if ( $fieldtype = 'string' ) {
            $sql = "SELECT * FROM $table_name WHERE $field = %s";
        }

        $dataset = $wpdb->get_row($wpdb->prepare($sql, $value));
        
        return $dataset;
    }

    public static function get_required( $guid, $post_id ) {

        global $wpdb;
        $table_name = $wpdb->prefix . self::$table_name;
        
        $sql = "SELECT * FROM $table_name WHERE guid = %s AND post_id = %d";

        $dataset = $wpdb->get_row($wpdb->prepare( $sql, $guid, $post_id ));
        
        return $dataset;
    }


    /**
     * Etwas in die visitorupvotes Tabelle packen
     */
    public static function insert(array $args = []) {
    
        $defaults = [
            'post_id' => null,
            'guid'  =>  null,
            'time' => gmdate('Y-m-d H:i:s'),
        ];

        $values = wp_parse_args($args, $defaults);

        if ( is_null($values['post_id']) ) {
            return new \WP_Error("Es wurde keine post_id angegeben");
        }
        if ( is_null($values['guid']) ) {
            return new \WP_Error("Es wurde keine guid angegeben");
        }

        if ( $existing = self::get_required( $values['guid'], $values['post_id'] ) ) {
            return $existing;
        }

        global $wpdb;
        $table_name = $wpdb->prefix . self::$table_name;

        $wpdb->insert($table_name, $values);

        return $wpdb->insert_id;
    }


    /**
     * Etwas in der visitorupvotes Tabelle updaten
     */
    public static function update(array $args = []) {
        
        $defaults = [
            'post_id' => null,
            'guid'  =>  null,
            'time' => gmdate('Y-m-d H:i:s'),
        ];
    
        $where = wp_parse_args($args, $defaults);
    
        if ( is_null($where['post_id']) ) {
            return new \WP_Error("Es wurde keine post_id angegeben");
        }
        if ( is_null($where['guid']) ) {
            return new \WP_Error("Es wurde keine guid angegeben");
        }

        $already_exist = self::get_required( $where['guid'], $where['post_id'] );
        
        if ( !empty($already_exist) ) {
            
            global $wpdb;
            $table_name = $wpdb->prefix . self::$table_name;
            $wpdb->update($table_name, $where, ['id' => $already_exist->id]);
            
        } else {
            do_action( 'visitorupvotes/insert', $where );
        }

    }

    /**
     * Etwas aus der visitorupvotes Tabelle löschen
     */
    public static function delete(array $args = []) {
        
        $defaults = [
            'post_id' => null,
            'guid'  =>  null
        ];
    
        $where = wp_parse_args($args, $defaults);
    
        if ( is_null($where['post_id']) ) {
            return new \WP_Error("Es wurde keine post_id angegeben");
        }
        if ( is_null($where['guid']) ) {
            return new \WP_Error("Es wurde keine guid angegeben");
        }

        $already_exist = self::get_required( $where['guid'], $where['post_id'] );
        
        if ( !empty($already_exist) ) {
            
            global $wpdb;
            $table_name = $wpdb->prefix . self::$table_name;
            $wpdb->delete($table_name, ['id' => $already_exist->id], ['%d']);
            
        }

    }

}

Ebenfalls hier erstellen wir die zwei Actions update und insert und zusätzlich noch eine Action um einen Eintrag zu löschen (delete).

add_action( 'init', [ '\JL\Database\Upvotes\Visitorupvotes\DB', 'setup_database' ] );
add_action( 'visitorupvotes/insert', [ '\JL\Database\Upvotes\Visitorupvotes\DB', 'insert' ]  );
add_action( 'visitorupvotes/update', [ '\JL\Database\Upvotes\Visitorupvotes\DB', 'update' ]  );
add_action( 'visitorupvotes/delete', [ '\JL\Database\Upvotes\Visitorupvotes\DB', 'delete' ]  );

Upvote Button in dein Template ausgeben

Mit folgender Helper Funktion kannst du den Like Button als HTML in dein Template ausgeben. Hierbei wird auch der bestehende Vote Count aus der Tabelle meta_upvotes ermittelt und überprüft ob der aktuelle Visitor bereits einen Vote Eintrag in der Tabelle: meta_visitor_upvotes besitzt und somit nicht erneut voten kann.

namespace JL\Upvotes;

class Helpers
{
    public static function render_likebutton( $post_id ) {

        $visitor = $_COOKIE['vt'];

        $row = \JL\Database\Upvotes\Upvotes\Db::get('post_id', $post_id, 'int');
        if ( !empty($row) ) {
            $vote_count = $row->vote_count;
        } else {
            $vote_count = 0;
        }

        $visitor_has_voted = false;
        $visitor_vote_row = \JL\Database\Upvotes\Visitorupvotes\Db::get_required( $visitor, $post_id );
        if ( !empty($visitor_vote_row) ) {
            $visitor_has_voted = true;
        }

        $voted_class = '';
        if ( true === $visitor_has_voted ) {
            $voted_class = 'voted';
        }

        $svg = '<?xml version="1.0" encoding="utf-8"?>
        <svg version="1.1" id="thumbsup_icon" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
             viewBox="0 0 20 20" enable-background="new 0 0 20 20" xml:space="preserve">
        <path fill="#FFFFFF" d="M13.648,7.362c-0.133-0.355,3.539-3.634,1.398-6.291c-0.501-0.621-2.201,2.975-4.615,4.603
            C9.099,6.572,6,8.484,6,9.541v6.842C6,17.654,10.914,19,14.648,19C16.017,19,18,10.424,18,9.062C18,7.694,13.779,7.718,13.648,7.362
            z M5,7.457c-0.658,0-3,0.4-3,3.123v4.848c0,2.721,2.342,3.021,3,3.021c0.657,0-1-0.572-1-2.26V9.816C4,8.048,5.657,7.457,5,7.457z"
            />
        </svg>';
        $nonce = wp_create_nonce( 'upvote' );
        $html = '<button class="uk-button like--button like--button__excerpt '.$voted_class.'" data-post-id="'.$post_id.'" data-nonce="'.$nonce.'">
            '.$svg.'
            <span class="uv--count">'.$vote_count.'</span>
        </button>';
        return $html;

    }
}

Hier noch das CSS/LESS für den Upvote / Like Button:

Passe es natürlich noch auf deine Bedürfnisse an. Wie immer verwende ich UiKit3 als CSS Framework und habe hierdurch meine Variablen @global-muted-background, @global-link-color, usw.. diese musst du ersetzen.

.like--button__excerpt {
    transition-duration: 0.4s;
    -webkit-transition-duration: 0.4s; /* Safari */
    background-color: @global-muted-background;
    color: @global-link-color;
    outline: none!important;
    border: none!important;
    border-radius: 3px;
    margin-right: 6px;
    display: flex;
    line-height: 21px!important;
    align-items: center;
    padding: 2px 10px;
    svg {
        display: inline-block;
        width: 15px;
        height: 15px;
        transition: .1s all;
        color: @global-link-color;
        path {
            fill: @global-link-color;
        }
    }
    > span {
        margin-left: 5px;
        font-size: 12px;
        font-weight: 600;
    }
    &:not(.voted) {
        &:hover {
            cursor: pointer;
            opacity: .8;
            transition: .2s all;
            svg {
                transform: rotate(-25deg) translate(-1px, -3px) scale(1.05);
                transition: .2s all;
            }
        }
    }
    &.voted {
        background-color: @global-primary-background;
        color: #fff;
        svg {
            color: #fff;
            path {
                fill: #fff;
            }
        }
    }
}

Javascript für Ajax einbinden

So bindest du deine Javascript Datei upvotes.js ein und übergibst den Visitor Token in das Javascript Objekt UPVOTES .

Für $version siehe den Beitrag: WordPress CSS und Javascript Dateien einbinden + Versionierung

// Visitor Token erhalten aus Cookiewert
$visitor_token = false;
if ( isset($_COOKIE['vt']) && '' !== trim($_COOKIE['vt']) ) {
    $visitor_token = $_COOKIE['vt'];
}

// Javascript einbinden
wp_enqueue_script( 'upvotes', get_template_directory_uri() . '/assets/js/upvotes.js', array('jquery'), $version, true );

// Variablen in Javascript übergeben (Visitor Token...)
wp_localize_script('upvotes', 'UPVOTES', [
    'url'       =>  admin_url('admin-ajax.php'),
    'visitor'   =>  $visitor_token,
    'action'    =>  'upvote',
]);

Javascript: upvotes.js

Erhöht optisch den Vote Count und setzt die voted Klasse, sendet die Upvote Aktion via Ajax und überprüft / setzt im Anschluss nochmal den übergebenen Vote Count und Mode.

(function($) {
    var Upvote = {
        ajax: function() {
            const self = this;
            $(document).on("click touch", ".like--button", function(e) {
                e.preventDefault();

                let $button = $(this);
                let postId = $button.attr('data-post-id');
                let nonce = $button.attr('data-nonce');
                let voted = $button.hasClass('voted');
                let currentUpvoteCount = $button.find('span').text();

                if ( true === voted ) {
                    let newUpvoteCount = parseInt(currentUpvoteCount) - 1;
                    $button.find('span').text(newUpvoteCount);
                    $button.removeClass('voted');
                }
                if ( false === voted ) {
                    let newUpvoteCount = parseInt(currentUpvoteCount) + 1; 
                    $button.find('span').text(newUpvoteCount);
                    $button.addClass('voted');
                }
                
                var formData = {
                    'post_id':  postId,
                    'nonce':    nonce,
                    'visitor':  UPVOTES.visitor,
                    'action':   'upvote'
                }
                
                var request = $.ajax({
                    url:    UPVOTES.url,
                    data:   formData,
                    type:   'POST',
                });

                request.done(function(data) {

                    let upvote_count = data.data.upvotes
                    let mode = data.data.mode

                    $button.find('span').text(upvote_count);

                    if ( 'increase' === mode ) {
                        $button.addClass('voted');
                    }
                    if ( 'decrease' === mode ) {
                        $button.removeClass('voted');
                    }
                    
                });
            });
        }
    }
    Upvote.ajax();

})(jQuery)

Ajax Funktion: Vote Count erhöhen und Vote einem Visitor zuordnen

namespace JL\Upvotes;

class Ajax
{

    public static function click() {
        if(wp_verify_nonce( $_POST[ 'nonce' ], 'upvote' )) {

            if ( !isset($_POST) OR empty($_POST) ) {
                return 0;
            }

            $visitor = $_POST['visitor'];
            $post_id = $_POST['post_id'];

            if ( '' !== trim($visitor) && '' !== trim($post_id) ) {

                $mode = 'increase';

                $existing_visitor_upvote = \JL\Database\Upvotes\Visitorupvotes\Db::get_required( $visitor, $post_id );
                
                if ( !empty($existing_visitor_upvote) ) {
                    $mode = 'decrease';
                    do_action('visitorupvotes/delete', [
                        'post_id'   =>  $post_id,
                        'guid'      =>  $visitor
                    ]);
                } else {
                    do_action('visitorupvotes/update', [
                        'post_id'   =>  $post_id,
                        'guid'      =>  $visitor
                    ]);
                }

                $upvote_entry = \JL\Database\Upvotes\Upvotes\Db::get('post_id', $post_id, 'int');
                if ( !empty( $upvote_entry ) ) {
                    $upvote_count_old = (int) $upvote_entry->vote_count;

                    if ( 'increase' === $mode ) {
                        $upvote_count = $upvote_count_old + 1;
                    }
                    if ( 'decrease' === $mode ) {
                        $upvote_count = $upvote_count_old - 1;
                    }

                } else {
                    $upvote_count = 1;
                }

                do_action('upvotes/update', [
                    'post_id'       =>  $post_id,
                    'vote_count'    =>  $upvote_count
                ]);


                $data = [
                    'upvotes'   =>  $upvote_count,
                    'mode'      =>  $mode
                ];

                wp_send_json_success( $data );

            }

            $data = [
                'message' =>  'Es fehlen Pflichtfelder'
            ];
            
            wp_send_json_error( $data );
        }
    }
}

Das Ergebnis

War der Beitrag hilfreich?

geschrieben von

Autor Avatar
Julian Lang

ist PHP / WordPress Entwickler. Arbeitet außerdem als Allrounder bei docrelations.de und entwickelt zwei coole Projekte: jadento.de | lifeisabinge.com

Schreibe einen Kommentar

© 2015 - 2022 | Julian Lang Webentwickler | WordPress Entwickler | Webdesigner