<?php
/**
 * Activity Triggers, used for triggering achievement earning
 *
 * @package     GamiPress\Triggers
 * @author      GamiPress <contact@gamipress.com>, Ruben Garcia <rubengcdev@gmail.com>
 * @since       1.0.0
 */
// Exit if accessed directly
if( !defined( 'ABSPATH' ) ) exit;

/**
 * GamiPress activity triggers
 *
 * @since  1.0.0
 *
 * @return array Array of all activity triggers
 */
function gamipress_get_activity_triggers() {

	GamiPress()->activity_triggers = apply_filters( 'gamipress_activity_triggers',
		array(
			// WordPress
			__( 'WordPress', 'gamipress' ) => array(
				'gamipress_register'             	    => __( 'Register to website', 'gamipress' ),
				'gamipress_login'             	    	=> __( 'Log in to website', 'gamipress' ),
				'gamipress_new_comment'  				=> __( 'Comment on a post', 'gamipress' ),
				'gamipress_specific_new_comment' 		=> __( 'Comment on a specific post', 'gamipress' ),
                'gamipress_new_comment_post_type'       => __( 'Comment on a post of a type', 'gamipress' ),
				'gamipress_user_post_comment'  			=> __( 'Get a comment on a post', 'gamipress' ),
				'gamipress_user_specific_post_comment' 	=> __( 'Get a comment on a specific post', 'gamipress' ),
                'gamipress_user_post_comment_post_type' => __( 'Get a comment on a post of a type', 'gamipress' ),
                'gamipress_spam_comment'  				=> __( 'Get a comment marked as spam', 'gamipress' ),
                'gamipress_specific_spam_comment' 		=> __( 'Get a comment of a specific post marked as spam', 'gamipress' ),
                'gamipress_spam_comment_post_type'  	=> __( 'Get a comment on a post of a type marked as spam', 'gamipress' ),
				'gamipress_publish_post'     			=> __( 'Publish a new post', 'gamipress' ),
				'gamipress_delete_post'     			=> __( 'Delete a post', 'gamipress' ),
				'gamipress_publish_page'     			=> __( 'Publish a new page', 'gamipress' ),
				'gamipress_delete_page'     			=> __( 'Delete a page', 'gamipress' ),
                'gamipress_publish_post_type'     	    => __( 'Publish a new post of a type', 'gamipress' ),
                'gamipress_delete_post_type'     	    => __( 'Delete a post of a type', 'gamipress' ),
				'gamipress_add_role'     			    => __( 'Get added to any role', 'gamipress' ),
				'gamipress_add_specific_role'     	    => __( 'Get added to specific role', 'gamipress' ),
				'gamipress_set_role'     			    => __( 'Get assigned to any role', 'gamipress' ),
				'gamipress_set_specific_role'     		=> __( 'Get assigned to specific role', 'gamipress' ),
				'gamipress_remove_role'     			=> __( 'Get removed from any role', 'gamipress' ),
				'gamipress_remove_specific_role'     	=> __( 'Get removed from a specific role', 'gamipress' ),
                'gamipress_update_user_meta_any_value'      => __( 'Get user meta updated with any value', 'gamipress' ),
                'gamipress_update_user_meta_specific_value' => __( 'Get user meta updated with a value', 'gamipress' ),
                'gamipress_update_post_meta_any_value'      => __( 'Post meta updated with any value', 'gamipress' ),
                'gamipress_update_post_meta_specific_value' => __( 'Post meta updated with a value', 'gamipress' ),
			),
			// Site Interactions
			__( 'Site Interactions', 'gamipress' ) => array(
				'gamipress_site_visit'  				=> __( 'Daily visit the website', 'gamipress' ),
				'gamipress_post_visit'  				=> __( 'Daily visit any post', 'gamipress' ),
				'gamipress_specific_post_visit'  		=> __( 'Daily visit a specific post', 'gamipress' ),
                'gamipress_post_type_visit'  		    => __( 'Daily visit a post of a type', 'gamipress' ),
				'gamipress_user_post_visit'  			=> __( 'Get visits on any post', 'gamipress' ),
				'gamipress_user_specific_post_visit'	=> __( 'Get visits on a specific post', 'gamipress' ),
                'gamipress_user_post_type_visit'  	    => __( 'Get visits on a post of a type', 'gamipress' ),
			),
			// GamiPress
			'GamiPress' => array(
				'specific-achievement' 					=> __( 'Unlock a specific achievement', 'gamipress' ),
				'any-achievement'      					=> __( 'Unlock any achievement of type', 'gamipress' ),
				'all-achievements'     					=> __( 'Unlock all Achievements of type', 'gamipress' ),
                'revoke-specific-achievement' 			=> __( 'Get a specific achievement revoked', 'gamipress' ),
                'revoke-any-achievement'      			=> __( 'Get any achievement of type revoked', 'gamipress' ),
                'earn-points' 							=> __( 'Earn an amount of points', 'gamipress' ),
				'points-balance' 					    => __( 'Reach a points balance', 'gamipress' ),
				'gamipress_expend_points' 				=> __( 'Expend an amount of points', 'gamipress' ),
				'earn-rank' 							=> __( 'Reach a rank', 'gamipress' ),
				'revoke-rank' 							=> __( 'Get a rank revoked', 'gamipress' ),
			),
		)
	);

	return GamiPress()->activity_triggers;

}

/**
 * GamiPress specific activity triggers
 *
 * @since  1.0.0
 *
 * @return array Array of all specific activity triggers
 */
function gamipress_get_specific_activity_triggers() {

	// Get all public post types which means they are visitable
	$public_post_types = get_post_types( array( 'public' => true ) );

	// Remove attachment from public post types
	if( isset( $public_post_types['attachment'] ) ) {
		unset( $public_post_types['attachment'] );
	}

	// Remove keys
	$public_post_types = array_values( $public_post_types );

	// Get all post types with comments support
	$comments_post_types = get_post_types_by_support( 'comments' );

	// Remove attachment from post types with comments support
	if( $index = array_search( 'attachment', $comments_post_types ) ) {
		unset( $comments_post_types[$index] );
	}

	// Remove keys
	$comments_post_types = array_values( $comments_post_types );

	return apply_filters( 'gamipress_specific_activity_triggers', array(
		'gamipress_specific_new_comment' 		=> $comments_post_types,
		'gamipress_user_specific_post_comment'  => $comments_post_types,
        'gamipress_specific_spam_comment' 		=> $comments_post_types,
		'gamipress_specific_post_visit'  		=> $public_post_types,
		'gamipress_user_specific_post_visit'  	=> $public_post_types,
	) );

}

/**
 * GamiPress triggers for the points and points type selector
 *
 * @since 2.2.6
 *
 * @return array Array of all points triggers
 */
function gamipress_get_points_triggers() {

    return apply_filters( 'gamipress_points_triggers', array(
        'earn-points',
        'points-balance',
        'gamipress_expend_points',
    ) );

}

/**
 * GamiPress triggers for the achievement type selector
 *
 * @since 2.2.6
 *
 * @return array Array of all achievement type triggers
 */
function gamipress_get_achievement_type_triggers() {

    return apply_filters( 'gamipress_achievement_type_triggers', array(
        'specific-achievement',
        'any-achievement',
        'all-achievements',
        'revoke-specific-achievement',
        'revoke-any-achievement'
    ) );

}

/**
 * GamiPress triggers for the rank type selector
 *
 * @since 2.2.6
 *
 * @return array Array of all rank type triggers
 */
