14 if ( ! defined(
'ABSPATH' ) ) {
58 add_filter(
'gform_form_settings_fields', array( $this,
'_filter_gform_form_settings_fields' ), 10, 2 );
59 add_filter(
'gform_custom_merge_tags', array( $this,
'_filter_gform_custom_merge_tags' ), 10, 4 );
60 add_filter(
'gform_replace_merge_tags', array( $this,
'_filter_gform_replace_merge_tags' ), 10, 7 );
62 add_action(
'init', array( $this,
'maybe_update_approved' ) );
63 add_action(
'init', array( $this,
'maybe_show_approval_notice' ) );
78 $global_default =
gravityview()->plugin->settings->get(
'public_entry_moderation',
'0' );
80 $fields[
'restrictions'][
'fields'][] = array(
81 'name' => self::FORM_SETTINGS_KEY,
85 'label' => sprintf( esc_html__(
'GravityView - %s',
'gk-gravityview' ), esc_html__(
'Enable Public Entry Moderation',
'gk-gravityview' ) ),
86 'description' => strtr(
88 __(
'If enabled, adding {public} to {link}entry moderation merge tags{/link} will allow logged-out users to approve or reject entries. If disabled, all entry moderation actions require the user to be logged-in and have the ability to edit the entry.',
'gk-gravityview' ),
90 '{public}' =>
'<code style="font-size: .9em">:public</code>',
91 '{link}' =>
'<a href="https://docs.gravitykit.com/article/904-entry-moderation-merge-tags" target="_blank" rel="noopener noreferrer">',
92 '{/link}' =>
'<span class="screen-reader-text"> ' . esc_html__(
'(This link opens in a new window.)',
'gk-gravityview' ) .
'</span></a>',
97 'label' => _x(
'Enable',
'Setting: On or off',
'gk-gravityview' ),
101 'label' => _x(
'Disable',
'Setting: On or off',
'gk-gravityview' ),
105 'default_value' => (
string) $global_default,
125 $entry_moderation_merge_tags = array(
127 'label' => __(
'Moderation: Approve entry link',
'gravityview' ),
128 'tag' =>
'{gv_approve_entry}',
131 'label' => __(
'Moderation: Disapprove entry link',
'gravityview' ),
132 'tag' =>
'{gv_disapprove_entry}',
135 'label' => __(
'Moderation: Reset entry approval link',
'gravityview' ),
136 'tag' =>
'{gv_unapprove_entry}',
140 return array_merge( $custom_merge_tags, $entry_moderation_merge_tags );
160 preg_match_all(
'/{gv_((?:dis|un)?approve)_entry:?(?:(\d+)([d|h|m|s]))?:?(public)?}/', $text, $matches, PREG_SET_ORDER );
163 if ( empty( $matches ) ) {
167 if ( ! isset(
$form[ self::FORM_SETTINGS_KEY ] ) ) {
168 $form[ self::FORM_SETTINGS_KEY ] =
gravityview()->plugin->settings->get(
'public_entry_moderation' );
190 foreach ( $matches as $match ) {
192 $full_tag = $match[0];
194 $expiration_value = ! empty( $match[2] ) ? (int) $match[2] : self::DEFAULT_EXPIRATION_VALUE;
195 $expiration_unit = ! empty( $match[3] ) ? $match[3] : self::DEFAULT_EXPIRATION_UNIT;
196 $privacy = isset( $match[4] ) ? $match[4] :
'private';
198 switch ( $expiration_unit ) {
200 $expiration_unit =
'days';
204 $expiration_unit =
'hours';
207 $expiration_unit =
'minutes';
210 $expiration_unit =
'seconds';
214 if (
false === (
bool) \
GV\Utils::get(
$form, self::FORM_SETTINGS_KEY,
false ) ) {
215 $privacy =
'private';
218 $expiration_timestamp = strtotime(
"+{$expiration_value} {$expiration_unit}" );
219 $expiration_seconds = $expiration_timestamp - time();
221 $token = $this->
get_token( $action, $expiration_timestamp, $privacy,
$entry );
227 $link_url = $this->
get_link_url( $token, $expiration_seconds, $privacy );
231 $link = sprintf(
'<a href="%s">%s</a>', esc_url_raw( $link_url ), esc_html( $anchor_text ) );
233 $text = str_replace( $full_tag,
$link, $text );
253 protected function get_token( $action =
false, $expiration_timestamp = 0, $privacy =
'private',
$entry = array() ) {
255 if ( ! $action || !
$entry[
'id'] ) {
259 if ( ! $expiration_timestamp ) {
264 $privacy =
'private';
269 if ( ! $approval_status ) {
274 $expiration_seconds = $expiration_timestamp - time();
277 'entry_id' =>
$entry[
'id'],
278 'approval_status' => $approval_status,
279 'expiration_seconds' => $expiration_seconds,
280 'privacy' => $privacy,
283 $token_array = array(
285 'exp' => $expiration_timestamp,
290 $token = rawurlencode( base64_encode( json_encode( $token_array ) ) );
292 $secret = wp_salt(
'nonce' );
294 $sig = hash_hmac(
'sha256', $token, $secret );
296 $token .=
'.' . $sig;
319 return $values[ $key ];
332 protected function get_link_url( $token =
false, $expiration_seconds = DAY_IN_SECONDS, $privacy =
'private' ) {
334 if (
'public' === $privacy ) {
337 $base_url = admin_url(
'admin.php?page=gf_entries' );
340 $query_args = array();
342 if ( ! empty( $token ) ) {
343 $query_args[ self::TOKEN_URL_ARG ] = $token;
346 if (
'private' === $privacy && DAY_IN_SECONDS >= (
int) $expiration_seconds ) {
347 $query_args[
'nonce'] = wp_create_nonce( self::TOKEN_URL_ARG );
350 return add_query_arg( $query_args,
$base_url );
371 if ( ! $token_string ) {
377 if ( is_wp_error( $token ) ) {
379 gravityview()->log->error(
'Decoding the entry approval token failed.', array(
'data' => $token ) );
381 wp_die( sprintf( __(
'Entry moderation failed: %s',
'gravityview' ), $token->get_error_message() ) );
387 if ( is_wp_error( $is_valid_token ) ) {
389 gravityview()->log->error(
'Validating the entry approval token failed.', array(
'data' => $is_valid_token ) );
391 wp_die( sprintf( __(
'Entry moderation failed: %s',
'gravityview' ), $is_valid_token->get_error_message() ) );
396 if ( is_wp_error( $is_request_valid ) ) {
398 gravityview()->log->error(
'Validating the entry approval token failed.', array(
'data' => $is_request_valid ) );
400 wp_die( sprintf( __(
'Entry moderation failed: %s',
'gravityview' ), $is_request_valid->get_error_message() ) );
403 $scopes = $token[
'scopes'];
405 $entry_id = $scopes[
'entry_id'];
406 $approval_status = $scopes[
'approval_status'];
408 $entry = GFAPI::get_entry( $entry_id );
410 if ( is_wp_error(
$entry ) ) {
411 gravityview()->log->error(
'Entry moderation failed: the entry was not found.', array(
'data' =>
$entry ) );
413 wp_die(
$entry->get_error_message() );
418 if (
'private' === $scopes[
'privacy'] ) {
419 $return_url = admin_url(
'/admin.php?page=gf_entries&s=' . $entry_id .
'&field_id=entry_id&operator=is&id=' . $form_id );
421 $return_url = home_url(
'/' );
427 gravityview()->log->error(
'Invalid approval status', array(
'data' => $scopes ) );
429 wp_safe_redirect( add_query_arg( array( self::NOTICE_URL_ARG =>
'error' ), $return_url ) );
434 if ( empty( $entry_id ) || empty( $form_id ) ) {
436 gravityview()->log->error(
'entry_id or form_id are empty.', array(
'data' => $scopes ) );
438 wp_safe_redirect( add_query_arg( array( self::NOTICE_URL_ARG =>
'error' ), $return_url ) );
443 if (
'private' === $scopes[
'privacy'] && !
GVCommon::has_cap(
'gravityview_moderate_entries', $entry_id ) ) {
445 gravityview()->log->error(
'User does not have the `gravityview_moderate_entries` capability.' );
447 wp_safe_redirect( add_query_arg( array( self::NOTICE_URL_ARG =>
'error' ), $return_url ) );
452 $this->
update_approved( $entry_id, $approval_status, $form_id, $scopes, $return_url );
466 if (
'private' === $token[
'scopes'][
'privacy'] && ! is_user_logged_in() ) {
467 return new WP_Error(
'user_not_logged_in', __(
'You are not allowed to perform this operation.',
'gravityview' ) );
470 if ( $token[
'exp'] < time() ) {
471 gravityview()->log->error(
'The entry moderation link expired.', array(
'data' => $is_valid_token ) );
473 return new WP_Error(
'link_expired', esc_html__(
'The link has expired.',
'gk-gravityview' ) );
477 if (
'private' === $token[
'scopes'][
'privacy'] && DAY_IN_SECONDS >= $token[
'scopes'][
'expiration_seconds'] ) {
479 if ( ! isset( $_REQUEST[
'nonce'] ) ) {
480 gravityview()->log->error(
'Entry moderation failed: No nonce was set for entry approval.' );
482 return new WP_Error(
'missing_nonce', esc_html__(
'The link is invalid.',
'gk-gravityview' ) );
485 $nonce_validation = wp_verify_nonce(
GV\Utils::_GET(
'nonce' ), self::TOKEN_URL_ARG );
487 if ( ! $nonce_validation ) {
488 gravityview()->log->error(
'Entry moderation failed: Nonce was invalid.', array(
'data' => $nonce_validation ) );
490 return new WP_Error(
'invalid_nonce', esc_html__(
'The link has expired.',
'gk-gravityview' ) );
516 if ( ! $result || ! $approval_status || ! $entry_id ) {
521 $approval_label = mb_strtolower( $approval_label );
523 if (
'success' === $result ) {
526 $message = esc_html__(
'Success: Entry #{entry_id} has been {approval_label}.',
'gk-gravityview' );
528 $css_class =
'updated';
532 $message = esc_html__(
'There was an error updating entry #{entry_id}.',
'gk-gravityview' );
534 $css_class =
'error';
537 $message = strtr( $message, array(
538 '{entry_id}' => esc_html( $entry_id ),
539 '{approval_label}' => esc_html( $approval_label ),
563 return new WP_Error(
'missing_token', __(
'Invalid security token.',
'gk-gravityview' ) );
566 $parts = explode(
'.', $token );
568 if ( count( $parts ) < 2 ) {
569 return new WP_Error(
'missing_period', __(
'Invalid security token.',
'gk-gravityview' ) );
576 list( $body_64, $sig ) = $parts;
578 if ( empty( $sig ) ) {
579 return new WP_Error(
'approve_link_no_signature', esc_html__(
'The link is invalid.',
'gk-gravityview' ) );
582 $secret = wp_salt(
'nonce' );
583 $verification_sig = hash_hmac(
'sha256', $body_64, $secret );
584 $verification_sig2 = hash_hmac(
'sha256', rawurlencode( $body_64 ), $secret );
586 if ( ! hash_equals( $sig, $verification_sig ) && ! hash_equals( $sig, $verification_sig2 ) ) {
587 return new WP_Error(
'approve_link_failed_signature_verification', esc_html__(
'The link is invalid.',
'gk-gravityview' ) );
590 $body_json = base64_decode( $body_64 );
591 $decoded_token = json_decode( $body_json,
true );
593 if ( empty( $body_json ) || empty( $decoded_token ) ) {
594 $decoded_token = base64_decode( urldecode( $body_64 ) );
597 if ( empty( $decoded_token ) ) {
598 return new WP_Error(
'approve_link_failed_base64_decode', esc_html__(
'The link is invalid.',
'gk-gravityview' ) );
601 return $decoded_token;
615 $required_keys = array(
621 foreach ( $required_keys as $required_key ) {
622 if ( ! isset( $token[ $required_key ] ) ) {
623 return new WP_Error(
'approve_link_no_' . $required_key, esc_html__(
'The link is invalid.',
'gk-gravityview' ) );
627 $required_scopes = array(
628 'expiration_seconds',
634 foreach ( $required_scopes as $required_scope ) {
635 if ( ! isset( $token[
'scopes'][ $required_scope ] ) ) {
636 return new WP_Error(
'approve_link_no_' . $required_scope .
'_scope', esc_html__(
'The link is invalid.',
'gk-gravityview' ) );
658 $query_args = $scopes;
660 $query_args[ self::NOTICE_URL_ARG ] = $result ?
'success' :
'error';
662 $return_url = add_query_arg( $query_args, $return_url );
664 wp_safe_redirect( esc_url_raw( $return_url ) );
_filter_gform_replace_merge_tags( $text, $form=array(), $entry=array(), $url_encode=false, $esc_html=false)
Matches the merge tag in replacement text for the field.
is_request_valid( $token)
Verifies the token.
static get_values()
Get the status values as an array.
static _GET( $name, $default=null)
Grab a value from the _GET superglobal or default.
replace_merge_tag( $matches=array(), $text='', $form=array(), $entry=array(), $url_encode=false, $esc_html=false)
Replaces merge tags.
_filter_gform_form_settings_fields( $fields=array(), $form=array())
Filters existing GF Form Settings Fields.
_filter_gform_custom_merge_tags( $custom_merge_tags=array(), $form_id=0, $fields=array(), $element_id='')
Adds custom merge tags to merge tag options.
maybe_update_approved()
Checks page load for approval link token then maybe process it.
update_approved( $entry_id, $approval_status, $form_id, $scopes, $return_url)
Updates the entry approval status and redirects to $return_url.
maybe_show_approval_notice()
Checks page load for approval link result then maybe show notice.
static generate_notice( $notice, $class='', $cap='', $object_id=null)
Display updated/error notice.
get_approval_status( $action=false)
Returns an approval status based on the provided action.
if( $add_query_args) $link
static get_action( $value_or_key)
Get the label for a specific approval value.
static get_key( $value)
Get the status key for a value.
new GravityView_Entry_Approval_Merge_Tags
if(gravityview() ->plugin->is_GF_25()) $form
If this file is called directly, abort.
decode_token( $token=false)
Decodes received token to its original form.
static is_valid( $value=NULL)
Check whether the passed value is one of the defined values for entry approval.
const DEFAULT_EXPIRATION_UNIT
Default value for the expiration_unit modifier.
static update_approved( $entry_id=0, $approved=2, $form_id=0, $approvedcolumn=0)
update_approved function.
is_token_valid(array $token)
Validates an approval token.
get_token( $action=false, $expiration_timestamp=0, $privacy='private', $entry=array())
Generates a JWT token based on the merge tag parameters.
const DEFAULT_EXPIRATION_VALUE
Default value for the expiration modifier.
if(empty( $created_by)) $form_id
__construct()
Initialization.
get_link_url( $token=false, $expiration_seconds=DAY_IN_SECONDS, $privacy='private')
Generates an approval link URL.
gravityview()
The main GravityView wrapper function.
add_hooks()
Adds actions and filters related to entry approval links.
static has_cap( $caps='', $object_id=null, $user_id=null)
Alias of GravityView_Roles_Capabilities::has_cap()
const TOKEN_URL_ARG
The name of the query arg used to pass token information to the approval URL.
static get_label( $value_or_key)
Get the label for a specific approval value.