594 lines
18 KiB
PHP
594 lines
18 KiB
PHP
<?php
|
|
|
|
if ( ! class_exists( 'GFForms' ) ) {
|
|
die();
|
|
}
|
|
|
|
/**
|
|
* Handles all tasks related to locking.
|
|
*
|
|
* - Loads the WordPress Heartbeat API and scripts & styles for GF Locking
|
|
* - Provides standardized UX
|
|
*
|
|
* @package GFLocking
|
|
* @author Rocketgenius
|
|
*/
|
|
abstract class GFLocking {
|
|
private $_object_type;
|
|
private $_object_id;
|
|
private $_edit_url;
|
|
private $_redirect_url;
|
|
private $_capabilities;
|
|
const PREFIX_EDIT_LOCK = 'lock_';
|
|
const PREFIX_EDIT_LOCK_REQUEST = 'lock_request_';
|
|
|
|
|
|
public function __construct( $object_type, $redirect_url, $edit_url = '', $capabilities = array() ) {
|
|
$this->_object_type = $object_type;
|
|
$this->_redirect_url = $redirect_url;
|
|
$this->_capabilities = $capabilities;
|
|
if ( defined( 'DOING_AJAX' ) && DOING_AJAX ) {
|
|
$this->init_ajax();
|
|
} else {
|
|
$this->register_scripts();
|
|
$is_locking_page = false;
|
|
$is_edit_page = false;
|
|
if ( $this->is_edit_page() ) {
|
|
$this->init_edit_lock();
|
|
$is_locking_page = true;
|
|
$is_edit_page = true;
|
|
} else if ( $this->is_list_page() ) {
|
|
$this->init_list_page();
|
|
$is_locking_page = true;
|
|
} else if ( $this->is_view_page() ) {
|
|
$this->init_view_page();
|
|
$is_locking_page = true;
|
|
}
|
|
if ( $is_locking_page ) {
|
|
$this->_object_id = $this->get_object_id();
|
|
$this->_edit_url = $edit_url;
|
|
$this->maybe_lock_object( $is_edit_page );
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Override this method to check the condition for the edit page.
|
|
*
|
|
* @return bool
|
|
*/
|
|
protected function is_edit_page() {
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Override this method to check the condition for the list page.
|
|
*
|
|
* @return bool
|
|
*/
|
|
protected function is_list_page() {
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Override this method to check the condition for the view page.
|
|
*
|
|
* @return bool
|
|
*/
|
|
protected function is_view_page() {
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Override this method to provide the class with the correct object id.
|
|
*
|
|
* @return bool
|
|
*/
|
|
protected function get_object_id() {
|
|
$id = rgget( 'id' );
|
|
$id = absint( $id );
|
|
return $id; // example in the case of form id
|
|
}
|
|
|
|
public function init_edit_lock() {
|
|
add_action( 'admin_enqueue_scripts', array( $this, 'enqueue_scripts' ) );
|
|
}
|
|
|
|
public function init_ajax() {
|
|
add_filter( 'heartbeat_received', array( $this, 'heartbeat_refresh_nonces' ), 10, 3 );
|
|
add_filter( 'heartbeat_received', array( $this, 'heartbeat_check_locked_objects' ), 10, 3 );
|
|
add_filter( 'heartbeat_received', array( $this, 'heartbeat_refresh_lock' ), 10, 3 );
|
|
add_filter( 'heartbeat_received', array( $this, 'heartbeat_request_lock' ), 10, 3 );
|
|
add_filter( 'wp_ajax_gf_lock_request_' . $this->_object_type, array( $this, 'ajax_lock_request' ) );
|
|
add_filter( 'wp_ajax_gf_reject_lock_request_' . $this->_object_type, array( $this, 'ajax_reject_lock_request' ) );
|
|
}
|
|
|
|
public function init_list_page() {
|
|
add_action( 'admin_enqueue_scripts', array( $this, 'enqueue_list_scripts' ) );
|
|
}
|
|
|
|
public function init_view_page() {
|
|
add_action( 'admin_enqueue_scripts', array( $this, 'enqueue_view_page_scripts' ) );
|
|
}
|
|
|
|
public function register_scripts() {
|
|
$min = defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG || isset( $_GET['gform_debug'] ) ? '' : '.min';
|
|
$locking_path = GFCommon::get_base_url() . '/includes/locking/';
|
|
wp_register_script( 'gforms_locking', $locking_path . "js/locking{$min}.js", array( 'jquery', 'heartbeat' ), GFCommon::$version );
|
|
wp_register_script( 'gforms_locking_view', $locking_path . "js/locking-view{$min}.js", array( 'jquery', 'heartbeat' ), GFCommon::$version );
|
|
wp_register_script( 'gforms_locking_list', $locking_path . "js/locking-list{$min}.js", array( 'jquery', 'heartbeat' ), GFCommon::$version );
|
|
wp_register_style( 'gforms_locking_css', $locking_path . "css/locking{$min}.css", array(), GFCommon::$version );
|
|
wp_register_style( 'gforms_locking_list_css', $locking_path . "css/locking-list{$min}.css", array(), GFCommon::$version );
|
|
|
|
// No conflict scripts
|
|
add_filter( 'gform_noconflict_scripts', array( $this, 'register_noconflict_scripts' ) );
|
|
add_filter( 'gform_noconflict_styles', array( $this, 'register_noconflict_styles' ) );
|
|
}
|
|
|
|
public function register_noconflict_scripts( $scripts ) {
|
|
$locking_scripts = array( 'gforms_locking', 'gforms_locking_list', 'gforms_locking_view' );
|
|
|
|
return array_merge( $scripts, $locking_scripts );
|
|
}
|
|
|
|
public function register_noconflict_styles( $styles ) {
|
|
$locking_styles = array( 'gforms_locking_css', 'gforms_locking_list_css' );
|
|
|
|
return array_merge( $styles, $locking_styles );
|
|
}
|
|
|
|
public function enqueue_scripts() {
|
|
|
|
|
|
wp_enqueue_script( 'gforms_locking' );
|
|
wp_enqueue_style( 'gforms_locking_css' );
|
|
$lock_user_id = $this->check_lock( $this->get_object_id() );
|
|
|
|
$strings = array(
|
|
'noResponse' => $this->get_string( 'no_response' ),
|
|
'requestAgain' => $this->get_string( 'request_again' ),
|
|
'requestError' => $this->get_string( 'request_error' ),
|
|
'gainedControl' => $this->get_string( 'gained_control' ),
|
|
'rejected' => $this->get_string( 'request_rejected' ),
|
|
'pending' => $this->get_string( 'request_pending' )
|
|
);
|
|
|
|
|
|
$vars = array(
|
|
'hasLock' => ! $lock_user_id ? 1 : 0,
|
|
'lockUI' => $this->get_lock_ui( $lock_user_id ),
|
|
'objectID' => $this->_object_id,
|
|
'objectType' => $this->_object_type,
|
|
'strings' => $strings,
|
|
);
|
|
|
|
wp_localize_script( 'gforms_locking', 'gflockingVars', $vars );
|
|
}
|
|
|
|
public function enqueue_list_scripts() {
|
|
|
|
wp_enqueue_script( 'gforms_locking_list' );
|
|
wp_enqueue_style( 'gforms_locking_list_css' );
|
|
|
|
$vars = array(
|
|
'objectType' => $this->_object_type,
|
|
);
|
|
|
|
wp_localize_script( 'gforms_locking_list', 'gflockingVars', $vars );
|
|
|
|
}
|
|
|
|
public function enqueue_view_page_scripts() {
|
|
|
|
wp_enqueue_script( 'gforms_locking_view' );
|
|
wp_enqueue_style( 'gforms_locking_view_css' );
|
|
|
|
$lock_user_id = $this->check_lock( $this->get_object_id() );
|
|
$vars = array(
|
|
'hasLock' => ! $lock_user_id ? 1 : 0,
|
|
'objectID' => $this->_object_id,
|
|
'objectType' => $this->_object_type,
|
|
);
|
|
|
|
wp_localize_script( 'gforms_locking_view', 'gflockingVars', $vars );
|
|
}
|
|
|
|
|
|
protected function get_strings() {
|
|
$strings = array(
|
|
'currently_locked' => wp_strip_all_tags( __( 'This page is currently locked. Click on the "Request Control" button to let %s know you\'d like to take over.', 'gravityforms' ) ),
|
|
'accept' => wp_strip_all_tags( __( 'Accept', 'gravityforms' ) ),
|
|
'cancel' => wp_strip_all_tags( __( 'Cancel', 'gravityforms' ) ),
|
|
'currently_editing' => wp_strip_all_tags( __( '%s is currently editing', 'gravityforms' ) ),
|
|
'taken_over' => wp_strip_all_tags( __( '%s has taken over and is currently editing.', 'gravityforms' ) ),
|
|
'lock_requested' => wp_strip_all_tags( __( '%s has requested permission to take over control.', 'gravityforms' ) ),
|
|
'gained_control' => wp_strip_all_tags( __( 'You now have control', 'gravityforms' ) ),
|
|
'request_pending' => wp_strip_all_tags( __( 'Pending', 'gravityforms' ) ),
|
|
'no_response' => wp_strip_all_tags( __( 'No response', 'gravityforms' ) ),
|
|
'request_again' => wp_strip_all_tags( __( 'Request again', 'gravityforms' ) ),
|
|
'request_error' => wp_strip_all_tags( __( 'Error', 'gravityforms' ) ),
|
|
'request_rejected' => wp_strip_all_tags( __( 'Your request was rejected', 'gravityforms' ) ),
|
|
);
|
|
|
|
return $strings;
|
|
}
|
|
|
|
public function ajax_lock_request() {
|
|
$object_id = rgget( 'object_id' );
|
|
$response = $this->request_lock( $object_id );
|
|
echo json_encode( $response );
|
|
die();
|
|
}
|
|
|
|
public function ajax_reject_lock_request() {
|
|
$object_id = rgget( 'object_id' );
|
|
$response = $this->delete_lock_request_meta( $object_id );
|
|
echo json_encode( $response );
|
|
die();
|
|
}
|
|
|
|
protected function has_lock() {
|
|
return $this->check_lock( $this->get_object_id() ) ? true : false;
|
|
}
|
|
|
|
|
|
protected function check_lock( $object_id ) {
|
|
|
|
if ( ! $user_id = $this->get_lock_meta( $object_id ) ) {
|
|
return false;
|
|
}
|
|
|
|
if ( $user_id != get_current_user_id() ) {
|
|
return $user_id;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
protected function check_lock_request( $object_id ) {
|
|
|
|
if ( ! $user_id = $this->get_lock_request_meta( $object_id ) ) {
|
|
return false;
|
|
}
|
|
|
|
if ( $user_id != get_current_user_id() ) {
|
|
return $user_id;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
protected function set_lock( $object_id ) {
|
|
if ( ! GFCommon::current_user_can_any( $this->_capabilities ) ) {
|
|
return false;
|
|
}
|
|
|
|
if ( 0 == ( $user_id = get_current_user_id() ) ) {
|
|
return false;
|
|
}
|
|
|
|
$this->update_lock_meta( $object_id, $user_id );
|
|
|
|
return $user_id;
|
|
}
|
|
|
|
protected function request_lock( $object_id ) {
|
|
if ( 0 == ( $user_id = get_current_user_id() ) ) {
|
|
return false;
|
|
}
|
|
|
|
$lock_holder_user_id = $this->check_lock( $object_id );
|
|
|
|
$result = array();
|
|
if ( ! $lock_holder_user_id ) {
|
|
$this->set_lock( $object_id );
|
|
$result['html'] = __( 'You now have control', 'gravityforms' );
|
|
$result['status'] = 'lock_obtained';
|
|
} else {
|
|
$user = get_userdata( $lock_holder_user_id );
|
|
$this->update_lock_request_meta( $object_id, $user_id );
|
|
$result['html'] = sprintf( __( 'Your request has been sent to %s.', 'gravityforms' ), $user->display_name );
|
|
$result['status'] = 'lock_requested';
|
|
}
|
|
|
|
return $result;
|
|
}
|
|
|
|
protected function get_lock_request_meta( $object_id ) {
|
|
return GFCache::get( self::PREFIX_EDIT_LOCK_REQUEST . $this->_object_type . '_' . $object_id );
|
|
}
|
|
|
|
protected function get_lock_meta( $object_id ) {
|
|
return GFCache::get( self::PREFIX_EDIT_LOCK . $this->_object_type . '_' . $object_id );
|
|
}
|
|
|
|
protected function update_lock_meta( $object_id, $lock_value ) {
|
|
GFCache::set( self::PREFIX_EDIT_LOCK . $this->_object_type . '_' . $object_id, $lock_value, true, 150 );
|
|
}
|
|
|
|
protected function update_lock_request_meta( $object_id, $lock_request_value ) {
|
|
GFCache::set( self::PREFIX_EDIT_LOCK_REQUEST . $this->_object_type . '_' . $object_id, $lock_request_value, true, 120 );
|
|
}
|
|
|
|
protected function delete_lock_request_meta( $object_id ) {
|
|
GFCache::delete( self::PREFIX_EDIT_LOCK_REQUEST . $this->_object_type . '_' . $object_id );
|
|
|
|
return true;
|
|
}
|
|
|
|
protected function delete_lock_meta( $object_id ) {
|
|
GFCache::delete( self::PREFIX_EDIT_LOCK . $this->_object_type . '_' . $object_id );
|
|
|
|
return true;
|
|
}
|
|
|
|
public function maybe_lock_object( $is_edit_page ) {
|
|
if ( isset( $_GET['get-edit-lock'] ) ) {
|
|
$this->set_lock( $this->_object_id );
|
|
wp_safe_redirect( $this->_edit_url );
|
|
exit();
|
|
} else if ( isset( $_GET['release-edit-lock'] ) ) {
|
|
$this->delete_lock_meta( $this->_object_id );
|
|
wp_safe_redirect( $this->_redirect_url );
|
|
exit();
|
|
} else {
|
|
if ( $is_edit_page && ! $user_id = $this->check_lock( $this->_object_id ) ) {
|
|
$this->set_lock( $this->_object_id );
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
public function heartbeat_check_locked_objects( $response, $data, $screen_id ) {
|
|
$checked = array();
|
|
$heartbeat_key = 'gform-check-locked-objects-' . $this->_object_type;
|
|
if ( array_key_exists( $heartbeat_key, $data ) && is_array( $data[ $heartbeat_key ] ) ) {
|
|
foreach ( $data[ $heartbeat_key ] as $object_id ) {
|
|
if ( ( $user_id = $this->check_lock( $object_id ) ) && ( $user = get_userdata( $user_id ) ) ) {
|
|
$send = array( 'text' => sprintf( __( $this->get_string( 'currently_editing' ) ), $user->display_name ) );
|
|
|
|
if ( ( $avatar = get_avatar( $user->ID, 18 ) ) && preg_match( "|src='([^']+)'|", $avatar, $matches ) ) {
|
|
$send['avatar_src'] = $matches[1];
|
|
}
|
|
|
|
$checked[ $object_id ] = $send;
|
|
}
|
|
}
|
|
}
|
|
|
|
if ( ! empty( $checked ) ) {
|
|
$response[ $heartbeat_key ] = $checked;
|
|
}
|
|
|
|
return $response;
|
|
}
|
|
|
|
public function heartbeat_refresh_lock( $response, $data, $screen_id ) {
|
|
$heartbeat_key = 'gform-refresh-lock-' . $this->_object_type;
|
|
if ( array_key_exists( $heartbeat_key, $data ) ) {
|
|
$received = $data[ $heartbeat_key ];
|
|
$send = array();
|
|
|
|
if ( ! isset( $received['objectID'] ) ) {
|
|
return $response;
|
|
}
|
|
|
|
$object_id = $received['objectID'];
|
|
|
|
if ( ( $user_id = $this->check_lock( $object_id ) ) && ( $user = get_userdata( $user_id ) ) ) {
|
|
$error = array(
|
|
'text' => sprintf( __( $this->get_string( 'taken_over' ) ), $user->display_name )
|
|
);
|
|
|
|
if ( $avatar = get_avatar( $user->ID, 64 ) ) {
|
|
if ( preg_match( "|src='([^']+)'|", $avatar, $matches ) ) {
|
|
$error['avatar_src'] = $matches[1];
|
|
}
|
|
}
|
|
|
|
$send['lock_error'] = $error;
|
|
} else {
|
|
if ( $new_lock = $this->set_lock( $object_id ) ) {
|
|
$send['new_lock'] = $new_lock;
|
|
|
|
if ( ( $lock_requester = $this->check_lock_request( $object_id ) ) && ( $user = get_userdata( $lock_requester ) ) ) {
|
|
$lock_request = array(
|
|
'text' => sprintf( __( $this->get_string( 'lock_requested' ) ), $user->display_name )
|
|
);
|
|
|
|
if ( $avatar = get_avatar( $user->ID, 64 ) ) {
|
|
if ( preg_match( "|src='([^']+)'|", $avatar, $matches ) ) {
|
|
$lock_request['avatar_src'] = $matches[1];
|
|
}
|
|
}
|
|
$send['lock_request'] = $lock_request;
|
|
}
|
|
}
|
|
}
|
|
|
|
$response[ $heartbeat_key ] = $send;
|
|
}
|
|
|
|
return $response;
|
|
}
|
|
|
|
public function heartbeat_request_lock( $response, $data, $screen_id ) {
|
|
$heartbeat_key = 'gform-request-lock-' . $this->_object_type;
|
|
if ( array_key_exists( $heartbeat_key, $data ) ) {
|
|
$received = $data[ $heartbeat_key ];
|
|
$send = array();
|
|
|
|
if ( ! isset( $received['objectID'] ) ) {
|
|
return $response;
|
|
}
|
|
|
|
$object_id = $received['objectID'];
|
|
|
|
if ( ( $user_id = $this->check_lock( $object_id ) ) && ( $user = get_userdata( $user_id ) ) ) {
|
|
if ( $this->get_lock_request_meta( $object_id ) ) {
|
|
$send['status'] = 'pending';
|
|
} else {
|
|
$send['status'] = 'deleted';
|
|
}
|
|
} else {
|
|
if ( $new_lock = $this->set_lock( $object_id ) ) {
|
|
$send['status'] = 'granted';
|
|
}
|
|
}
|
|
|
|
$response[ $heartbeat_key ] = $send;
|
|
}
|
|
|
|
return $response;
|
|
}
|
|
|
|
|
|
public function heartbeat_refresh_nonces( $response, $data, $screen_id ) {
|
|
if ( array_key_exists( 'gform-refresh-nonces', $data ) ) {
|
|
$received = $data['gform-refresh-nonces'];
|
|
$response['gform-refresh-nonces'] = array( 'check' => 1 );
|
|
|
|
if ( ! isset( $received['objectID'] ) ) {
|
|
return $response;
|
|
}
|
|
|
|
$object_id = $received['objectID'];
|
|
|
|
if ( ! GFCommon::current_user_can_any( $this->_capabilities ) || empty( $received['post_nonce'] ) ) {
|
|
return $response;
|
|
}
|
|
|
|
if ( 2 === wp_verify_nonce( $received['object_nonce'], 'update-contact_' . $object_id ) ) {
|
|
$response['gform-refresh-nonces'] = array(
|
|
'replace' => array(
|
|
'_wpnonce' => wp_create_nonce( 'update-object_' . $object_id ),
|
|
),
|
|
'heartbeatNonce' => wp_create_nonce( 'heartbeat-nonce' ),
|
|
);
|
|
}
|
|
}
|
|
|
|
return $response;
|
|
}
|
|
|
|
public function get_lock_ui( $user_id ) {
|
|
|
|
$user = get_userdata( $user_id );
|
|
|
|
$locked = $user_id && $user;
|
|
|
|
$edit_url = $this->_edit_url === null ? '' : $this->_edit_url;
|
|
|
|
$hidden = $locked ? '' : ' hidden';
|
|
if ( $locked ) {
|
|
|
|
$message = '<div class="gform-locked-message">
|
|
<div class="gform-locked-avatar">' . get_avatar( $user->ID, 64 ) . '</div>
|
|
<p class="currently-editing" tabindex="0">' . sprintf( $this->get_string( 'currently_locked' ), $user->display_name ) . '</p>
|
|
<p>
|
|
|
|
<a id="gform-take-over-button" style="display:none" class="button button-primary wp-tab-first" href="' . esc_url( add_query_arg( 'get-edit-lock', '1', $edit_url ) ) . '">' . __( 'Take Over', 'gravityforms' ) . '</a>
|
|
<button id="gform-lock-request-button" class="button button-primary wp-tab-last">' . __( 'Request Control', 'gravityforms' ) . '</button>
|
|
<a class="button" href="' . esc_url( $this->_redirect_url ) . '">' . $this->get_string( 'cancel' ) . '</a>
|
|
</p>
|
|
<div id="gform-lock-request-status">
|
|
<!-- placeholder -->
|
|
</div>
|
|
</div>';
|
|
|
|
} else {
|
|
|
|
$message = '<div class="gform-taken-over">
|
|
<div class="gform-locked-avatar"></div>
|
|
<p class="wp-tab-first" tabindex="0">
|
|
<span class="currently-editing"></span><br>
|
|
</p>
|
|
<p>
|
|
<a id="gform-release-lock-button" class="button button-primary wp-tab-last" href="' . esc_url( add_query_arg( 'release-edit-lock', '1', $edit_url ) ) . '">' . $this->get_string( 'accept' ) . '</a>
|
|
<button id="gform-reject-lock-request-button" style="display:none" class="button button-primary wp-tab-last">' . __( 'Reject Request', 'gravityforms' ) . '</button>
|
|
</p>
|
|
</div>';
|
|
|
|
}
|
|
$html = '<div id="gform-lock-dialog" class="notification-dialog-wrap' . $hidden . '">
|
|
<div class="notification-dialog-background"></div>
|
|
<div class="notification-dialog">';
|
|
$html .= $message;
|
|
|
|
$html .= ' </div>
|
|
</div>';
|
|
|
|
return $html;
|
|
}
|
|
|
|
public function get_string( $string_key ) {
|
|
$strings = $this->get_strings();
|
|
|
|
return rgar( $strings, $string_key );
|
|
}
|
|
|
|
// helper functions for the list page
|
|
|
|
public function list_row_class( $object_id, $echo = true ) {
|
|
$locked_class = $this->is_locked( $object_id ) ? 'wp-locked' : '';
|
|
$classes = ' gf-locking ' . $locked_class;
|
|
if ( $echo ) {
|
|
echo $classes;
|
|
}
|
|
|
|
return $classes;
|
|
}
|
|
|
|
public function is_locked( $object_id ) {
|
|
if ( ! $user_id = GFCache::get( self::PREFIX_EDIT_LOCK . $this->_object_type . '_' . $object_id ) ) {
|
|
return false;
|
|
}
|
|
|
|
if ( $user_id != get_current_user_id() ) {
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
public function lock_indicator( $echo = true ) {
|
|
|
|
$lock_indicator = '<div class="locked-indicator"></div>';
|
|
|
|
if ( $echo ) {
|
|
echo $lock_indicator;
|
|
}
|
|
|
|
return $lock_indicator;
|
|
}
|
|
|
|
public function lock_info( $object_id, $echo = true ) {
|
|
$user_id = $this->check_lock( $object_id );
|
|
|
|
if ( ! $user_id ) {
|
|
return '';
|
|
}
|
|
|
|
if ( $user_id && $user = get_userdata( $user_id ) ) {
|
|
$locked_avatar = get_avatar( $user->ID, 18 );
|
|
$locked_text = esc_html( sprintf( $this->get_string( 'currently_editing' ), $user->display_name ) );
|
|
} else {
|
|
$locked_avatar = $locked_text = '';
|
|
}
|
|
|
|
$locked_info = '<div class="locked-info"><span class="locked-avatar">' . $locked_avatar . '</span> <span class="locked-text">' . $locked_text . "</span></div>\n";
|
|
|
|
if ( $echo ) {
|
|
echo $locked_info;
|
|
}
|
|
|
|
return $locked_info;
|
|
}
|
|
|
|
protected function is_page( $page_name ) {
|
|
|
|
return $page_name == GFForms::get_page();
|
|
}
|
|
|
|
}
|