function gamipress_get_rank_type_triggers() {

    return apply_filters( 'gamipress_rank_type_triggers', array(
        'earn-rank',
        'revoke-rank',
    ) );

}

/**
 * GamiPress triggers for the post type selector
 *
 * @since 2.2.6
 *
 * @return array Array of all post type triggers
 */
function gamipress_get_post_type_triggers() {

    return apply_filters( 'gamipress_post_type_triggers', array(
        'gamipress_new_comment_post_type',
        'gamipress_user_post_comment_post_type',
        'gamipress_spam_comment_post_type',
        'gamipress_spam_comment_post_type',
        'gamipress_publish_post_type',
        'gamipress_delete_post_type',
        'gamipress_post_type_visit',
        'gamipress_user_post_type_visit',
    ) );

}

/**
 * GamiPress triggers for the user role selector
 *
 * @since 2.2.6
 *
 * @return array Array of all user role triggers
 */
function gamipress_get_user_role_triggers() {

    return apply_filters( 'gamipress_user_role_triggers', array(
        'gamipress_add_specific_role',
        'gamipress_set_specific_role',
        'gamipress_remove_specific_role',
    ) );

}

/**
 * GamiPress specific activity triggers query args (used on requirements UI and on ajax get posts)
 *
 * @since  1.3.6
 *
 * @param array|string 	$query_args
 * @param string 		$activity_trigger
 *
 * @return array|string
 */
function gamipress_get_specific_activity_triggers_query_args( $query_args, $activity_trigger ) {

    /**
     * Specific activity triggers query args (used on requirements UI and on ajax get posts)
     *
     * Note: Use $_REQUEST for all given parameters
     * @see gamipress_ajax_get_posts()
     *
     * @since  1.3.6
     *
     * @param array|string 	$query_args
     * @param string 		$activity_trigger
     *
     * @return array|string
     */
	return apply_filters( 'gamipress_specific_activity_triggers_query_args', $query_args, $activity_trigger );

}

/**
 * Helper function for returning an activity trigger label
 *
 * @since  1.0.0
 *
 * @param string $activity_trigger
 *
 * @return string
 */
function gamipress_get_activity_trigger_label( $activity_trigger ) {

    $label = '';
	$activity_triggers = gamipress_get_activity_triggers();

	foreach( $activity_triggers as $group => $group_triggers ) {
		if( isset( $group_triggers[$activity_trigger] ) ) {
            $label = $group_triggers[$activity_trigger];
        }
	}

	if( gamipress_starts_with( $activity_trigger, 'gamipress_unlock_' ) ) {

        // Check if trigger is for unlocking ANY and ALL posts of an achievement type
	    $achievement_type = $activity_trigger;
	    $achievement_type = str_replace( 'gamipress_unlock_all_', '', $achievement_type );
	    $achievement_type = str_replace( 'gamipress_unlock_', '', $achievement_type );
	    $data = gamipress_get_achievement_type( $achievement_type );

        if ( $data ) {
            if( gamipress_starts_with( $activity_trigger, 'gamipress_unlock_all_' ) )
                $label = sprintf( __( 'Unlocked all %s', 'gamipress' ), $data['plural_name'] );
            else
                $label = sprintf( __( 'Unlocked a %s', 'gamipress' ), $data['singular_name'] );
        }

    } else if( gamipress_starts_with( $activity_trigger, 'gamipress_revoke_' ) ) {

        // Check if trigger is for revoking ANY posts of an achievement type
        $achievement_type = $activity_trigger;
        $achievement_type = str_replace( 'gamipress_revoke_', '', $achievement_type );
        $data = gamipress_get_achievement_type( $achievement_type );

        if ( $data ) {
            $label = sprintf( __( 'Get a %s revoked', 'gamipress' ), $data['singular_name'] );
        }

    }

    /**
     * Available filter to override an activity trigger label
     *
     * @since  1.9.9
     *
     * @param string $label
     * @param string $activity_trigger
     *
     * @return string
     */
	return apply_filters( 'gamipress_get_activity_trigger_label', $label, $activity_trigger );

}

/**
 * Helper function for returning a specific activity trigger label
 *
 * @since  1.0.0
 *
 * @param string $activity_trigger
 *
 * @return string
 */
function gamipress_get_specific_activity_trigger_label( $activity_trigger ) {

	$specific_activity_trigger_labels = apply_filters( 'gamipress_specific_activity_trigger_label', array(
		'gamipress_specific_new_comment' 		=> __( 'Comment on %s', 'gamipress' ),
		'gamipress_user_specific_post_comment'  => __( 'Get a comment on %s', 'gamipress' ),
        'gamipress_specific_spam_comment' 		=> __( 'Get a comment on %s marked as spam', 'gamipress' ),
		'gamipress_specific_post_visit'  		=> __( 'Visit %s', 'gamipress' ),
		'gamipress_user_specific_post_visit'  	=> __( '%s gets visited', 'gamipress' ),
	) );

	if( isset( $specific_activity_trigger_labels[$activity_trigger] ) ) {
		return $specific_activity_trigger_labels[$activity_trigger];
	}

	return '';

}

/**
 * Helper function to get the title of a given specific ID for a specific activity trigger
 *
 * @since 	1.4.0
 * @updated 1.4.8 Added $site_id parameter
 *
 * @param integer $specific_id      The specific ID
 * @param string  $trigger_type     The requirement trigger type
 * @param integer $site_id     		The site ID
 *
 * @return string                   The specific title
 */
function gamipress_get_specific_activity_trigger_post_title( $specific_id, $trigger_type, $site_id ) {

	$override = apply_filters( 'gamipress_specific_activity_trigger_post_title', 'no_override', $specific_id, $trigger_type, $site_id );

	if( $override !== 'no_override' ) {
		return $override;
	}

	if( gamipress_is_network_wide_active() && $site_id !== get_current_blog_id() ) {

		// Switch to the given site to get the post title from this site
		switch_to_blog( $site_id );

		$post_title = get_post_field( 'post_title', $specific_id );

		// Restore the current blog
        restore_current_blog();

		return $post_title;

	}

	return get_post_field( 'post_title', $specific_id );

}

/**
 * Helper function to determine if a given specific ID is public to get his permalink
 *
 * @since 1.5.0
 *
 * @param integer $specific_id      The specific ID
 * @param string  $trigger_type     The requirement trigger type
 * @param integer $site_id     		The site ID
 *
 * @return bool
 */
function gamipress_is_specific_activity_trigger_post_type_public( $specific_id, $trigger_type, $site_id ) {

	$override = apply_filters( 'gamipress_specific_activity_trigger_is_post_type_public', 'no_override', $specific_id, $trigger_type, $site_id );

	if( $override !== 'no_override' ) {
		return (bool) $override;
	}

    // Get all public registered post types
    $public_post_types = get_post_types( array( 'public' => true ) );

	if( gamipress_is_network_wide_active() && $site_id !== get_current_blog_id() ) {

		// Switch to the given site to get the post title from this site
		switch_to_blog( $site_id );

		$post_type = get_post_field( 'post_type', $specific_id );

		// Restore the current blog
        restore_current_blog();

	} else {
        $post_type = get_post_field( 'post_type', $specific_id );
    }

	return in_array( $post_type, $public_post_types );

}

