GFLocking

Handles all tasks related to locking.

Defined (1)

The class is defined in the following location(s).

/includes/locking/class-gf-locking.php  
  1. abstract class GFLocking { 
  2. private $_object_type; 
  3. private $_object_id; 
  4. private $_edit_url; 
  5. private $_redirect_url; 
  6. private $_capabilities; 
  7. const PREFIX_EDIT_LOCK = 'lock_'; 
  8. const PREFIX_EDIT_LOCK_REQUEST = 'lock_request_'; 
  9.  
  10.  
  11. public function __construct( $object_type, $redirect_url, $edit_url = '', $capabilities = array() ) { 
  12. $this->_object_type = $object_type; 
  13. $this->_redirect_url = $redirect_url; 
  14. $this->_capabilities = $capabilities; 
  15. if ( defined( 'DOING_AJAX' ) && DOING_AJAX ) { 
  16. $this->init_ajax(); 
  17. } else { 
  18. $this->register_scripts(); 
  19. $is_locking_page = false; 
  20. $is_edit_page = false; 
  21. if ( $this->is_edit_page() ) { 
  22. $this->init_edit_lock(); 
  23. $is_locking_page = true; 
  24. $is_edit_page = true; 
  25. } else if ( $this->is_list_page() ) { 
  26. $this->init_list_page(); 
  27. $is_locking_page = true; 
  28. } else if ( $this->is_view_page() ) { 
  29. $this->init_view_page(); 
  30. $is_locking_page = true; 
  31. if ( $is_locking_page ) { 
  32. $this->_object_id = $this->get_object_id(); 
  33. $this->_edit_url = $edit_url; 
  34. $this->maybe_lock_object( $is_edit_page ); 
  35.  
  36. /** 
  37. * Override this method to check the condition for the edit page. 
  38. * @return bool 
  39. */ 
  40. protected function is_edit_page() { 
  41. return false; 
  42.  
  43. /** 
  44. * Override this method to check the condition for the list page. 
  45. * @return bool 
  46. */ 
  47. protected function is_list_page() { 
  48. return false; 
  49.  
  50. /** 
  51. * Override this method to check the condition for the view page. 
  52. * @return bool 
  53. */ 
  54. protected function is_view_page() { 
  55. return false; 
  56.  
  57. /** 
  58. * Override this method to provide the class with the correct object id. 
  59. * @return bool 
  60. */ 
  61. protected function get_object_id() { 
  62. $id = rgget( 'id' ); 
  63. $id = absint( $id ); 
  64. return $id; // example in the case of form id 
  65.  
  66. public function init_edit_lock() { 
  67. add_action( 'admin_enqueue_scripts', array( $this, 'enqueue_scripts' ) ); 
  68.  
  69. public function init_ajax() { 
  70. add_filter( 'heartbeat_received', array( $this, 'heartbeat_refresh_nonces' ), 10, 3 ); 
  71. add_filter( 'heartbeat_received', array( $this, 'heartbeat_check_locked_objects' ), 10, 3 ); 
  72. add_filter( 'heartbeat_received', array( $this, 'heartbeat_refresh_lock' ), 10, 3 ); 
  73. add_filter( 'heartbeat_received', array( $this, 'heartbeat_request_lock' ), 10, 3 ); 
  74. add_filter( 'wp_ajax_gf_lock_request_' . $this->_object_type, array( $this, 'ajax_lock_request' ) ); 
  75. add_filter( 'wp_ajax_gf_reject_lock_request_' . $this->_object_type, array( $this, 'ajax_reject_lock_request' ) ); 
  76.  
  77. public function init_list_page() { 
  78. add_action( 'admin_enqueue_scripts', array( $this, 'enqueue_list_scripts' ) ); 
  79.  
  80. public function init_view_page() { 
  81. add_action( 'admin_enqueue_scripts', array( $this, 'enqueue_view_page_scripts' ) ); 
  82.  
  83. public function register_scripts() { 
  84. $min = defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG || isset( $_GET['gform_debug'] ) ? '' : '.min'; 
  85. $locking_path = GFCommon::get_base_url() . '/includes/locking/'; 
  86. wp_register_script( 'gforms_locking', $locking_path . "js/locking{$min}.js", array( 'jquery', 'heartbeat' ), GFCommon::$version ); 
  87. wp_register_script( 'gforms_locking_view', $locking_path . "js/locking-view{$min}.js", array( 'jquery', 'heartbeat' ), GFCommon::$version ); 
  88. wp_register_script( 'gforms_locking_list', $locking_path . "js/locking-list{$min}.js", array( 'jquery', 'heartbeat' ), GFCommon::$version ); 
  89. wp_register_style( 'gforms_locking_css', $locking_path . "css/locking{$min}.css", array(), GFCommon::$version ); 
  90. wp_register_style( 'gforms_locking_list_css', $locking_path . "css/locking-list{$min}.css", array(), GFCommon::$version ); 
  91.  
  92. // No conflict scripts 
  93. add_filter( 'gform_noconflict_scripts', array( $this, 'register_noconflict_scripts' ) ); 
  94. add_filter( 'gform_noconflict_styles', array( $this, 'register_noconflict_styles' ) ); 
  95.  
  96. public function register_noconflict_scripts( $scripts ) { 
  97. $locking_scripts = array( 'gforms_locking', 'gforms_locking_list', 'gforms_locking_view' ); 
  98.  
  99. return array_merge( $scripts, $locking_scripts ); 
  100.  
  101. public function register_noconflict_styles( $styles ) { 
  102. $locking_styles = array( 'gforms_locking_css', 'gforms_locking_list_css' ); 
  103.  
  104. return array_merge( $styles, $locking_styles ); 
  105.  
  106. public function enqueue_scripts() { 
  107.  
  108.  
  109. wp_enqueue_script( 'gforms_locking' ); 
  110. wp_enqueue_style( 'gforms_locking_css' ); 
  111. $lock_user_id = $this->check_lock( $this->get_object_id() ); 
  112.  
  113. $strings = array( 
  114. 'noResponse' => $this->get_string( 'no_response' ),  
  115. 'requestAgain' => $this->get_string( 'request_again' ),  
  116. 'requestError' => $this->get_string( 'request_error' ),  
  117. 'gainedControl' => $this->get_string( 'gained_control' ),  
  118. 'rejected' => $this->get_string( 'request_rejected' ),  
  119. 'pending' => $this->get_string( 'request_pending' ) 
  120. ); 
  121.  
  122.  
  123. $vars = array( 
  124. 'hasLock' => ! $lock_user_id ? 1 : 0,  
  125. 'lockUI' => $this->get_lock_ui( $lock_user_id ),  
  126. 'objectID' => $this->_object_id,  
  127. 'objectType' => $this->_object_type,  
  128. 'strings' => $strings,  
  129. ); 
  130.  
  131. wp_localize_script( 'gforms_locking', 'gflockingVars', $vars ); 
  132.  
  133. public function enqueue_list_scripts() { 
  134.  
  135. wp_enqueue_script( 'gforms_locking_list' ); 
  136. wp_enqueue_style( 'gforms_locking_list_css' ); 
  137.  
  138. $vars = array( 
  139. 'objectType' => $this->_object_type,  
  140. ); 
  141.  
  142. wp_localize_script( 'gforms_locking_list', 'gflockingVars', $vars ); 
  143.  
  144.  
  145. public function enqueue_view_page_scripts() { 
  146.  
  147. wp_enqueue_script( 'gforms_locking_view' ); 
  148. wp_enqueue_style( 'gforms_locking_view_css' ); 
  149.  
  150. $lock_user_id = $this->check_lock( $this->get_object_id() ); 
  151. $vars = array( 
  152. 'hasLock' => ! $lock_user_id ? 1 : 0,  
  153. 'objectID' => $this->_object_id,  
  154. 'objectType' => $this->_object_type,  
  155. ); 
  156.  
  157. wp_localize_script( 'gforms_locking_view', 'gflockingVars', $vars ); 
  158.  
  159.  
  160. protected function get_strings() { 
  161. $strings = array( 
  162. 'currently_locked' => __( 'This page is currently locked. Click on the "Request Control" button to let %s know you\'d like to take over.', 'gravityforms' ),  
  163. 'accept' => __( 'Accept', 'gravityforms' ),  
  164. 'cancel' => __( 'Cancel', 'gravityforms' ),  
  165. 'currently_editing' => __( '%s is currently editing', 'gravityforms' ),  
  166. 'taken_over' => __( '%s has taken over and is currently editing.', 'gravityforms' ),  
  167. 'lock_requested' => __( '%s has requested permission to take over control.', 'gravityforms' ),  
  168. 'gained_control' => __( 'You now have control', 'gravityforms' ),  
  169. 'request_pending' => __( 'Pending', 'gravityforms' ),  
  170. 'no_response' => __( 'No response', 'gravityforms' ),  
  171. 'request_again' => __( 'Request again', 'gravityforms' ),  
  172. 'request_error' => __( 'Error', 'gravityforms' ),  
  173. 'request_rejected' => __( 'Your request was rejected', 'gravityforms' ),  
  174. ); 
  175.  
  176. return $strings; 
  177.  
  178. public function ajax_lock_request() { 
  179. $object_id = rgget( 'object_id' ); 
  180. $response = $this->request_lock( $object_id ); 
  181. echo json_encode( $response ); 
  182. die(); 
  183.  
  184. public function ajax_reject_lock_request() { 
  185. $object_id = rgget( 'object_id' ); 
  186. $response = $this->delete_lock_request_meta( $object_id ); 
  187. echo json_encode( $response ); 
  188. die(); 
  189.  
  190. protected function has_lock() { 
  191. return $this->check_lock( $this->get_object_id() ) ? true : false; 
  192.  
  193.  
  194. protected function check_lock( $object_id ) { 
  195.  
  196. if ( ! $user_id = $this->get_lock_meta( $object_id ) ) { 
  197. return false; 
  198.  
  199. if ( $user_id != get_current_user_id() ) { 
  200. return $user_id; 
  201.  
  202. return false; 
  203.  
  204. protected function check_lock_request( $object_id ) { 
  205.  
  206. if ( ! $user_id = $this->get_lock_request_meta( $object_id ) ) { 
  207. return false; 
  208.  
  209. if ( $user_id != get_current_user_id() ) { 
  210. return $user_id; 
  211.  
  212. return false; 
  213.  
  214. protected function set_lock( $object_id ) { 
  215. if ( ! GFCommon::current_user_can_any( $this->_capabilities ) ) { 
  216. return false; 
  217.  
  218. if ( 0 == ( $user_id = get_current_user_id() ) ) { 
  219. return false; 
  220.  
  221. $this->update_lock_meta( $object_id, $user_id ); 
  222.  
  223. return $user_id; 
  224.  
  225. protected function request_lock( $object_id ) { 
  226. if ( 0 == ( $user_id = get_current_user_id() ) ) { 
  227. return false; 
  228.  
  229. $lock_holder_user_id = $this->check_lock( $object_id ); 
  230.  
  231. $result = array(); 
  232. if ( ! $lock_holder_user_id ) { 
  233. $this->set_lock( $object_id ); 
  234. $result['html'] = __( 'You now have control', 'gravityforms' ); 
  235. $result['status'] = 'lock_obtained'; 
  236. } else { 
  237. $user = get_userdata( $lock_holder_user_id ); 
  238. $this->update_lock_request_meta( $object_id, $user_id ); 
  239. $result['html'] = sprintf( __( 'Your request has been sent to %s.', 'gravityforms' ), $user->display_name ); 
  240. $result['status'] = 'lock_requested'; 
  241.  
  242. return $result; 
  243.  
  244. protected function get_lock_request_meta( $object_id ) { 
  245. return GFCache::get( self::PREFIX_EDIT_LOCK_REQUEST . $this->_object_type . '_' . $object_id ); 
  246.  
  247. protected function get_lock_meta( $object_id ) { 
  248. return GFCache::get( self::PREFIX_EDIT_LOCK . $this->_object_type . '_' . $object_id ); 
  249.  
  250. protected function update_lock_meta( $object_id, $lock_value ) { 
  251. GFCache::set( self::PREFIX_EDIT_LOCK . $this->_object_type . '_' . $object_id, $lock_value, true, 150 ); 
  252.  
  253. protected function update_lock_request_meta( $object_id, $lock_request_value ) { 
  254. GFCache::set( self::PREFIX_EDIT_LOCK_REQUEST . $this->_object_type . '_' . $object_id, $lock_request_value, true, 120 ); 
  255.  
  256. protected function delete_lock_request_meta( $object_id ) { 
  257. GFCache::delete( self::PREFIX_EDIT_LOCK_REQUEST . $this->_object_type . '_' . $object_id ); 
  258.  
  259. return true; 
  260.  
  261. protected function delete_lock_meta( $object_id ) { 
  262. GFCache::delete( self::PREFIX_EDIT_LOCK . $this->_object_type . '_' . $object_id ); 
  263.  
  264. return true; 
  265.  
  266. public function maybe_lock_object( $is_edit_page ) { 
  267. if ( isset( $_GET['get-edit-lock'] ) ) { 
  268. $this->set_lock( $this->_object_id ); 
  269. wp_safe_redirect( $this->_edit_url ); 
  270. exit(); 
  271. } else if ( isset( $_GET['release-edit-lock'] ) ) { 
  272. $this->delete_lock_meta( $this->_object_id ); 
  273. wp_safe_redirect( $this->_redirect_url ); 
  274. exit(); 
  275. } else { 
  276. if ( $is_edit_page && ! $user_id = $this->check_lock( $this->_object_id ) ) { 
  277. $this->set_lock( $this->_object_id ); 
  278.  
  279.  
  280. public function heartbeat_check_locked_objects( $response, $data, $screen_id ) { 
  281. $checked = array(); 
  282. $heartbeat_key = 'gform-check-locked-objects-' . $this->_object_type; 
  283. if ( array_key_exists( $heartbeat_key, $data ) && is_array( $data[ $heartbeat_key ] ) ) { 
  284. foreach ( $data[ $heartbeat_key ] as $object_id ) { 
  285. if ( ( $user_id = $this->check_lock( $object_id ) ) && ( $user = get_userdata( $user_id ) ) ) { 
  286. $send = array( 'text' => sprintf( __( $this->get_string( 'currently_editing' ) ), $user->display_name ) ); 
  287.  
  288. if ( ( $avatar = get_avatar( $user->ID, 18 ) ) && preg_match( "|src='([^']+)'|", $avatar, $matches ) ) { 
  289. $send['avatar_src'] = $matches[1]; 
  290.  
  291. $checked[ $object_id ] = $send; 
  292.  
  293. if ( ! empty( $checked ) ) { 
  294. $response[ $heartbeat_key ] = $checked; 
  295.  
  296. return $response; 
  297.  
  298. public function heartbeat_refresh_lock( $response, $data, $screen_id ) { 
  299. $heartbeat_key = 'gform-refresh-lock-' . $this->_object_type; 
  300. if ( array_key_exists( $heartbeat_key, $data ) ) { 
  301. $received = $data[ $heartbeat_key ]; 
  302. $send = array(); 
  303.  
  304. if ( ! isset( $received['objectID'] ) ) { 
  305. return $response; 
  306.  
  307. $object_id = $received['objectID']; 
  308.  
  309. if ( ( $user_id = $this->check_lock( $object_id ) ) && ( $user = get_userdata( $user_id ) ) ) { 
  310. $error = array( 
  311. 'text' => sprintf( __( $this->get_string( 'taken_over' ) ), $user->display_name ) 
  312. ); 
  313.  
  314. if ( $avatar = get_avatar( $user->ID, 64 ) ) { 
  315. if ( preg_match( "|src='([^']+)'|", $avatar, $matches ) ) { 
  316. $error['avatar_src'] = $matches[1]; 
  317.  
  318. $send['lock_error'] = $error; 
  319. } else { 
  320. if ( $new_lock = $this->set_lock( $object_id ) ) { 
  321. $send['new_lock'] = $new_lock; 
  322.  
  323. if ( ( $lock_requester = $this->check_lock_request( $object_id ) ) && ( $user = get_userdata( $lock_requester ) ) ) { 
  324. $lock_request = array( 
  325. 'text' => sprintf( __( $this->get_string( 'lock_requested' ) ), $user->display_name ) 
  326. ); 
  327.  
  328. if ( $avatar = get_avatar( $user->ID, 64 ) ) { 
  329. if ( preg_match( "|src='([^']+)'|", $avatar, $matches ) ) { 
  330. $lock_request['avatar_src'] = $matches[1]; 
  331. $send['lock_request'] = $lock_request; 
  332.  
  333. $response[ $heartbeat_key ] = $send; 
  334.  
  335. return $response; 
  336.  
  337. public function heartbeat_request_lock( $response, $data, $screen_id ) { 
  338. $heartbeat_key = 'gform-request-lock-' . $this->_object_type; 
  339. if ( array_key_exists( $heartbeat_key, $data ) ) { 
  340. $received = $data[ $heartbeat_key ]; 
  341. $send = array(); 
  342.  
  343. if ( ! isset( $received['objectID'] ) ) { 
  344. return $response; 
  345.  
  346. $object_id = $received['objectID']; 
  347.  
  348. if ( ( $user_id = $this->check_lock( $object_id ) ) && ( $user = get_userdata( $user_id ) ) ) { 
  349. if ( $this->get_lock_request_meta( $object_id ) ) { 
  350. $send['status'] = 'pending'; 
  351. } else { 
  352. $send['status'] = 'deleted'; 
  353. } else { 
  354. if ( $new_lock = $this->set_lock( $object_id ) ) { 
  355. $send['status'] = 'granted'; 
  356.  
  357. $response[ $heartbeat_key ] = $send; 
  358.  
  359. return $response; 
  360.  
  361.  
  362. public function heartbeat_refresh_nonces( $response, $data, $screen_id ) { 
  363. if ( array_key_exists( 'gform-refresh-nonces', $data ) ) { 
  364. $received = $data['gform-refresh-nonces']; 
  365. $response['gform-refresh-nonces'] = array( 'check' => 1 ); 
  366.  
  367. if ( ! isset( $received['objectID'] ) ) { 
  368. return $response; 
  369.  
  370. $object_id = $received['objectID']; 
  371.  
  372. if ( ! GFCommon::current_user_can_any( $this->_capabilities ) || empty( $received['post_nonce'] ) ) { 
  373. return $response; 
  374.  
  375. if ( 2 === wp_verify_nonce( $received['object_nonce'], 'update-contact_' . $object_id ) ) { 
  376. $response['gform-refresh-nonces'] = array( 
  377. 'replace' => array( 
  378. '_wpnonce' => wp_create_nonce( 'update-object_' . $object_id ),  
  379. ),  
  380. 'heartbeatNonce' => wp_create_nonce( 'heartbeat-nonce' ),  
  381. ); 
  382.  
  383. return $response; 
  384.  
  385. public function get_lock_ui( $user_id ) { 
  386.  
  387. $user = get_userdata( $user_id ); 
  388.  
  389. $locked = $user_id && $user; 
  390.  
  391. $edit_url = $this->_edit_url; 
  392.  
  393. $hidden = $locked ? '' : ' hidden'; 
  394. if ( $locked ) { 
  395.  
  396. $message = '<div class="gform-locked-message"> 
  397. <div class="gform-locked-avatar">' . get_avatar( $user->ID, 64 ) . '</div> 
  398. <p class="currently-editing" tabindex="0">' . __( sprintf( $this->get_string( 'currently_locked' ), $user->display_name ) ) . '</p> 
  399. <p> 
  400.  
  401. <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> 
  402. <button id="gform-lock-request-button" class="button button-primary wp-tab-last">' . __( 'Request Control', 'gravityforms' ) . '</button> 
  403. <a class="button" href="' . esc_url( $this->_redirect_url ) . '">' . $this->get_string( 'cancel' ) . '</a> 
  404. </p> 
  405. <div id="gform-lock-request-status"> 
  406. <!-- placeholder --> 
  407. </div> 
  408. </div>'; 
  409.  
  410. } else { 
  411.  
  412. $message = '<div class="gform-taken-over"> 
  413. <div class="gform-locked-avatar"></div> 
  414. <p class="wp-tab-first" tabindex="0"> 
  415. <span class="currently-editing"></span><br> 
  416. </p> 
  417. <p> 
  418. <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> 
  419. <button id="gform-reject-lock-request-button" style="display:none" class="button button-primary wp-tab-last">' . __( 'Reject Request', 'gravityforms' ) . '</button> 
  420. </p> 
  421. </div>'; 
  422.  
  423. $html = '<div id="gform-lock-dialog" class="notification-dialog-wrap' . $hidden . '"> 
  424. <div class="notification-dialog-background"></div> 
  425. <div class="notification-dialog">'; 
  426. $html .= $message; 
  427.  
  428. $html .= ' </div> 
  429. </div>'; 
  430.  
  431. return $html; 
  432.  
  433. public function get_string( $string_key ) { 
  434. $strings = $this->get_strings(); 
  435.  
  436. return rgar( $strings, $string_key ); 
  437.  
  438. // helper functions for the list page 
  439.  
  440. public function list_row_class( $object_id, $echo = true ) { 
  441. $locked_class = $this->is_locked( $object_id ) ? 'wp-locked' : ''; 
  442. $classes = ' gf-locking ' . $locked_class; 
  443. if ( $echo ) { 
  444. echo $classes; 
  445.  
  446. return $classes; 
  447.  
  448. public function is_locked( $object_id ) { 
  449. if ( ! $user_id = GFCache::get( self::PREFIX_EDIT_LOCK . $this->_object_type . '_' . $object_id ) ) { 
  450. return false; 
  451.  
  452. if ( $user_id != get_current_user_id() ) { 
  453. return true; 
  454.  
  455. return false; 
  456.  
  457. public function lock_indicator( $echo = true ) { 
  458.  
  459. $lock_indicator = '<div class="locked-indicator"></div>'; 
  460.  
  461. if ( $echo ) { 
  462. echo $lock_indicator; 
  463.  
  464. return $lock_indicator; 
  465.  
  466. public function lock_info( $object_id, $echo = true ) { 
  467. $user_id = $this->check_lock( $object_id ); 
  468.  
  469. if ( ! $user_id ) { 
  470. return ''; 
  471.  
  472. if ( $user_id && $user = get_userdata( $user_id ) ) { 
  473. $locked_avatar = get_avatar( $user->ID, 18 ); 
  474. $locked_text = esc_html( sprintf( $this->get_string( 'currently_editing' ), $user->display_name ) ); 
  475. } else { 
  476. $locked_avatar = $locked_text = ''; 
  477.  
  478. $locked_info = '<div class="locked-info"><span class="locked-avatar">' . $locked_avatar . '</span> <span class="locked-text">' . $locked_text . "</span></div>\n"; 
  479.  
  480. if ( $echo ) { 
  481. echo $locked_info; 
  482.  
  483. return $locked_info; 
  484.  
  485. protected function is_page( $page_name ) { 
  486.  
  487. return $page_name == GFForms::get_page(); 
  488.