<?php

/*
 * Plugin Name: Ajax Pageview
 * Description: Contabiliza os pageviews por ajax
 * Author: hacklab/
 * Author URI: https://hacklab.com.br/
 * Version: 2.0.3
 */

class AjaxPageviews {
    /**
     *
     * @var wpdb
     */
    static $wpdb;

    static $post_types = [];

    static function init() {
        global $wpdb;

        self::$wpdb = $wpdb;

        add_action('wp_enqueue_scripts', [__CLASS__, 'scripts']);
        add_action('wp_ajax_nopriv_ajaxpv', [__CLASS__, 'action']);
        add_action('wp_loaded', [__CLASS__, 'set_post_types']);
        add_action('add_meta_boxes', [__CLASS__, 'add_meta_box_pageviews']);

        self::db_updates();
    }

    static function scripts() {
        if(!is_user_logged_in() && (is_single() || is_home() || is_front_page())){  
            wp_enqueue_script('ajax-pageview', plugin_dir_url(__FILE__) . '/ajax-pv.js', 'jquery', false, true);
            wp_localize_script('ajax-pageview', 'ajaxurl', admin_url('admin-ajax.php'));

            wp_localize_script('ajax-pageview', 'ajaxpv', base_convert(get_the_ID(), 10, 36));

            $post_type_hash = self::post_type_hash(is_front_page() ? 'frontpage' : get_post_type());

            wp_localize_script('ajax-pageview', 'ajaxpt', $post_type_hash);
        }
    }

    static function activation() {
        // https://codex.wordpress.org/Creating_Tables_with_Plugins
        global $wpdb;
        $table_name = $wpdb->prefix . "pageviews";

        $charset_collate = $wpdb->get_charset_collate();

        $sql = "CREATE TABLE $table_name (
          id bigint NOT NULL AUTO_INCREMENT,
          time datetime DEFAULT CURRENT_TIMESTAMP NOT NULL,
          post_type varchar(20) NOT NULL,
          post_id bigint(20) unsigned NOT NULL,
          PRIMARY KEY (id)
        ) $charset_collate;";

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

    static function post_type_hash($post_type){
        return md5(__METHOD__ . $post_type);
    }

    static function set_post_types(){
        $post_types = get_post_types();
        $post_types['frontpage'] = 'frontpage';
        foreach($post_types as $post_type){
            $hash = self::post_type_hash($post_type);
            self::$post_types[$hash] = $post_type;
        }
    }

    static function action() {
        if (isset($_POST['ajaxpv']) && isset($_POST['ajaxpt'])) {
            if (!self::is_bot()) {
                $post_id = base_convert($_POST['ajaxpv'], 36, 10);
                $post_type = isset(self::$post_types[$_POST['ajaxpt']]) ? self::$post_types[$_POST['ajaxpt']] : null;

                if ($post_type && self::get_post_id_from_referer() == $post_id) {
                    self::add_pageview($post_id, $post_type);
                }
            }
        }
    }

    static function add_pageview($post_id, $post_type) {
        $table_name = self::$wpdb->prefix . "pageviews";

        self::$wpdb->insert($table_name, ['post_id' => $post_id, 'post_type' => $post_type]);
    }

    static function get_post_id_from_referer() {
        if (isset($_SERVER['HTTP_REFERER'])) {
            return url_to_postid($_SERVER['HTTP_REFERER']);
        }
    }

    static function get_top_viewed($num = 10, $args = []){
        $args += [
            'post_type' => 'post',
            'from' => date('Y-m-d', strtotime('-14 days')),
            'to' => null
        ];

        $cache_key = md5(__METHOD__ . json_encode($args));
        $found = false;
        $result = wp_cache_get($cache_key, __CLASS__, false, $found);
        if($found){
            return $result;
        }

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

        $where = [];
        if($args['post_type']){
            if(!is_array($args['post_type'])){
                $args['post_type'] = [$args['post_type']];
            }
            
            $args['post_type'] = array_map(function($item) { return "'$item'"; }, $args['post_type']);
            $args['post_type'] = implode(",", $args['post_type']);
        
            $where[] = "post_type IN ({$args['post_type']})";
        }

        if($args['from']){
            $where[] = "time >= '{$args['from']}'";
        }

        if($args['to']){
            $where[] = "time <= '{$args['to']}'";
        }

        $where = implode(' AND ', $where);

        if($where){
            $where = "WHERE $where";
        }
        
        $limit = '';
        if($num != -1){
            $limit = "LIMIT {$num}";
        } 

        $sql = "
            SELECT 
                COUNT(id) AS num, 
                post_id 

            FROM {$table_name} 

            {$where} 
            
            GROUP BY post_id 
            ORDER BY num DESC 
            $limit";

        $ids = self::$wpdb->get_results($sql);

        wp_cache_add($cache_key, $ids, __CLASS__, 15 * MINUTE_IN_SECONDS);
        
        return $ids;
    }