/**
 * Helper function to get the permalink of a given specific ID for a specific activity trigger
 *
 * @since 	1.5.0
 *
 * @param integer $specific_id      The specific ID
 * @param string  $trigger_type     The requirement trigger type
 * @param integer $site_id     		The site ID
 *
 * @return false|string             The specific permalink
 */
function gamipress_get_specific_activity_trigger_permalink( $specific_id, $trigger_type, $site_id ) {

    $override = apply_filters( 'gamipress_specific_activity_trigger_permalink', 'no_override', $specific_id, $trigger_type, $site_id );

    if( $override !== 'no_override' ) {
        return $override;
    }

    if( ! gamipress_is_specific_activity_trigger_post_type_public( $specific_id, $trigger_type, $site_id ) ) {
        return false;
    }

    if( absint( $specific_id ) === 0 ) {
        return false;
    }

    if( gamipress_is_network_wide_active() && $site_id !== get_current_blog_id() ) {

        // Switch to the given site to get the post title from this site
        switch_to_blog( $site_id );

        $permalink = get_permalink( $specific_id );

        // Restore the current blog
        restore_current_blog();

        return $permalink;

    }

    return get_permalink( $specific_id );

}

/**
 * Load activity triggers to hook them
 *
 * @since 1.0.0
 */
function gamipress_hook_activity_triggers() {

    // GamiPress requires to load activity triggers early to hook them
    // To prevent any errors and since in this function labels are not required
    // Lets to prevent load text domains and avoid warnings
    // Next, gamipress_load_activity_triggers will load them again with their labels at the init hook

    // Prevent to load text domains here
    add_filter( 'lang_dir_for_domain', 'gamipress_prevent_load_textdoamin' );

    // Grab our activity triggers
    $activity_triggers = gamipress_get_activity_triggers();
    $excluded_to_load = gamipress_get_activity_triggers_excluded_to_load();

    // Restore text domains load
    remove_filter( 'lang_dir_for_domain', 'gamipress_prevent_load_textdoamin' );

    // Loop through each achievement type and add triggers for unlocking/revoking them
    foreach ( gamipress_get_achievement_types() as $achievement_type => $data ) {
        // Add trigger for unlocking/revoking ANY and ALL posts for each achievement type
        $activity_triggers['GamiPress']['gamipress_unlock_' . $achievement_type] = ''; // Label not required here
        $activity_triggers['GamiPress']['gamipress_unlock_all_' . $achievement_type] = ''; // Label not required here
        $activity_triggers['GamiPress']['gamipress_revoke_' . $achievement_type] = ''; // Label not required here
    }

    // Loop through each trigger and add our trigger event to the hook
    foreach ( $activity_triggers as $group => $group_triggers ) {
        foreach( $group_triggers as $trigger => $label ) {
            // Hook if trigger is not excluded to be loaded
            if( ! in_array( $trigger, $excluded_to_load ) ) {
                add_action( $trigger, 'gamipress_trigger_event', 10, 20 );
            }
        }
    }

}
add_action( 'gamipress_init', 'gamipress_hook_activity_triggers' );

// Just a return false function to hook it on text domains
function gamipress_prevent_load_textdoamin() {
    return false;
}

/**
 * Load activity triggers text domains
 *
 * @since 1.0.0
 */
function gamipress_load_activity_triggers() {

    gamipress_get_activity_triggers();

}
add_action( 'init', 'gamipress_load_activity_triggers' );

/**
 * Get activity triggers excluded to be loaded automatically from gamipress_load_activity_triggers()
 *
 * @since 1.0.0
 *
 * @return array
 */
function gamipress_get_activity_triggers_excluded_to_load() {

    return apply_filters( 'gamipress_activity_triggers_excluded_to_load', array(
        'gamipress_login',
        'gamipress_register'
    ) );

}

/**
 * Get activity triggers excluded from activity time limits
 *
 * @since 1.5.9
 *
 * @return array
 */
function gamipress_get_activity_triggers_excluded_from_activity_limit() {

    return apply_filters( 'gamipress_activity_triggers_excluded_from_activity_limit', array(
        'gamipress_register',
        'earn-points',
        'points-balance',
        'earn-rank',
        'revoke-rank'
    ) );

}

/**
 * Handle each of our activity triggers
 *
 * If method is called directly, pass an array of arguments with next items:
 * array(
 * 	'event' 		=> 'gamipress_login',
 * 	'user_id' 		=> 1,
 *  'specific_id' 	=> 100 // Just if is an specific trigger
 * )
 *
 * @since 	1.0.0
 * @updated 1.4.3 Added the ability to be called directly
 *
 * @return mixed    Returns an array will all achievements awarded or false if none has been awarded
 */
function gamipress_trigger_event() {

    // Get the current site
	$site_id = get_current_blog_id();

	$args = func_get_args();

	// Check if method has been called directly
	if( isset( $args[0] ) && is_array( $args[0] ) && isset( $args[0]['event'] ) ) {
		$args = $args[0];
    }

	// Grab our current trigger
	$trigger = ( isset( $args['event'] ) ? $args['event'] : current_filter() );

	// gamipress_unlock_all_{type}, gamipress_unlock_{type} and gamipress_revoke_{type} are excluded from this check
	if( ! ( gamipress_starts_with( $trigger, 'gamipress_unlock_' ) || gamipress_starts_with( $trigger, 'gamipress_revoke_' ) ) ) {

        /**
         * Filter to decide if GamiPress should log all events triggered (filter in replacement to the option "log_all_events")
         *
         * @since 2.5.3
         *
         * @param bool $log_all_events
         *
         * @return bool
         */
        $log_all_events = apply_filters( 'gamipress_log_all_events', false );

		// Check if log all events is enabled
		if( ! $log_all_events ) {

            // If not achievements listening it, then return
			if( ! gamipress_trigger_has_listeners( $trigger, $site_id, $args ) ) {
				return false;
            }

		}

	}

	// Grab the user ID
	$user_id = ( isset( $args['user_id'] ) ? $args['user_id'] : gamipress_trigger_get_user_id( $trigger, $args ) );
	$user_data = get_user_by( 'id', $user_id );

	// Sanity check, if we don't have a user object, bail here
	if ( ! is_object( $user_data ) ) {
		return false;
    }

	// If the user doesn't satisfy the trigger requirements, bail here
	if ( ! apply_filters( 'gamipress_user_deserves_trigger', true, $user_id, $trigger, $site_id, $args ) ) {
		return false;
    }

	// Update hook count for this user
	gamipress_update_user_trigger_count( $user_id, $trigger, $site_id, $args );

	// Register event triggered in logs
    gamipress_log_event_triggered( $user_id, $trigger, $site_id, $args );

	// Check if any achievements are earned based on this trigger event
	$triggered_achievements = gamipress_get_triggered_requirements( $user_id, $trigger, $site_id, $args );

	$awarded_achievements = array();

	foreach ( $triggered_achievements as $achievement ) {

	    $award_result = gamipress_maybe_award_achievement_to_user( $achievement->ID, $user_id, $trigger, $site_id, $args );

		if( $award_result === true ) {
            $awarded_achievements[] = $achievement->ID;
        }
	}

	return ( count( $awarded_achievements ) ? $awarded_achievements : false );

}

/**
 * Gets the event arg parameter
 *
 * @since 1.8.8
 *
 * @param array    	$args   Event args
 * @param string 	$key    The args key to get
 * @param int    	$index The index key to fallback
 *
 * @return mixed
 */
function gamipress_get_event_arg( $args = array(), $key = '', $index = 0 ) {
    return ( isset( $args[$key] ) ? $args[$key] : ( isset( $args[$index] ) ? $args[$index] : '' ) );
}

/**
 * Logs the event triggered ono gamipress_trigger_event() function
 *
 * @since 1.6.2
 *
 * @param int    	$user_id
 * @param string 	$trigger
 * @param int    	$site_id
 * @param array 	$args
 */
function gamipress_log_event_triggered( $user_id, $trigger, $site_id, $args ) {

    // Log meta data
    $log_meta = array(
        'pattern' => apply_filters( 'gamipress_trigger_log_pattern', __( '{user} triggered {trigger_type} (x{count})', 'gamipress' ) ),
        'count' => gamipress_get_user_trigger_count( $user_id, $trigger, 0, $site_id, $args ),
        //'trigger_type' => $trigger, // Removed since 1.4.7
    );

    // On multisite, search for site logs
    if( is_multisite() && gamipress_is_network_wide_active() ) {
        $log_meta['site_id'] = $site_id;
    }

    // If is specific trigger then try to get the attached id
    if( in_array( $trigger, array_keys( gamipress_get_specific_activity_triggers() ) ) ) {

        $specific_id = ( isset( $args['specific_id'] ) ? $args['specific_id'] : gamipress_specific_trigger_get_id( $trigger, $args ) );

        // If there is a specific id, then add it to the log meta data
        if( $specific_id !== 0 ) {
            $log_meta['achievement_post'] = $specific_id;
            $log_meta['achievement_post_site_id'] = $site_id;
        }

    }

    /**
     * Available filter to insert custom meta data
     *
     * @since 1.6.2
     *
     * @param array 	$log_meta
     * @param int 	    $user_id
     * @param string 	$trigger
     * @param int 	    $site_id
     * @param array 	$args
     *
     * @return array
     */
    $log_meta = apply_filters( 'gamipress_log_event_trigger_meta_data', $log_meta, $user_id, $trigger, $site_id, $args );

    // Mark the count in the log entry
    gamipress_insert_log( 'event_trigger', $user_id, 'private', $trigger, $log_meta );

}

/**
 * Extended meta data for event trigger logging
 *
 * @since 1.1.8
 *
 * @param array 	$log_meta
 * @param integer 	$user_id
 * @param string 	$trigger
 * @param integer 	$site_id
 * @param array 	$args
 *
 * @return array
 */
function gamipress_log_event_trigger_extended_meta_data( $log_meta, $user_id, $trigger, $site_id, $args ) {

	switch ( $trigger ) {
		case 'gamipress_publish_post':
		case 'gamipress_publish_page':
		case 'gamipress_delete_post':
		case 'gamipress_delete_page':
		case 'gamipress_post_visit':
		case 'gamipress_specific_post_visit':
			// Add the published/deleted/visited post ID
			$log_meta['post_id'] = gamipress_get_event_arg( $args, 'post_id', 0 );
			break;
        case 'gamipress_publish_post_type':
        case 'gamipress_delete_post_type':
        case 'gamipress_post_type_visit':
        case 'gamipress_user_post_type_visit':
            // Add the post ID and type
            $log_meta['post_id'] = gamipress_get_event_arg( $args, 'post_id', 0 );
            $log_meta['post_type'] = gamipress_get_event_arg( $args, 'post_type', 2 );
            break;
		case 'gamipress_user_post_visit':
		case 'gamipress_user_specific_post_visit':
			// Add the visited post ID
			$log_meta['post_id'] = gamipress_get_event_arg( $args, 'post_id', 0 );
			$log_meta['visitor_id'] = gamipress_get_event_arg( $args, 'user_id', 2 );
			break;
		case 'gamipress_new_comment':
		case 'gamipress_specific_new_comment':
		case 'gamipress_user_post_comment':
		case 'gamipress_user_specific_post_comment':
		case 'gamipress_spam_comment':
		case 'gamipress_specific_spam_comment':
			// Add the comment ID and post commented ID
			$log_meta['comment_id'] = gamipress_get_event_arg( $args, 'comment_id', 0 );
			$log_meta['comment_post_id'] = gamipress_get_event_arg( $args, 'post_id', 2 );
			break;
        case 'gamipress_new_comment_post_type':
        case 'gamipress_user_post_comment_post_type':
        case 'gamipress_spam_comment_post_type':
            // Add the comment ID, post commented ID and type
            $log_meta['comment_id'] = gamipress_get_event_arg( $args, 'comment_id', 0 );
            $log_meta['comment_post_id'] = gamipress_get_event_arg( $args, 'post_id', 2 );
            $log_meta['comment_post_type'] = gamipress_get_event_arg( $args, 'post_type', 3 );
            break;
		case 'gamipress_expend_points':
			// Add the post ID, the amount of points and the points type
			$log_meta['post_id'] = gamipress_get_event_arg( $args, 'post_id', 0 );
			$log_meta['points'] = gamipress_get_event_arg( $args, 'points', 2 );
			$log_meta['points_type'] = gamipress_get_event_arg( $args, 'points_type', 3 );
			break;
		case gamipress_starts_with( $trigger, 'gamipress_unlock_' ):
            if( ! gamipress_starts_with( $trigger, 'gamipress_unlock_all_' ) ) {
                $log_meta['post_id'] = gamipress_get_event_arg( $args, 'post_id', 1 );
            }
            break;
		case gamipress_starts_with( $trigger, 'gamipress_revoke_' ):
            $log_meta['post_id'] = gamipress_get_event_arg( $args, 'post_id', 1 );
            break;
        case 'gamipress_login':
        case 'gamipress_register':
        case 'gamipress_site_visit':
		default:
			// Nothing to store
			break;
	}

	return $log_meta;
}
add_filter( 'gamipress_log_event_trigger_meta_data', 'gamipress_log_event_trigger_extended_meta_data', 10, 5 );

/**
 * Extended meta data to filter the logs count
 *
 * @since 2.0.5
 *
 * @param  array    $log_meta       The meta data to filter the logs count
 * @param  int      $user_id        The given user's ID
 * @param  string   $trigger        The given trigger we're checking
 * @param  int      $since 	        The since timestamp where retrieve the logs
 * @param  int      $site_id        The desired Site ID to check
 * @param  array    $args           The triggered args or requirement object
 *
 * @return array                    The meta data to filter the logs count
 */