    /**
     * Retrieves the top viewed posts by terms.
     *
     * @param int $num The number of top viewed posts to retrieve.
     * @param array $args {
     *     Optional. An array of arguments to customize the query.
     *
     *     @type string|array $post_type The post type(s) to include.
     *     @type string $taxonomy The taxonomy to filter by.
     *     @type string|array $terms The term(s) IDs to filter by.
     *     @type string $from The start date for the date range.
     *     @type string $to The end date for the date range.
     *     @type string $co_author The guest author to filter by.
     *
     * @return array An array of post IDs ordered by the number of views.
     */
    static function get_top_viewed_by_terms( $num = 10, $args = [] ) {
        $args += [
            'post_type' => 'post',
            'taxonomy'  => null,
            'terms'     => null,
            'from'      => date( 'Y-m-d', strtotime( '-14 days' ) ),
            'to'        => null,
            'co_author' => null,
            'post_from' => null,
            'post_to'   => null
        ];

        $cache_key = md5( __METHOD__ . json_encode( $args ) );
        $found     = false;
        $result    = wp_cache_get( $cache_key, __CLASS__, false, $found );

        if ( $found ) {
            return $result;
        }

        $table_name = self::$wpdb->prefix . "pageviews";
        $where = [];
        $join = "";

        if ( $args['post_from'] || $args['post_to'] || $args['post_type'] ) {
            $join = "INNER JOIN " . self::$wpdb->prefix . "posts p ON pv.post_id = p.ID";
        }

        if ( $args['post_type'] ) {
            if ( ! is_array( $args['post_type'] ) ) {
                $args['post_type'] = [$args['post_type']];
            }

            $args['post_type'] = array_map( function( $item ) { return "'$item'"; }, $args['post_type'] );
            $args['post_type'] = implode( ",", $args['post_type'] );

            $where[] = "p.post_type IN ({$args['post_type']})";
        }

        if ( $args['post_from'] && $args['post_to'] ) {
            $where[] = "p.post_date BETWEEN '{$args['post_from']}' AND '{$args['post_to']}'";
        } else if ($args['post_from']) {
            $where[] = "p.post_date >= '{$args['post_from']}'";
        } else if ($args['post_to']) {
            $where[] = "p.post_date <= '{$args['post_to']}'";
        }

        if ( $args['from'] ) {
            $where[] = "pv.time >= '{$args['from']}'";
        }

        if ( $args['to'] ) {
            $where[] = "pv.time <= '{$args['to']}'";
        }

        if ( $args['co_author'] ) {
            if ( substr( $args['co_author'], 0, 4 ) !== 'cap-' ) {
                $args['co_author'] = 'cap-' . $args['co_author'];
            }

            $co_author_subquery = "
                SELECT
                    p.ID
                FROM
                    " . self::$wpdb->prefix . "posts p 
                INNER JOIN
                    " . self::$wpdb->prefix . "pageviews pv ON p.ID = pv.post_id 
                    INNER JOIN " . self::$wpdb->prefix . "term_relationships tr ON p.ID = tr.object_id 
                    INNER JOIN " . self::$wpdb->prefix . "term_taxonomy tt ON tr.term_taxonomy_id = tt.term_taxonomy_id 
                    INNER JOIN " . self::$wpdb->prefix . "terms t ON tt.term_id = t.term_id 
                    WHERE p.post_status = 'publish' 
                    AND tt.taxonomy = 'author' 
                    AND t.slug = '{$args["co_author"]}' 
                GROUP BY 
                    p.ID 
                ORDER BY 
                    COUNT(pv.id) DESC";

            $where[] = "pv.post_id IN ($co_author_subquery)";
        }

        if ( $args["taxonomy"] && $args["terms"] ) {
            if ( ! is_array( $args["terms"] ) ) {
                $args["terms"] = [$args["terms"]];
            }

            $args['terms'] = implode( ",", array_map( 'intval', $args['terms'] ) );

            $subquery_taxonomy = "
                SELECT
                    object_id
                FROM
                    " . self::$wpdb->prefix . "term_relationships 
                INNER JOIN " . self::$wpdb->prefix . "term_taxonomy ON " . self::$wpdb->prefix . "term_taxonomy.term_taxonomy_id = " . self::$wpdb->prefix . "term_relationships.term_taxonomy_id
                INNER JOIN " . self::$wpdb->prefix . "terms ON " . self::$wpdb->prefix . "terms.term_id = " . self::$wpdb->prefix . "term_taxonomy.term_id
                WHERE " . self::$wpdb->prefix . "term_taxonomy.taxonomy = '" . esc_sql( $args["taxonomy"] ) . "' AND " . self::$wpdb->prefix . "terms.term_id IN (". $args["terms"] . ")";
            $where[] = "pv.post_id IN ($subquery_taxonomy)";
        }

        $where = implode(' AND ', $where);

        if ( $where ) {
            $where = "WHERE $where";
        }

        $limit = $num != -1 ? "LIMIT {$num}" : "";

        $sql = "
            SELECT 
                COUNT(pv.id) AS num, 
                pv.post_id" . 
                ($join ? ", p.post_title, p.post_date" : "") . "
            FROM {$table_name} pv
            {$join}
            {$where} 
            GROUP BY pv.post_id 
            ORDER BY num DESC 
            $limit";

        $ids = self::$wpdb->get_results( $sql );

        wp_cache_add( $cache_key, $ids, __CLASS__, 15 * MINUTE_IN_SECONDS );

        return $ids;
    }