function gamipress_get_user_trigger_count_log_extended_meta( $log_meta, $user_id, $trigger, $since, $site_id, $args ) {

    switch ( $trigger ) {
        case 'gamipress_publish_post_type':
        case 'gamipress_delete_post_type':
        case 'gamipress_post_type_visit':
        case 'gamipress_user_post_type_visit':
            // Filter by the post type
            $log_meta['post_type'] = gamipress_get_event_arg( $args, 'post_type', 2 );

            // $args could be a requirement object
            if( isset( $args['post_type_required'] ) ) {
                $log_meta['post_type'] = $args['post_type_required'];
            }
            break;
        case 'gamipress_new_comment_post_type':
        case 'gamipress_user_post_comment_post_type':
        case 'gamipress_spam_comment_post_type':
            // Filter by the post type
            $log_meta['comment_post_type'] = gamipress_get_event_arg( $args, 'post_type', 3 );

            // $args could be a requirement object
            if( isset( $args['post_type_required'] ) ) {
                $log_meta['comment_post_type'] = $args['post_type_required'];
            }
            break;
        case 'specific-achievement':
            // Filter by the post ID
            $log_meta['post_id'] = gamipress_get_event_arg( $args, 'post_id', 1 );

            if( isset( $args['achievement_post'] ) && absint( $log_meta['achievement_post'] ) === 0 ) {
                $log_meta['post_id'] = $args['achievement_post'];
            }

            // Update the trigger type
            $log_meta['trigger_type'] = 'gamipress_unlock_' . gamipress_get_post_type( $log_meta['post_id'] );
            break;
        case 'revoke-specific-achievement':
            // Filter by the post ID
            $log_meta['post_id'] = gamipress_get_event_arg( $args, 'post_id', 1 );

            if( isset( $args['achievement_post'] ) && absint( $log_meta['achievement_post'] ) === 0 ) {
                $log_meta['post_id'] = $args['achievement_post'];
            }

            // Update the trigger type
            $log_meta['trigger_type'] = 'gamipress_revoke_' . gamipress_get_post_type( $log_meta['post_id'] );
            break;
    }

    return $log_meta;

}
add_filter( 'gamipress_get_user_trigger_count_log_meta', 'gamipress_get_user_trigger_count_log_extended_meta', 10, 6 );

/**
 * Extra filter to check duplicated activity
 *
 * @since 1.1.8
 *
 * @param bool 		$return
 * @param integer 	$user_id
 * @param string 	$trigger
 * @param integer 	$site_id
 * @param array 	$args
 *
 * @return bool					True if user deserves trigger, else false
 */
function gamipress_trigger_duplicity_check( $return, $user_id, $trigger, $site_id, $args  ) {

	$log_meta = array(
		'type' => 'event_trigger',
		'trigger_type' => $trigger,
	);

	// On multisite, search for site logs
	if( is_multisite() && gamipress_is_network_wide_active() ) {
        $log_meta['site_id'] = $site_id;
    }

	switch ( $trigger ) {
		case 'gamipress_publish_post':
		case 'gamipress_publish_page':
		case 'gamipress_publish_post_type':
		case 'gamipress_delete_post':
		case 'gamipress_delete_page':
		case 'gamipress_delete_post_type':
			// User can not publish/delete same post more times, so check it
			$log_meta['post_id'] = gamipress_get_event_arg( $args, 'post_id', 0 );
			$return = (bool) ( gamipress_get_user_last_log( $user_id, $log_meta ) === false );
			break;
		case 'gamipress_user_post_visit':
		case 'gamipress_user_specific_post_visit':
			// Prevent award author for receive repeated visits
			$log_meta['post_id'] = $args[0];
			$log_meta['visitor_id'] = $args[2];

			// Guests are allowed to trigger the visit unlimited times
			if( $log_meta['visitor_id'] !== 0 ) {
				$return = (bool) ( gamipress_get_user_last_log( $user_id, $log_meta ) === false );
			}
			break;
		case 'gamipress_new_comment':
		case 'gamipress_specific_new_comment':
		case 'gamipress_new_comment_post_type':
		case 'gamipress_user_post_comment':
		case 'gamipress_user_specific_post_comment':
		case 'gamipress_user_post_comment_post_type':
        case 'gamipress_spam_comment':
        case 'gamipress_specific_spam_comment':
        case 'gamipress_spam_comment_post_type':
			// User can not publish same comment more times, so check it
			$log_meta['comment_id'] = $args[0];
			$return = (bool) ( gamipress_get_user_last_log( $user_id, $log_meta ) === false );
			break;
	}

	return apply_filters( 'gamipress_trigger_duplicity_check', $return, $user_id, $trigger, $site_id, $args );

}
add_filter( 'gamipress_user_deserves_trigger', 'gamipress_trigger_duplicity_check', 10, 5 );

/**
 * Get user for a given trigger action.
 *
 * @since  1.0.0
 *
 * @param  string  $trigger Trigger name.
 * @param  array   $args    Passed trigger args.
 *
 * @return integer          User ID.
 */
function gamipress_trigger_get_user_id( $trigger = '', $args = array() ) {

	// If gamipress_trigger_event() has called directly, then get user ID from args
	if( isset( $args['event'] ) && isset( $args['user_id'] ) ) {
		return $args['user_id'];
	}

	switch ( $trigger ) {
		case 'gamipress_login':
		case 'gamipress_register':
		case 'gamipress_site_visit':
        case 'gamipress_add_role':
        case 'gamipress_add_specific_role':
        case 'gamipress_set_role':
        case 'gamipress_set_specific_role':
        case 'gamipress_remove_role':
        case 'gamipress_remove_specific_role':
		case gamipress_starts_with( $trigger, 'gamipress_unlock_' ):
		case gamipress_starts_with( $trigger, 'gamipress_revoke_' ):
        case 'gamipress_update_user_meta_any_value':
        case 'gamipress_update_user_meta_specific_value':
        case 'gamipress_update_post_meta_any_value':
        case 'gamipress_update_post_meta_specific_value':
			$user_id = $args[0];
			break;
		case 'gamipress_publish_post':
		case 'gamipress_publish_page':
		case 'gamipress_publish_post_type':
		case 'gamipress_delete_post':
		case 'gamipress_delete_page':
		case 'gamipress_delete_post_type':
		case 'gamipress_new_comment':
		case 'gamipress_specific_new_comment':
		case 'gamipress_new_comment_post_type':
		case 'gamipress_user_post_comment':
		case 'gamipress_user_specific_post_comment':
		case 'gamipress_user_post_comment_post_type':
        case 'gamipress_spam_comment':
        case 'gamipress_specific_spam_comment':
        case 'gamipress_spam_comment_post_type':
		case 'gamipress_post_visit':
		case 'gamipress_specific_post_visit':
		case 'gamipress_post_type_visit':
		case 'gamipress_user_post_visit':
		case 'gamipress_user_specific_post_visit':
		case 'gamipress_user_post_type_visit':
		case 'gamipress_expend_points':
			$user_id = $args[1];
			break;
		default :
			$user_id = get_current_user_id();
			break;
	}

	return apply_filters( 'gamipress_trigger_get_user_id', $user_id, $trigger, $args );
}

/**
 * Get the id for a given specific trigger action.
 *
 * @since  1.0.8
 *
 * @param  string  $trigger Trigger name.
 * @param  array   $args    Passed trigger args.
 *
 * @return integer          Specific ID.
 */
function gamipress_specific_trigger_get_id( $trigger = '', $args = array() ) {

	// If gamipress_trigger_event() has called directly, then get user ID from args
	if( isset( $args['event'] ) && isset( $args['specific_id'] ) ) {
		return $args['specific_id'];
	}

	switch ( $trigger ) {
		case 'gamipress_specific_new_comment':
		case 'gamipress_user_specific_post_comment':
        case 'gamipress_specific_spam_comment':
			$specific_id = $args[2];
			break;
		case 'gamipress_specific_post_visit':
		case 'gamipress_user_specific_post_visit':
		default :
			$specific_id = $args[0];
			break;
	}

	return absint( apply_filters( 'gamipress_specific_trigger_get_id', $specific_id, $trigger, $args ) );
}

/**
 * Check if trigger has listeners
 *
 * @since 1.0.8
 *
 * @param string 	$trigger
 * @param integer 	$site_id
 * @param array 	$args
 *
 * @return bool
 */
function gamipress_trigger_has_listeners( $trigger, $site_id, $args ) {

	global $wpdb;

	$posts  	= GamiPress()->db->posts;
	$postmeta  	= GamiPress()->db->postmeta;

    $triggers_count = gamipress_get_triggers_listeners_count();
	$listeners_count = ( isset( $triggers_count[$trigger] ) ? $triggers_count[$trigger] : 0 );
    
	// If is specific trigger then try to get the attached id
	if( in_array( $trigger, array_keys( gamipress_get_specific_activity_triggers() ) ) && isset( $triggers_count[$trigger] ) ) {

	    // Reset the listeners count since is a specific trigger
        $listeners_count = 0;
		$specific_id = 0;

		// If isset this key it means $args is a requirement object
		if( isset( $args['achievement_post'] ) ) {
			$specific_id = absint( $args['achievement_post'] );
		} else if( ! empty( $args ) ) {
			$specific_id = gamipress_specific_trigger_get_id( $trigger, $args );
		}

		// If there is a specific id, then try to find the count
		if( $specific_id !== 0 ) {

            $cache = gamipress_get_cache( "{$trigger}_{$specific_id}_listeners_count", false );

            // If result already cached, return it
            if( $cache !== false ) {
                $listeners_count = absint( $cache );
            } else {
                $listeners_count = $wpdb->get_var( $wpdb->prepare(
                    "SELECT COUNT(*)
                    FROM   {$posts} AS p
                    LEFT JOIN {$postmeta} AS pm ON ( p.ID = pm.post_id AND pm.meta_key = '_gamipress_trigger_type' )
                    LEFT JOIN {$postmeta} AS pm2 ON ( p.ID = pm2.post_id AND pm2.meta_key = '_gamipress_achievement_post' )
                    WHERE p.post_status = 'publish'
					AND p.post_type IN ( '" . implode( "', '", gamipress_get_requirement_types_slugs() ) . "' )
					AND pm.meta_value = %s
					AND pm2.meta_value = %s",
                    $trigger,
                    $specific_id
                ) );

                // Cache listeners count
                gamipress_save_cache( "{$trigger}_{$specific_id}_listeners_count", $listeners_count );
            }

		}

	}
    
	$has_listeners = ( absint( $listeners_count ) > 0 );

    /**
     * Filter to override if trigger has listeners
     *
     * @since 1.0.8
     *
     * @param bool 	    $has_listeners
     * @param string 	$trigger
     * @param integer 	$site_id
     * @param array 	$args
     *
     * @return bool
     */
	return apply_filters( 'gamipress_trigger_has_listeners', $has_listeners, $trigger, $site_id, $args );
}

/**
 * Get all triggers listeners count
 *
 * @since 1.8.0
 *
 * @return array
 */
function gamipress_get_triggers_listeners_count() {

    $cache = gamipress_get_cache( 'gamipress_triggers_listeners_count', false );

    // If result already cached, return it
    if( is_array( $cache ) ) {
        return $cache;
    }

    global $wpdb;

    $posts  	= GamiPress()->db->posts;
    $postmeta  	= GamiPress()->db->postmeta;

    // Get an array with each trigger count
    $results = $wpdb->get_results(
        "SELECT pm.meta_value AS 'trigger', COUNT(*) AS 'count'
         FROM {$postmeta} AS pm
         LEFT JOIN {$posts} AS p ON ( pm.post_id = p.ID )
         WHERE p.post_status = 'publish'
         AND p.post_type IN ( '" . implode( "', '", gamipress_get_requirement_types_slugs() ) . "' )
         AND pm.meta_key = '_gamipress_trigger_type'
         AND pm.meta_value != '' 
         GROUP BY pm.meta_value"
    );

    $listeners_count = array();

    // Turn the results array into a more accessible associative array
    foreach( $results as $result )
        $listeners_count[$result->trigger] = absint( $result->count );

    // Cache listeners count
    gamipress_save_cache( "gamipress_triggers_listeners_count", $listeners_count );

    /**
     * Filter to override if triggers listeners count
     *
     * @since 1.8.0
     *
     * @param array $listeners_count
     *
     * @return array
     */
    return apply_filters( 'gamipress_get_triggers_listeners_count', $listeners_count );

}

/**
 * Return triggered requirements by a specific trigger
 *
 * @since   1.6.1
 * @updated 1.7.9 Improvement on requirements with specific triggers querying them by the specific ID to reduce the amount of triggered requirements
 * @updated 1.7.9 Added $user_id, $site_id and $args parameters
 *
 * @param int 	    $user_id    The User ID
 * @param string 	$trigger    The trigger type
 * @param int 	    $site_id    The site ID where event has been triggered
 * @param array 	$args       The event arguments
 *
 * @return array
 */