    static function get_top_viewed_co_authors( $num = 10 ) {
        global $wpdb;

        $table_name     = $wpdb->prefix . "pageviews";
        $posts_table    = $wpdb->prefix . "posts";
        $postmeta_table = $wpdb->prefix . "postmeta";

        $sql = $wpdb->prepare("
            SELECT ga.ID, ga.post_title, COUNT(pv.id) AS views
            FROM $table_name pv
            INNER JOIN $posts_table p ON pv.post_id = p.ID
            INNER JOIN $posts_table ga ON ga.ID = p.post_author
            INNER JOIN $postmeta_table pm ON ga.ID = pm.post_id
            WHERE ga.post_type = 'guest-author'
            AND pm.meta_key = 'colunista' AND pm.meta_value = '1'
            GROUP BY ga.ID
            ORDER BY views DESC
            LIMIT %d
        ", $num);

        $results = $wpdb->get_results( $sql );

        return $results;
    }

    static function get_views_by_post_id( $post_id, $from = null, $to = null ) {
        global $wpdb;
        self::$wpdb = $wpdb;
        $table_name = self::$wpdb->prefix . "pageviews";

        $where_clauses = ["post_id = %d"];
        $params = [$post_id];

        if ( ! empty( $from ) ) {
            $where_clauses[] = "time >= %s";
            $params[] = $from;
        }

        if ( !empty( $to ) ) {
            $where_clauses[] = "time <= %s";
            $params[] = $to;
        }

        $where_sql = implode( " AND ", $where_clauses );

        $sql = $wpdb->prepare(
            "SELECT COUNT(id) AS views
             FROM {$table_name}
             WHERE {$where_sql}",
             $params
        );

        $result = $wpdb->get_var( $sql );

        return $result ? (int) $result : 0;
    }

    static function db_updates(){
        if(!defined( 'DOING_CRON' )){
            return false;
        }
        $wpdb = self::$wpdb;

        $table_name = $wpdb->prefix . "pageviews";

        $updates = [
            'id to bigint' => function() use($wpdb, $table_name) {
                $wpdb->query("ALTER TABLE $table_name MODIFY id BIGINT UNSIGNED AUTO_INCREMENT;");
            }
        ];

        foreach($updates as $key => $fn){
            $option_name = __METHOD__ . ':' . $key;
            if(!get_option($option_name)){
                $fn();
                add_option($option_name, true);
            }
        }
    }

    static function get_supported_post_types() {
        $default_post_types = ['post', 'page'];
        return apply_filters( 'ajax_pv/supported_post_types', $default_post_types );
    }

    static function add_meta_box_pageviews() {
        $post_types = self::get_supported_post_types();

        add_meta_box(
            'ajax-pageviews',
            __( 'Ajax Pageviews', 'ajax-pv' ),
            [__CLASS__, 'add_meta_box_pageviews_callback'],
            $post_types,
            'normal',
            'default'
        );
    }

    static function add_meta_box_pageviews_callback( $post ) {
        if ( ! empty( $post->ID ) ) {
            $pageviews_partial  = AjaxPageviews::get_views_by_post_id( $post->ID, date( 'Y-m-d', strtotime( '-14 days' ) ) );
            $pageviews_complete = AjaxPageviews::get_views_by_post_id( $post->ID );

            echo '<p>' . __( 'Visualizações nos últimos 15 dias:', 'ajax-pv' ) . ' <b>' . $pageviews_partial . '</b></p>';
            echo '<p>' . __( 'Visualizações em todo período:', 'ajax-pv' ) . ' <b>' . $pageviews_complete . '</b></p>';
        }
    }

    static function is_bot() {
        if(!isset($_SERVER['HTTP_USER_AGENT']) || preg_match('/bot|crawl|curl|dataprovider|search|get|spider|find|java|majesticsEO|google|yahoo|teoma|contaxe|yandex|libwww-perl|facebookexternalhit/i', $_SERVER['HTTP_USER_AGENT'])) {
            return true;
        }

        return false;
    }
}

add_action('init', function(){
    AjaxPageviews::init();
});

register_activation_hook(__FILE__, ['AjaxPageviews', 'activation']);