function gamipress_get_triggered_requirements( $user_id, $trigger, $site_id, $args ) {

    $triggered_requirements = array();
    $proceed                = true;
    $cache_key              = "{$trigger}_triggered_requirements";
    $query_args             = array(
        'fields'        => 'ids',
        'post_status'   => 'publish',
        'meta_query'    => array(
            '_gamipress_trigger_type' => $trigger,
        ),
    );

    // If is specific trigger then try to get the attached id
    if( in_array( $trigger, array_keys( gamipress_get_specific_activity_triggers() ) ) ) {

        $specific_id = 0;

        // If isset this key it means $args is a requirement object
        if( isset( $args['achievement_post'] ) ) {
            $specific_id = absint( $args['achievement_post'] );
        } else if( ! empty( $args ) ) {
            $specific_id = gamipress_specific_trigger_get_id( $trigger, $args );
        }

        // If there is a specific id, then try to find the count
        if( $specific_id !== 0 ) {

            $cache_key = "{$trigger}_{$specific_id}_triggered_requirements";

            // Add the achievement post ID
            $query_args['meta_query']['_gamipress_achievement_post'] = $specific_id;

        } else {
            // Set proceed to false since the assigned post is not correctly assigned
            $proceed = false;
        }

    } else if( gamipress_starts_with( $trigger, 'gamipress_unlock_' ) ) {
        // Check if trigger is for unlocking ANY and ALL posts of an achievement type

        $event_trigger = $trigger;

        // Events for unlock an achievement of type or all requires to change the trigger
        if( gamipress_starts_with( $trigger, 'gamipress_unlock_all_' ) ) {
            // Replace "gamipress_unlock_all_{achievement-type}" to "all-achievements"
            $event_trigger = 'all-achievements';
        } else if( gamipress_starts_with( $trigger, 'gamipress_unlock_' ) ) {
            // Replace "gamipress_unlock_{achievement-type}" to "any-achievement"
            $event_trigger = 'any-achievement';
        }

        // Update the trigger with the correct trigger
        $query_args['meta_query']['_gamipress_trigger_type'] = $event_trigger;

        // Get the achievement type
        $achievement_type = str_replace( 'gamipress_unlock_all_', '', $trigger );
        $achievement_type = str_replace( 'gamipress_unlock_', '', $achievement_type );

        // Add the achievement type
        $query_args['meta_query']['_gamipress_achievement_type'] = $achievement_type;

    } else if( gamipress_starts_with( $trigger, 'gamipress_revoke_' ) ) {
        // Check if trigger is for revoking ANY posts of an achievement type

        $event_trigger = $trigger;

        // Get the achievement type
        $achievement_type = str_replace( 'gamipress_revoke_', '', $trigger );

        if( in_array( $achievement_type, gamipress_get_achievement_types_slugs() ) ) {
            // Replace "gamipress_revoke_{achievement-type}" to "revoke-any-achievement"
            $event_trigger = 'revoke-any-achievement';

            // Add the achievement type
            $query_args['meta_query']['_gamipress_achievement_type'] = $achievement_type;
        }

        // Update the trigger with the correct trigger
        $query_args['meta_query']['_gamipress_trigger_type'] = $event_trigger;

    }

    /**
     * Filter to modify triggered requirements cache key
     *
     * @since   1.8.8
     *
     * @param bool 	    $proceed
     * @param int 	    $user_id    The User ID
     * @param string 	$trigger    The trigger type
     * @param int 	    $site_id    The site ID where event has been triggered
     * @param array 	$args       The event arguments
     *
     * @return bool
     */
    $proceed = apply_filters( 'gamipress_get_triggered_requirements_proceed', $proceed, $user_id, $trigger, $site_id, $args );

    /**
     * Filter to modify triggered requirements cache key
     *
     * @since   1.8.8
     *
     * @param string 	$cache_key  The cache key to store cached results
     * @param int 	    $user_id    The User ID
     * @param string 	$trigger    The trigger type
     * @param int 	    $site_id    The site ID where event has been triggered
     * @param array 	$args       The event arguments
     *
     * @return string
     */
    $cache_key = apply_filters( 'gamipress_get_triggered_requirements_cache_key', $cache_key, $user_id, $trigger, $site_id, $args );

    /**
     * Filter to modify triggered requirements query args
     *
     * @since   1.8.8
     *
     * @see gamipress_query_requirements()
     *
     * @param array 	$query_args The query args to query triggered requirements
     * @param int 	    $user_id    The User ID
     * @param string 	$trigger    The trigger type
     * @param int 	    $site_id    The site ID where event has been triggered
     * @param array 	$args       The event arguments
     *
     * @return array
     */
    $query_args = apply_filters( 'gamipress_get_triggered_requirements_query_args', $query_args, $user_id, $trigger, $site_id, $args );

    if( $proceed ) {

        $cache = gamipress_get_cache( $cache_key, false );

        // If result already cached, return it
        if( $cache !== false && is_array( $cache ) ) {
            $triggered_requirements = $cache;
        } else {
            // Get requirements with this query args
            $triggered_requirements = gamipress_query_requirements( $query_args );

            // Cache function result
            gamipress_save_cache( $cache_key, $triggered_requirements );

        }

    }

    /**
     * Filter to modify triggered requirements by a specific trigger
     *
     * @since   1.6.1
     * @updated 1.7.9 Added $user_id, $site_id and $args parameters
     *
     * @param array 	$triggered_requirements
     * @param int 	    $user_id    The User ID
     * @param string 	$trigger    The trigger type
     * @param int 	    $site_id    The site ID where event has been triggered
     * @param array 	$args       The event arguments
     *
     * @return array
     */
    $triggered_requirements = apply_filters( 'gamipress_get_triggered_requirements', $triggered_requirements, $user_id, $trigger, $site_id, $args );

    return $triggered_requirements;

}

/**
 * Sort triggered requirements by rank priority in order to send them ordered to the awards engine
 *
 * @since 1.6.2
 *
 * @param array 	$triggered_requirements
 * @param integer 	$user_id
 * @param string 	$trigger
 * @param integer 	$site_id
 * @param array 	$args
 *
 * @return array
 */
function gamipress_sort_triggered_rank_requirements( $triggered_requirements, $user_id = 0, $trigger = '', $site_id = 0, $args = array() ) {
    $reordered_requirements = array();

    // Loop all triggered requirements
    foreach( $triggered_requirements as $index => $requirement ) {

        // If is rank requirement, add it to the reordered requirements array
        if( gamipress_get_post_type( $requirement->ID ) === 'rank-requirement' ) {

            // Get the requirement's rank ID
            $rank_id = absint( gamipress_get_post_field( 'post_parent', $requirement->ID ) );

            if( $rank_id !== 0 ) {

                // Get the rank's priority and add the requirement to reordered requirements array
                $priority = gamipress_get_rank_priority( $rank_id );
                
                if( ! isset( $reordered_requirements[$priority] ) )
                    $reordered_requirements[$priority] = array();

                $reordered_requirements[$priority][] = $requirement;

                // Remove requirement from triggered requirements (will be added after again)
                unset( $triggered_requirements[$index] );

            }

        }

    }

    // To order the requirements by priority
    ksort( $reordered_requirements );
    
    // Append rank requirements reordered to the triggered requirements array
    foreach( $reordered_requirements as $requirements ) {
        $triggered_requirements = array_merge( $triggered_requirements, $requirements );
    }

    return $triggered_requirements;

}
add_filter( 'gamipress_get_triggered_requirements', 'gamipress_sort_triggered_rank_requirements', 10, 5 );

/**
 * Delete cache of a specific trigger
 *
 * @since   1.6.1
 * @updated 1.7.9 Added support to the brand new triggered requirements cache
 *
 * @param string $trigger
 *
 * @return bool
 */
function gamipress_delete_trigger_cache( $trigger ) {

    global $wpdb;

    // Bail if empty trigger
    if( empty( $trigger ) ) {
        return false;
    }

    // Listeners count cache varies if is specific trigger
    if( in_array( $trigger, array_keys( gamipress_get_specific_activity_triggers() ) ) ) {

        // Cache prefix and suffix
        $trigger = sanitize_text_field( $trigger );
        $prefix = "gamipress_cache_{$trigger}_";
        $triggered_suffix = '_triggered_requirements';
        $listeners_suffix = '_listeners_count';

        // Delete listeners count cache for specific trigger

        if( gamipress_is_network_wide_active() ) {
            // Multi site installs
            $wpdb->query( "DELETE FROM {$wpdb->sitemeta} WHERE meta_key LIKE '{$prefix}%' AND ( meta_key LIKE '%{$triggered_suffix}' OR meta_key LIKE '%{$listeners_suffix}' )");
        } else {
            // Single site installs
            $wpdb->query( "DELETE FROM {$wpdb->options} WHERE option_name LIKE '{$prefix}%' AND ( option_name LIKE '%{$triggered_suffix}' OR option_name LIKE '%{$listeners_suffix}' )" );
        }

    } else if ( $trigger === 'any-achievement' || $trigger === 'all-achievements' || $trigger === 'revoke-any-achievement' ) {

        // Cache prefix and suffix
        $prefix = 'gamipress_cache_gamipress_unlock_';
        $triggered_suffix = '_triggered_requirements';
        $listeners_suffix = '_listeners_count';

        if( $trigger === 'revoke-any-achievement' ) {
            $prefix = 'gamipress_cache_gamipress_revoke_';
        }

        // Delete listeners count cache for unlock all/specific achievement
        if( gamipress_is_network_wide_active() ) {
            // Multi site installs
            $wpdb->query( "DELETE FROM {$wpdb->sitemeta} WHERE meta_key LIKE '{$prefix}%' AND ( meta_key LIKE '%{$triggered_suffix}' OR meta_key LIKE '%{$listeners_suffix}' )");
        } else {
            // Single site installs
            $wpdb->query( "DELETE FROM {$wpdb->options} WHERE option_name LIKE '{$prefix}%' AND ( option_name LIKE '%{$triggered_suffix}' OR option_name LIKE '%{$listeners_suffix}' )" );
        }

    } else {

        // Delete triggered requirements cache
        gamipress_delete_cache( "{$trigger}_triggered_requirements" );

        // Delete listeners count cache for not specific trigger
        gamipress_delete_cache( "{$trigger}_listeners_count" );
    }

    // Delete all listeners count cache
    gamipress_delete_cache( 'gamipress_triggers_listeners_count' );

    return true;

}

/**
 * Wrapper function for returning a user's array of sprung triggers
 *
 * @since  1.0.0
 *
 * @param  integer $user_id The given user's ID
 * @param  integer $site_id The desired Site ID to check
 *
 * @return array            An array of the triggers a user has triggered
 */
function gamipress_get_user_triggers( $user_id = 0, $site_id = 0 ) {

	// Grab all of the user's triggers
	$user_triggers = ( $exists = gamipress_get_user_meta( $user_id, '_gamipress_triggered_triggers' ) ) ? $exists : array( $site_id => array() );

	// Use current site ID if site ID is not set, AND not explicitly set to false
	if ( ! $site_id && false !== $site_id ) {
		$site_id = get_current_blog_id();
	}

	// Return only the triggers that are relevant to the provided $site_id
	if ( $site_id && isset( $user_triggers[ $site_id ] ) ) {
		return $user_triggers[ $site_id ];

	// Otherwise, return the full array of all triggers across all sites
	} else {
		return $user_triggers;
	}
}

/**
 * Get the count of the number of times a user has triggered a particular trigger
 *
 * @since   1.0.0
 * @updated 1.6.2 Added use of gamipress_get_user_triggers() function when $since is 0
 * @updated 1.6.3 Revert back 1.6.2 and full function refactoring to make use of gamipress_get_user_log_count() function and added new filters
 *
 * @param  int      $user_id    The given user's ID
 * @param  string   $trigger    The given trigger we're checking
 * @param  int      $since 	    The since timestamp where retrieve the logs
 * @param  int      $site_id    The desired Site ID to check
 * @param  array    $args       The triggered args or requirement object
 *
 * @return int                  The total number of times a user has triggered the trigger
 */
function gamipress_get_user_trigger_count( $user_id, $trigger, $since = 0, $site_id = 0, $args = array() ) {

	// If not properly upgrade to required version fallback to compatibility function
	if( ! is_gamipress_upgraded_to( '1.4.7' ) ) {
		return gamipress_get_user_trigger_count_old_147(  $user_id, $trigger, $since, $site_id, $args );
	}

	// Set to current site id
	if ( ! $site_id ) {
		$site_id = get_current_blog_id();
    }

    // Setup the meta data to filter the logs count
    $log_meta = array(
        'type'          => 'event_trigger',
        'trigger_type'  => $trigger,
    );

    // If is specific trigger then try to get the attached id
    if( in_array( $trigger, array_keys( gamipress_get_specific_activity_triggers() ) ) ) {

        $specific_id = 0;

        // If isset this key it means $args is a requirement object
        if( isset( $args['achievement_post'] ) ) {
            $specific_id = absint( $args['achievement_post'] );
        } else if( ! empty( $args ) ) {
            $specific_id = gamipress_specific_trigger_get_id( $trigger, $args );
        }

        // If there is a specific id, then try to find the count
        if( $specific_id !== 0 ) {
            $log_meta['achievement_post'] = $specific_id;
        }

    }

    /**
     * Filter to override the meta data to filter the logs count
     *
     * @since   1.6.3
     *
     * @param  array    $log_meta       The meta data to filter the logs count
     * @param  int      $user_id        The given user's ID
     * @param  string   $trigger        The given trigger we're checking
     * @param  int      $since 	        The since timestamp where retrieve the logs
     * @param  int      $site_id        The desired Site ID to check
     * @param  array    $args           The triggered args or requirement object
     *
     * @return array                    The meta data to filter the logs count
     */
    $log_meta = apply_filters( 'gamipress_get_user_trigger_count_log_meta', $log_meta, $user_id, $trigger, $since, $site_id, $args );

    // Get the trigger count
    $trigger_count = gamipress_get_user_log_count( absint( $user_id ), $log_meta, $since );

    /**
     * Filter to override the number of times a user has triggered a particular trigger
     *
     * @since   1.6.3
     *
     * @param  int      $trigger_count  The total number of times a user has triggered the trigger
     * @param  int      $user_id        The given user's ID
     * @param  string   $trigger        The given trigger we're checking
     * @param  int      $since 	        The since timestamp where retrieve the logs
     * @param  int      $site_id        The desired Site ID to check
     * @param  array    $args           The triggered args or requirement object
     *
     * @return int                      The total number of times a user has triggered the trigger
     */
    $trigger_count = apply_filters( 'gamipress_get_user_trigger_count', absint( $trigger_count ), $user_id, $trigger, $since, $site_id, $args );

    // Return the current count for the given trigger
	return absint( $trigger_count );

}

/**
 * Update the user's trigger count for a given trigger by 1
 *
 * @since  1.0.0
 *
 * @param  integer 	$user_id 	The given user's ID
 * @param  string  	$trigger 	The trigger we're updating
 * @param  integer 	$site_id 	The desired Site ID to update
 * @param  array 	$args 		The triggered args
 *
 * @return integer          	The updated trigger count
 */
function gamipress_update_user_trigger_count( $user_id, $trigger, $site_id = 0, $args = array() ) {

	// Set to current site id
	if ( ! $site_id ) {
		$site_id = get_current_blog_id();
    }

	// Get the user triggered triggers
	$user_triggers = gamipress_get_user_triggers( $user_id, $site_id );

    // Check to ensure is an array
    if ( !is_array( $user_triggers ) )
        return;

	if( isset( $user_triggers[$site_id][$trigger] ) ) {
		// If already have this count, just retrieve it
		$trigger_count = absint( $user_triggers[$site_id][$trigger] );
	} else {
		// Grab the current count directly from the database
		$trigger_count = absint( gamipress_get_user_trigger_count( $user_id, $trigger, 0, $site_id, $args ) );
	}

	// Increase the current count by 1
	$trigger_count += (int) apply_filters( 'gamipress_update_user_trigger_count', 1, $user_id, $trigger, $site_id, $args );

	// Update the triggers array with the new count
	$user_triggers[$site_id][$trigger] = $trigger_count;
	gamipress_update_user_meta( $user_id, '_gamipress_triggered_triggers', $user_triggers );

	// Send back our trigger count for other purposes
	return $trigger_count;

}

/**
 * Reset a user's trigger count for a given trigger to 0 or reset ALL triggers
 *
 * @since  1.0.0
 *
 * @param  integer $user_id The given user's ID
 * @param  string  $trigger The trigger we're updating (or "all" to dump all triggers)
 * @param  integer $site_id The desired Site ID to update (or "all" to dump across all sites)
 */
function gamipress_reset_user_trigger_count( $user_id, $trigger, $site_id = 0 ) {

	// Set to current site id
	if ( ! $site_id )
		$site_id = get_current_blog_id();

	// Grab the user's current triggers
	$user_triggers = gamipress_get_user_triggers( $user_id, false );

	// If we're deleteing all triggers...
	if ( 'all' == $trigger ) {
		// For all sites
		if ( 'all' == $site_id )
			$user_triggers = array();
		// For a specific site
		else
			$user_triggers[$site_id] = array();
	// Otherwise, reset the specific trigger back to zero
	} else {
		$user_triggers[$site_id][$trigger] = 0;
	}

	// Finally, update our user meta
	gamipress_update_user_meta( $user_id, '_gamipress_triggered_triggers', $user_triggers );

}
