user.php 49 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586
  1. <?php
  2. /**
  3. * WordPress user administration API.
  4. *
  5. * @package WordPress
  6. * @subpackage Administration
  7. */
  8. /**
  9. * Creates a new user from the "Users" form using $_POST information.
  10. *
  11. * @since 2.0.0
  12. *
  13. * @return int|WP_Error WP_Error or User ID.
  14. */
  15. function add_user() {
  16. return edit_user();
  17. }
  18. /**
  19. * Edit user settings based on contents of $_POST
  20. *
  21. * Used on user-edit.php and profile.php to manage and process user options, passwords etc.
  22. *
  23. * @since 2.0.0
  24. *
  25. * @param int $user_id Optional. User ID.
  26. * @return int|WP_Error user id of the updated user
  27. */
  28. function edit_user( $user_id = 0 ) {
  29. $wp_roles = wp_roles();
  30. $user = new stdClass;
  31. if ( $user_id ) {
  32. $update = true;
  33. $user->ID = (int) $user_id;
  34. $userdata = get_userdata( $user_id );
  35. $user->user_login = wp_slash( $userdata->user_login );
  36. } else {
  37. $update = false;
  38. }
  39. if ( !$update && isset( $_POST['user_login'] ) )
  40. $user->user_login = sanitize_user($_POST['user_login'], true);
  41. $pass1 = $pass2 = '';
  42. if ( isset( $_POST['pass1'] ) )
  43. $pass1 = $_POST['pass1'];
  44. if ( isset( $_POST['pass2'] ) )
  45. $pass2 = $_POST['pass2'];
  46. if ( isset( $_POST['role'] ) && current_user_can( 'edit_users' ) ) {
  47. $new_role = sanitize_text_field( $_POST['role'] );
  48. $potential_role = isset($wp_roles->role_objects[$new_role]) ? $wp_roles->role_objects[$new_role] : false;
  49. // Don't let anyone with 'edit_users' (admins) edit their own role to something without it.
  50. // Multisite super admins can freely edit their blog roles -- they possess all caps.
  51. if ( ( is_multisite() && current_user_can( 'manage_sites' ) ) || $user_id != get_current_user_id() || ($potential_role && $potential_role->has_cap( 'edit_users' ) ) )
  52. $user->role = $new_role;
  53. // If the new role isn't editable by the logged-in user die with error
  54. $editable_roles = get_editable_roles();
  55. if ( ! empty( $new_role ) && empty( $editable_roles[$new_role] ) )
  56. wp_die( __( 'Sorry, you are not allowed to give users that role.' ), 403 );
  57. }
  58. if ( isset( $_POST['email'] ))
  59. $user->user_email = sanitize_text_field( wp_unslash( $_POST['email'] ) );
  60. if ( isset( $_POST['url'] ) ) {
  61. if ( empty ( $_POST['url'] ) || $_POST['url'] == 'http://' ) {
  62. $user->user_url = '';
  63. } else {
  64. $user->user_url = esc_url_raw( $_POST['url'] );
  65. $protocols = implode( '|', array_map( 'preg_quote', wp_allowed_protocols() ) );
  66. $user->user_url = preg_match('/^(' . $protocols . '):/is', $user->user_url) ? $user->user_url : 'http://'.$user->user_url;
  67. }
  68. }
  69. if ( isset( $_POST['first_name'] ) )
  70. $user->first_name = sanitize_text_field( $_POST['first_name'] );
  71. if ( isset( $_POST['last_name'] ) )
  72. $user->last_name = sanitize_text_field( $_POST['last_name'] );
  73. if ( isset( $_POST['nickname'] ) )
  74. $user->nickname = sanitize_text_field( $_POST['nickname'] );
  75. if ( isset( $_POST['display_name'] ) )
  76. $user->display_name = sanitize_text_field( $_POST['display_name'] );
  77. if ( isset( $_POST['description'] ) )
  78. $user->description = trim( $_POST['description'] );
  79. foreach ( wp_get_user_contact_methods( $user ) as $method => $name ) {
  80. if ( isset( $_POST[$method] ))
  81. $user->$method = sanitize_text_field( $_POST[$method] );
  82. }
  83. if ( $update ) {
  84. $user->rich_editing = isset( $_POST['rich_editing'] ) && 'false' === $_POST['rich_editing'] ? 'false' : 'true';
  85. $user->syntax_highlighting = isset( $_POST['syntax_highlighting'] ) && 'false' === $_POST['syntax_highlighting'] ? 'false' : 'true';
  86. $user->admin_color = isset( $_POST['admin_color'] ) ? sanitize_text_field( $_POST['admin_color'] ) : 'fresh';
  87. $user->show_admin_bar_front = isset( $_POST['admin_bar_front'] ) ? 'true' : 'false';
  88. $user->locale = '';
  89. if ( isset( $_POST['locale'] ) ) {
  90. $locale = sanitize_text_field( $_POST['locale'] );
  91. if ( 'site-default' === $locale ) {
  92. $locale = '';
  93. } elseif ( '' === $locale ) {
  94. $locale = 'en_US';
  95. } elseif ( ! in_array( $locale, get_available_languages(), true ) ) {
  96. $locale = '';
  97. }
  98. $user->locale = $locale;
  99. }
  100. }
  101. $user->comment_shortcuts = isset( $_POST['comment_shortcuts'] ) && 'true' == $_POST['comment_shortcuts'] ? 'true' : '';
  102. $user->use_ssl = 0;
  103. if ( !empty($_POST['use_ssl']) )
  104. $user->use_ssl = 1;
  105. $errors = new WP_Error();
  106. /* checking that username has been typed */
  107. if ( $user->user_login == '' )
  108. $errors->add( 'user_login', __( '<strong>ERROR</strong>: Please enter a username.' ) );
  109. /* checking that nickname has been typed */
  110. if ( $update && empty( $user->nickname ) ) {
  111. $errors->add( 'nickname', __( '<strong>ERROR</strong>: Please enter a nickname.' ) );
  112. }
  113. /**
  114. * Fires before the password and confirm password fields are checked for congruity.
  115. *
  116. * @since 1.5.1
  117. *
  118. * @param string $user_login The username.
  119. * @param string $pass1 The password (passed by reference).
  120. * @param string $pass2 The confirmed password (passed by reference).
  121. */
  122. do_action_ref_array( 'check_passwords', array( $user->user_login, &$pass1, &$pass2 ) );
  123. // Check for blank password when adding a user.
  124. if ( ! $update && empty( $pass1 ) ) {
  125. $errors->add( 'pass', __( '<strong>ERROR</strong>: Please enter a password.' ), array( 'form-field' => 'pass1' ) );
  126. }
  127. // Check for "\" in password.
  128. if ( false !== strpos( wp_unslash( $pass1 ), "\\" ) ) {
  129. $errors->add( 'pass', __( '<strong>ERROR</strong>: Passwords may not contain the character "\\".' ), array( 'form-field' => 'pass1' ) );
  130. }
  131. // Checking the password has been typed twice the same.
  132. if ( ( $update || ! empty( $pass1 ) ) && $pass1 != $pass2 ) {
  133. $errors->add( 'pass', __( '<strong>ERROR</strong>: Please enter the same password in both password fields.' ), array( 'form-field' => 'pass1' ) );
  134. }
  135. if ( !empty( $pass1 ) )
  136. $user->user_pass = $pass1;
  137. if ( !$update && isset( $_POST['user_login'] ) && !validate_username( $_POST['user_login'] ) )
  138. $errors->add( 'user_login', __( '<strong>ERROR</strong>: This username is invalid because it uses illegal characters. Please enter a valid username.' ));
  139. if ( !$update && username_exists( $user->user_login ) )
  140. $errors->add( 'user_login', __( '<strong>ERROR</strong>: This username is already registered. Please choose another one.' ));
  141. /** This filter is documented in wp-includes/user.php */
  142. $illegal_logins = (array) apply_filters( 'illegal_user_logins', array() );
  143. if ( in_array( strtolower( $user->user_login ), array_map( 'strtolower', $illegal_logins ) ) ) {
  144. $errors->add( 'invalid_username', __( '<strong>ERROR</strong>: Sorry, that username is not allowed.' ) );
  145. }
  146. /* checking email address */
  147. if ( empty( $user->user_email ) ) {
  148. $errors->add( 'empty_email', __( '<strong>ERROR</strong>: Please enter an email address.' ), array( 'form-field' => 'email' ) );
  149. } elseif ( !is_email( $user->user_email ) ) {
  150. $errors->add( 'invalid_email', __( '<strong>ERROR</strong>: The email address isn&#8217;t correct.' ), array( 'form-field' => 'email' ) );
  151. } elseif ( ( $owner_id = email_exists($user->user_email) ) && ( !$update || ( $owner_id != $user->ID ) ) ) {
  152. $errors->add( 'email_exists', __('<strong>ERROR</strong>: This email is already registered, please choose another one.'), array( 'form-field' => 'email' ) );
  153. }
  154. /**
  155. * Fires before user profile update errors are returned.
  156. *
  157. * @since 2.8.0
  158. *
  159. * @param WP_Error $errors WP_Error object (passed by reference).
  160. * @param bool $update Whether this is a user update.
  161. * @param stdClass $user User object (passed by reference).
  162. */
  163. do_action_ref_array( 'user_profile_update_errors', array( &$errors, $update, &$user ) );
  164. if ( $errors->get_error_codes() )
  165. return $errors;
  166. if ( $update ) {
  167. $user_id = wp_update_user( $user );
  168. } else {
  169. $user_id = wp_insert_user( $user );
  170. $notify = isset( $_POST['send_user_notification'] ) ? 'both' : 'admin';
  171. /**
  172. * Fires after a new user has been created.
  173. *
  174. * @since 4.4.0
  175. *
  176. * @param int $user_id ID of the newly created user.
  177. * @param string $notify Type of notification that should happen. See wp_send_new_user_notifications()
  178. * for more information on possible values.
  179. */
  180. do_action( 'edit_user_created_user', $user_id, $notify );
  181. }
  182. return $user_id;
  183. }
  184. /**
  185. * Fetch a filtered list of user roles that the current user is
  186. * allowed to edit.
  187. *
  188. * Simple function who's main purpose is to allow filtering of the
  189. * list of roles in the $wp_roles object so that plugins can remove
  190. * inappropriate ones depending on the situation or user making edits.
  191. * Specifically because without filtering anyone with the edit_users
  192. * capability can edit others to be administrators, even if they are
  193. * only editors or authors. This filter allows admins to delegate
  194. * user management.
  195. *
  196. * @since 2.8.0
  197. *
  198. * @return array
  199. */
  200. function get_editable_roles() {
  201. $all_roles = wp_roles()->roles;
  202. /**
  203. * Filters the list of editable roles.
  204. *
  205. * @since 2.8.0
  206. *
  207. * @param array $all_roles List of roles.
  208. */
  209. $editable_roles = apply_filters( 'editable_roles', $all_roles );
  210. return $editable_roles;
  211. }
  212. /**
  213. * Retrieve user data and filter it.
  214. *
  215. * @since 2.0.5
  216. *
  217. * @param int $user_id User ID.
  218. * @return WP_User|bool WP_User object on success, false on failure.
  219. */
  220. function get_user_to_edit( $user_id ) {
  221. $user = get_userdata( $user_id );
  222. if ( $user )
  223. $user->filter = 'edit';
  224. return $user;
  225. }
  226. /**
  227. * Retrieve the user's drafts.
  228. *
  229. * @since 2.0.0
  230. *
  231. * @global wpdb $wpdb WordPress database abstraction object.
  232. *
  233. * @param int $user_id User ID.
  234. * @return array
  235. */
  236. function get_users_drafts( $user_id ) {
  237. global $wpdb;
  238. $query = $wpdb->prepare("SELECT ID, post_title FROM $wpdb->posts WHERE post_type = 'post' AND post_status = 'draft' AND post_author = %d ORDER BY post_modified DESC", $user_id);
  239. /**
  240. * Filters the user's drafts query string.
  241. *
  242. * @since 2.0.0
  243. *
  244. * @param string $query The user's drafts query string.
  245. */
  246. $query = apply_filters( 'get_users_drafts', $query );
  247. return $wpdb->get_results( $query );
  248. }
  249. /**
  250. * Remove user and optionally reassign posts and links to another user.
  251. *
  252. * If the $reassign parameter is not assigned to a User ID, then all posts will
  253. * be deleted of that user. The action {@see 'delete_user'} that is passed the User ID
  254. * being deleted will be run after the posts are either reassigned or deleted.
  255. * The user meta will also be deleted that are for that User ID.
  256. *
  257. * @since 2.0.0
  258. *
  259. * @global wpdb $wpdb WordPress database abstraction object.
  260. *
  261. * @param int $id User ID.
  262. * @param int $reassign Optional. Reassign posts and links to new User ID.
  263. * @return bool True when finished.
  264. */
  265. function wp_delete_user( $id, $reassign = null ) {
  266. global $wpdb;
  267. if ( ! is_numeric( $id ) ) {
  268. return false;
  269. }
  270. $id = (int) $id;
  271. $user = new WP_User( $id );
  272. if ( !$user->exists() )
  273. return false;
  274. // Normalize $reassign to null or a user ID. 'novalue' was an older default.
  275. if ( 'novalue' === $reassign ) {
  276. $reassign = null;
  277. } elseif ( null !== $reassign ) {
  278. $reassign = (int) $reassign;
  279. }
  280. /**
  281. * Fires immediately before a user is deleted from the database.
  282. *
  283. * @since 2.0.0
  284. *
  285. * @param int $id ID of the user to delete.
  286. * @param int|null $reassign ID of the user to reassign posts and links to.
  287. * Default null, for no reassignment.
  288. */
  289. do_action( 'delete_user', $id, $reassign );
  290. if ( null === $reassign ) {
  291. $post_types_to_delete = array();
  292. foreach ( get_post_types( array(), 'objects' ) as $post_type ) {
  293. if ( $post_type->delete_with_user ) {
  294. $post_types_to_delete[] = $post_type->name;
  295. } elseif ( null === $post_type->delete_with_user && post_type_supports( $post_type->name, 'author' ) ) {
  296. $post_types_to_delete[] = $post_type->name;
  297. }
  298. }
  299. /**
  300. * Filters the list of post types to delete with a user.
  301. *
  302. * @since 3.4.0
  303. *
  304. * @param array $post_types_to_delete Post types to delete.
  305. * @param int $id User ID.
  306. */
  307. $post_types_to_delete = apply_filters( 'post_types_to_delete_with_user', $post_types_to_delete, $id );
  308. $post_types_to_delete = implode( "', '", $post_types_to_delete );
  309. $post_ids = $wpdb->get_col( $wpdb->prepare( "SELECT ID FROM $wpdb->posts WHERE post_author = %d AND post_type IN ('$post_types_to_delete')", $id ) );
  310. if ( $post_ids ) {
  311. foreach ( $post_ids as $post_id )
  312. wp_delete_post( $post_id );
  313. }
  314. // Clean links
  315. $link_ids = $wpdb->get_col( $wpdb->prepare("SELECT link_id FROM $wpdb->links WHERE link_owner = %d", $id) );
  316. if ( $link_ids ) {
  317. foreach ( $link_ids as $link_id )
  318. wp_delete_link($link_id);
  319. }
  320. } else {
  321. $post_ids = $wpdb->get_col( $wpdb->prepare( "SELECT ID FROM $wpdb->posts WHERE post_author = %d", $id ) );
  322. $wpdb->update( $wpdb->posts, array('post_author' => $reassign), array('post_author' => $id) );
  323. if ( ! empty( $post_ids ) ) {
  324. foreach ( $post_ids as $post_id )
  325. clean_post_cache( $post_id );
  326. }
  327. $link_ids = $wpdb->get_col( $wpdb->prepare("SELECT link_id FROM $wpdb->links WHERE link_owner = %d", $id) );
  328. $wpdb->update( $wpdb->links, array('link_owner' => $reassign), array('link_owner' => $id) );
  329. if ( ! empty( $link_ids ) ) {
  330. foreach ( $link_ids as $link_id )
  331. clean_bookmark_cache( $link_id );
  332. }
  333. }
  334. // FINALLY, delete user
  335. if ( is_multisite() ) {
  336. remove_user_from_blog( $id, get_current_blog_id() );
  337. } else {
  338. $meta = $wpdb->get_col( $wpdb->prepare( "SELECT umeta_id FROM $wpdb->usermeta WHERE user_id = %d", $id ) );
  339. foreach ( $meta as $mid )
  340. delete_metadata_by_mid( 'user', $mid );
  341. $wpdb->delete( $wpdb->users, array( 'ID' => $id ) );
  342. }
  343. clean_user_cache( $user );
  344. /**
  345. * Fires immediately after a user is deleted from the database.
  346. *
  347. * @since 2.9.0
  348. *
  349. * @param int $id ID of the deleted user.
  350. * @param int|null $reassign ID of the user to reassign posts and links to.
  351. * Default null, for no reassignment.
  352. */
  353. do_action( 'deleted_user', $id, $reassign );
  354. return true;
  355. }
  356. /**
  357. * Remove all capabilities from user.
  358. *
  359. * @since 2.1.0
  360. *
  361. * @param int $id User ID.
  362. */
  363. function wp_revoke_user($id) {
  364. $id = (int) $id;
  365. $user = new WP_User($id);
  366. $user->remove_all_caps();
  367. }
  368. /**
  369. * @since 2.8.0
  370. *
  371. * @global int $user_ID
  372. *
  373. * @param false $errors Deprecated.
  374. */
  375. function default_password_nag_handler($errors = false) {
  376. global $user_ID;
  377. // Short-circuit it.
  378. if ( ! get_user_option('default_password_nag') )
  379. return;
  380. // get_user_setting = JS saved UI setting. else no-js-fallback code.
  381. if ( 'hide' == get_user_setting('default_password_nag') || isset($_GET['default_password_nag']) && '0' == $_GET['default_password_nag'] ) {
  382. delete_user_setting('default_password_nag');
  383. update_user_option($user_ID, 'default_password_nag', false, true);
  384. }
  385. }
  386. /**
  387. * @since 2.8.0
  388. *
  389. * @param int $user_ID
  390. * @param object $old_data
  391. */
  392. function default_password_nag_edit_user($user_ID, $old_data) {
  393. // Short-circuit it.
  394. if ( ! get_user_option('default_password_nag', $user_ID) )
  395. return;
  396. $new_data = get_userdata($user_ID);
  397. // Remove the nag if the password has been changed.
  398. if ( $new_data->user_pass != $old_data->user_pass ) {
  399. delete_user_setting('default_password_nag');
  400. update_user_option($user_ID, 'default_password_nag', false, true);
  401. }
  402. }
  403. /**
  404. * @since 2.8.0
  405. *
  406. * @global string $pagenow
  407. */
  408. function default_password_nag() {
  409. global $pagenow;
  410. // Short-circuit it.
  411. if ( 'profile.php' == $pagenow || ! get_user_option('default_password_nag') )
  412. return;
  413. echo '<div class="error default-password-nag">';
  414. echo '<p>';
  415. echo '<strong>' . __('Notice:') . '</strong> ';
  416. _e('You&rsquo;re using the auto-generated password for your account. Would you like to change it?');
  417. echo '</p><p>';
  418. printf( '<a href="%s">' . __('Yes, take me to my profile page') . '</a> | ', get_edit_profile_url() . '#password' );
  419. printf( '<a href="%s" id="default-password-nag-no">' . __('No thanks, do not remind me again') . '</a>', '?default_password_nag=0' );
  420. echo '</p></div>';
  421. }
  422. /**
  423. * @since 3.5.0
  424. * @access private
  425. */
  426. function delete_users_add_js() { ?>
  427. <script>
  428. jQuery(document).ready( function($) {
  429. var submit = $('#submit').prop('disabled', true);
  430. $('input[name="delete_option"]').one('change', function() {
  431. submit.prop('disabled', false);
  432. });
  433. $('#reassign_user').focus( function() {
  434. $('#delete_option1').prop('checked', true).trigger('change');
  435. });
  436. });
  437. </script>
  438. <?php
  439. }
  440. /**
  441. * Optional SSL preference that can be turned on by hooking to the 'personal_options' action.
  442. *
  443. * See the {@see 'personal_options'} action.
  444. *
  445. * @since 2.7.0
  446. *
  447. * @param object $user User data object
  448. */
  449. function use_ssl_preference($user) {
  450. ?>
  451. <tr class="user-use-ssl-wrap">
  452. <th scope="row"><?php _e('Use https')?></th>
  453. <td><label for="use_ssl"><input name="use_ssl" type="checkbox" id="use_ssl" value="1" <?php checked('1', $user->use_ssl); ?> /> <?php _e('Always use https when visiting the admin'); ?></label></td>
  454. </tr>
  455. <?php
  456. }
  457. /**
  458. *
  459. * @param string $text
  460. * @return string
  461. */
  462. function admin_created_user_email( $text ) {
  463. $roles = get_editable_roles();
  464. $role = $roles[ $_REQUEST['role'] ];
  465. /* translators: 1: Site name, 2: site URL, 3: role */
  466. return sprintf( __( 'Hi,
  467. You\'ve been invited to join \'%1$s\' at
  468. %2$s with the role of %3$s.
  469. If you do not want to join this site please ignore
  470. this email. This invitation will expire in a few days.
  471. Please click the following link to activate your user account:
  472. %%s' ), wp_specialchars_decode( get_bloginfo( 'name' ), ENT_QUOTES ), home_url(), wp_specialchars_decode( translate_user_role( $role['name'] ) ) );
  473. }
  474. /**
  475. * Resend an existing request and return the result.
  476. *
  477. * @since 4.9.6
  478. * @access private
  479. *
  480. * @param int $request_id Request ID.
  481. * @return bool|WP_Error Returns true/false based on the success of sending the email, or a WP_Error object.
  482. */
  483. function _wp_privacy_resend_request( $request_id ) {
  484. $request_id = absint( $request_id );
  485. $request = get_post( $request_id );
  486. if ( ! $request || 'user_request' !== $request->post_type ) {
  487. return new WP_Error( 'privacy_request_error', __( 'Invalid request.' ) );
  488. }
  489. $result = wp_send_user_request( $request_id );
  490. if ( is_wp_error( $result ) ) {
  491. return $result;
  492. } elseif ( ! $result ) {
  493. return new WP_Error( 'privacy_request_error', __( 'Unable to initiate confirmation request.' ) );
  494. }
  495. return true;
  496. }
  497. /**
  498. * Marks a request as completed by the admin and logs the current timestamp.
  499. *
  500. * @since 4.9.6
  501. * @access private
  502. *
  503. * @param int $request_id Request ID.
  504. * @return int|WP_Error $request Request ID on success or WP_Error.
  505. */
  506. function _wp_privacy_completed_request( $request_id ) {
  507. $request_id = absint( $request_id );
  508. $request_data = wp_get_user_request_data( $request_id );
  509. if ( ! $request_data ) {
  510. return new WP_Error( 'privacy_request_error', __( 'Invalid request.' ) );
  511. }
  512. update_post_meta( $request_id, '_wp_user_request_completed_timestamp', time() );
  513. $request = wp_update_post( array(
  514. 'ID' => $request_id,
  515. 'post_status' => 'request-completed',
  516. ) );
  517. return $request;
  518. }
  519. /**
  520. * Handle list table actions.
  521. *
  522. * @since 4.9.6
  523. * @access private
  524. */
  525. function _wp_personal_data_handle_actions() {
  526. if ( isset( $_POST['privacy_action_email_retry'] ) ) {
  527. check_admin_referer( 'bulk-privacy_requests' );
  528. $request_id = absint( current( array_keys( (array) wp_unslash( $_POST['privacy_action_email_retry'] ) ) ) );
  529. $result = _wp_privacy_resend_request( $request_id );
  530. if ( is_wp_error( $result ) ) {
  531. add_settings_error(
  532. 'privacy_action_email_retry',
  533. 'privacy_action_email_retry',
  534. $result->get_error_message(),
  535. 'error'
  536. );
  537. } else {
  538. add_settings_error(
  539. 'privacy_action_email_retry',
  540. 'privacy_action_email_retry',
  541. __( 'Confirmation request sent again successfully.' ),
  542. 'updated'
  543. );
  544. }
  545. } elseif ( isset( $_POST['action'] ) ) {
  546. $action = isset( $_POST['action'] ) ? sanitize_key( wp_unslash( $_POST['action'] ) ) : '';
  547. switch ( $action ) {
  548. case 'add_export_personal_data_request':
  549. case 'add_remove_personal_data_request':
  550. check_admin_referer( 'personal-data-request' );
  551. if ( ! isset( $_POST['type_of_action'], $_POST['username_or_email_for_privacy_request'] ) ) {
  552. add_settings_error(
  553. 'action_type',
  554. 'action_type',
  555. __( 'Invalid action.' ),
  556. 'error'
  557. );
  558. }
  559. $action_type = sanitize_text_field( wp_unslash( $_POST['type_of_action'] ) );
  560. $username_or_email_address = sanitize_text_field( wp_unslash( $_POST['username_or_email_for_privacy_request'] ) );
  561. $email_address = '';
  562. if ( ! in_array( $action_type, _wp_privacy_action_request_types(), true ) ) {
  563. add_settings_error(
  564. 'action_type',
  565. 'action_type',
  566. __( 'Invalid action.' ),
  567. 'error'
  568. );
  569. }
  570. if ( ! is_email( $username_or_email_address ) ) {
  571. $user = get_user_by( 'login', $username_or_email_address );
  572. if ( ! $user instanceof WP_User ) {
  573. add_settings_error(
  574. 'username_or_email_for_privacy_request',
  575. 'username_or_email_for_privacy_request',
  576. __( 'Unable to add this request. A valid email address or username must be supplied.' ),
  577. 'error'
  578. );
  579. } else {
  580. $email_address = $user->user_email;
  581. }
  582. } else {
  583. $email_address = $username_or_email_address;
  584. }
  585. if ( empty( $email_address ) ) {
  586. break;
  587. }
  588. $request_id = wp_create_user_request( $email_address, $action_type );
  589. if ( is_wp_error( $request_id ) ) {
  590. add_settings_error(
  591. 'username_or_email_for_privacy_request',
  592. 'username_or_email_for_privacy_request',
  593. $request_id->get_error_message(),
  594. 'error'
  595. );
  596. break;
  597. } elseif ( ! $request_id ) {
  598. add_settings_error(
  599. 'username_or_email_for_privacy_request',
  600. 'username_or_email_for_privacy_request',
  601. __( 'Unable to initiate confirmation request.' ),
  602. 'error'
  603. );
  604. break;
  605. }
  606. wp_send_user_request( $request_id );
  607. add_settings_error(
  608. 'username_or_email_for_privacy_request',
  609. 'username_or_email_for_privacy_request',
  610. __( 'Confirmation request initiated successfully.' ),
  611. 'updated'
  612. );
  613. break;
  614. }
  615. }
  616. }
  617. /**
  618. * Cleans up failed and expired requests before displaying the list table.
  619. *
  620. * @since 4.9.6
  621. * @access private
  622. */
  623. function _wp_personal_data_cleanup_requests() {
  624. /** This filter is documented in wp-includes/user.php */
  625. $expires = (int) apply_filters( 'user_request_key_expiration', DAY_IN_SECONDS );
  626. $requests_query = new WP_Query( array(
  627. 'post_type' => 'user_request',
  628. 'posts_per_page' => -1,
  629. 'post_status' => 'request-pending',
  630. 'fields' => 'ids',
  631. 'date_query' => array(
  632. array(
  633. 'column' => 'post_modified_gmt',
  634. 'before' => $expires . ' seconds ago',
  635. ),
  636. ),
  637. ) );
  638. $request_ids = $requests_query->posts;
  639. foreach ( $request_ids as $request_id ) {
  640. wp_update_post( array(
  641. 'ID' => $request_id,
  642. 'post_status' => 'request-failed',
  643. 'post_password' => '',
  644. ) );
  645. }
  646. }
  647. /**
  648. * Personal data export.
  649. *
  650. * @since 4.9.6
  651. * @access private
  652. */
  653. function _wp_personal_data_export_page() {
  654. if ( ! current_user_can( 'export_others_personal_data' ) ) {
  655. wp_die( __( 'Sorry, you are not allowed to export personal data on this site.' ) );
  656. }
  657. _wp_personal_data_handle_actions();
  658. _wp_personal_data_cleanup_requests();
  659. // "Borrow" xfn.js for now so we don't have to create new files.
  660. wp_enqueue_script( 'xfn' );
  661. $requests_table = new WP_Privacy_Data_Export_Requests_Table( array(
  662. 'plural' => 'privacy_requests',
  663. 'singular' => 'privacy_request',
  664. ) );
  665. $requests_table->process_bulk_action();
  666. $requests_table->prepare_items();
  667. ?>
  668. <div class="wrap nosubsub">
  669. <h1><?php esc_html_e( 'Export Personal Data' ); ?></h1>
  670. <hr class="wp-header-end" />
  671. <?php settings_errors(); ?>
  672. <form method="post" class="wp-privacy-request-form">
  673. <h2><?php esc_html_e( 'Add Data Export Request' ); ?></h2>
  674. <p><?php esc_html_e( 'An email will be sent to the user at this email address asking them to verify the request.' ); ?></p>
  675. <div class="wp-privacy-request-form-field">
  676. <label for="username_or_email_for_privacy_request"><?php esc_html_e( 'Username or email address' ); ?></label>
  677. <input type="text" required class="regular-text" id="username_or_email_for_privacy_request" name="username_or_email_for_privacy_request" />
  678. <?php submit_button( __( 'Send Request' ), 'secondary', 'submit', false ); ?>
  679. </div>
  680. <?php wp_nonce_field( 'personal-data-request' ); ?>
  681. <input type="hidden" name="action" value="add_export_personal_data_request" />
  682. <input type="hidden" name="type_of_action" value="export_personal_data" />
  683. </form>
  684. <hr />
  685. <?php $requests_table->views(); ?>
  686. <form class="search-form wp-clearfix">
  687. <?php $requests_table->search_box( __( 'Search Requests' ), 'requests' ); ?>
  688. <input type="hidden" name="page" value="export_personal_data" />
  689. <input type="hidden" name="filter-status" value="<?php echo isset( $_REQUEST['filter-status'] ) ? esc_attr( sanitize_text_field( $_REQUEST['filter-status'] ) ) : ''; ?>" />
  690. <input type="hidden" name="orderby" value="<?php echo isset( $_REQUEST['orderby'] ) ? esc_attr( sanitize_text_field( $_REQUEST['orderby'] ) ) : ''; ?>" />
  691. <input type="hidden" name="order" value="<?php echo isset( $_REQUEST['order'] ) ? esc_attr( sanitize_text_field( $_REQUEST['order'] ) ) : ''; ?>" />
  692. </form>
  693. <form method="post">
  694. <?php
  695. $requests_table->display();
  696. $requests_table->embed_scripts();
  697. ?>
  698. </form>
  699. </div>
  700. <?php
  701. }
  702. /**
  703. * Personal data anonymization.
  704. *
  705. * @since 4.9.6
  706. * @access private
  707. */
  708. function _wp_personal_data_removal_page() {
  709. /*
  710. * Require both caps in order to make it explicitly clear that delegating
  711. * erasure from network admins to single-site admins will give them the
  712. * ability to affect global users, rather than being limited to the site
  713. * that they administer.
  714. */
  715. if ( ! current_user_can( 'erase_others_personal_data' ) || ! current_user_can( 'delete_users' ) ) {
  716. wp_die( __( 'Sorry, you are not allowed to erase data on this site.' ) );
  717. }
  718. _wp_personal_data_handle_actions();
  719. _wp_personal_data_cleanup_requests();
  720. // "Borrow" xfn.js for now so we don't have to create new files.
  721. wp_enqueue_script( 'xfn' );
  722. $requests_table = new WP_Privacy_Data_Removal_Requests_Table( array(
  723. 'plural' => 'privacy_requests',
  724. 'singular' => 'privacy_request',
  725. ) );
  726. $requests_table->process_bulk_action();
  727. $requests_table->prepare_items();
  728. ?>
  729. <div class="wrap nosubsub">
  730. <h1><?php esc_html_e( 'Erase Personal Data' ); ?></h1>
  731. <hr class="wp-header-end" />
  732. <?php settings_errors(); ?>
  733. <form method="post" class="wp-privacy-request-form">
  734. <h2><?php esc_html_e( 'Add Data Erasure Request' ); ?></h2>
  735. <p><?php esc_html_e( 'An email will be sent to the user at this email address asking them to verify the request.' ); ?></p>
  736. <div class="wp-privacy-request-form-field">
  737. <label for="username_or_email_for_privacy_request"><?php esc_html_e( 'Username or email address' ); ?></label>
  738. <input type="text" required class="regular-text" id="username_or_email_for_privacy_request" name="username_or_email_for_privacy_request" />
  739. <?php submit_button( __( 'Send Request' ), 'secondary', 'submit', false ); ?>
  740. </div>
  741. <?php wp_nonce_field( 'personal-data-request' ); ?>
  742. <input type="hidden" name="action" value="add_remove_personal_data_request" />
  743. <input type="hidden" name="type_of_action" value="remove_personal_data" />
  744. </form>
  745. <hr />
  746. <?php $requests_table->views(); ?>
  747. <form class="search-form wp-clearfix">
  748. <?php $requests_table->search_box( __( 'Search Requests' ), 'requests' ); ?>
  749. <input type="hidden" name="page" value="remove_personal_data" />
  750. <input type="hidden" name="filter-status" value="<?php echo isset( $_REQUEST['filter-status'] ) ? esc_attr( sanitize_text_field( $_REQUEST['filter-status'] ) ) : ''; ?>" />
  751. <input type="hidden" name="orderby" value="<?php echo isset( $_REQUEST['orderby'] ) ? esc_attr( sanitize_text_field( $_REQUEST['orderby'] ) ) : ''; ?>" />
  752. <input type="hidden" name="order" value="<?php echo isset( $_REQUEST['order'] ) ? esc_attr( sanitize_text_field( $_REQUEST['order'] ) ) : ''; ?>" />
  753. </form>
  754. <form method="post">
  755. <?php
  756. $requests_table->display();
  757. $requests_table->embed_scripts();
  758. ?>
  759. </form>
  760. </div>
  761. <?php
  762. }
  763. /**
  764. * Mark erasure requests as completed after processing is finished.
  765. *
  766. * This intercepts the Ajax responses to personal data eraser page requests, and
  767. * monitors the status of a request. Once all of the processing has finished, the
  768. * request is marked as completed.
  769. *
  770. * @since 4.9.6
  771. *
  772. * @see wp_privacy_personal_data_erasure_page
  773. *
  774. * @param array $response The response from the personal data eraser for
  775. * the given page.
  776. * @param int $eraser_index The index of the personal data eraser. Begins
  777. * at 1.
  778. * @param string $email_address The email address of the user whose personal
  779. * data this is.
  780. * @param int $page The page of personal data for this eraser.
  781. * Begins at 1.
  782. * @param int $request_id The request ID for this personal data erasure.
  783. * @return array The filtered response.
  784. */
  785. function wp_privacy_process_personal_data_erasure_page( $response, $eraser_index, $email_address, $page, $request_id ) {
  786. /*
  787. * If the eraser response is malformed, don't attempt to consume it; let it
  788. * pass through, so that the default Ajax processing will generate a warning
  789. * to the user.
  790. */
  791. if ( ! is_array( $response ) ) {
  792. return $response;
  793. }
  794. if ( ! array_key_exists( 'done', $response ) ) {
  795. return $response;
  796. }
  797. if ( ! array_key_exists( 'items_removed', $response ) ) {
  798. return $response;
  799. }
  800. if ( ! array_key_exists( 'items_retained', $response ) ) {
  801. return $response;
  802. }
  803. if ( ! array_key_exists( 'messages', $response ) ) {
  804. return $response;
  805. }
  806. $request = wp_get_user_request_data( $request_id );
  807. if ( ! $request || 'remove_personal_data' !== $request->action_name ) {
  808. wp_send_json_error( __( 'Invalid request ID when processing eraser data.' ) );
  809. }
  810. /** This filter is documented in wp-admin/includes/ajax-actions.php */
  811. $erasers = apply_filters( 'wp_privacy_personal_data_erasers', array() );
  812. $is_last_eraser = count( $erasers ) === $eraser_index;
  813. $eraser_done = $response['done'];
  814. if ( ! $is_last_eraser || ! $eraser_done ) {
  815. return $response;
  816. }
  817. _wp_privacy_completed_request( $request_id );
  818. /**
  819. * Fires immediately after a personal data erasure request has been marked completed.
  820. *
  821. * @since 4.9.6
  822. *
  823. * @param int $request_id The privacy request post ID associated with this request.
  824. */
  825. do_action( 'wp_privacy_personal_data_erased', $request_id );
  826. return $response;
  827. }
  828. /**
  829. * Add requests pages.
  830. *
  831. * @since 4.9.6
  832. * @access private
  833. */
  834. function _wp_privacy_hook_requests_page() {
  835. add_submenu_page( 'tools.php', __( 'Export Personal Data' ), __( 'Export Personal Data' ), 'export_others_personal_data', 'export_personal_data', '_wp_personal_data_export_page' );
  836. add_submenu_page( 'tools.php', __( 'Erase Personal Data' ), __( 'Erase Personal Data' ), 'erase_others_personal_data', 'remove_personal_data', '_wp_personal_data_removal_page' );
  837. }
  838. /**
  839. * Add options for the privacy requests screens.
  840. *
  841. * @since 4.9.8
  842. * @access private
  843. */
  844. function _wp_privacy_requests_screen_options() {
  845. $args = array(
  846. 'option' => str_replace( 'tools_page_', '', get_current_screen()->id ) . '_requests_per_page',
  847. );
  848. add_screen_option( 'per_page', $args );
  849. }
  850. // TODO: move the following classes in new files.
  851. if ( ! class_exists( 'WP_List_Table' ) ) {
  852. require_once( ABSPATH . 'wp-admin/includes/class-wp-list-table.php' );
  853. }
  854. /**
  855. * WP_Privacy_Requests_Table class.
  856. *
  857. * @since 4.9.6
  858. */
  859. abstract class WP_Privacy_Requests_Table extends WP_List_Table {
  860. /**
  861. * Action name for the requests this table will work with. Classes
  862. * which inherit from WP_Privacy_Requests_Table should define this.
  863. *
  864. * Example: 'export_personal_data'.
  865. *
  866. * @since 4.9.6
  867. *
  868. * @var string $request_type Name of action.
  869. */
  870. protected $request_type = 'INVALID';
  871. /**
  872. * Post type to be used.
  873. *
  874. * @since 4.9.6
  875. *
  876. * @var string $post_type The post type.
  877. */
  878. protected $post_type = 'INVALID';
  879. /**
  880. * Get columns to show in the list table.
  881. *
  882. * @since 4.9.6
  883. *
  884. * @return array Array of columns.
  885. */
  886. public function get_columns() {
  887. $columns = array(
  888. 'cb' => '<input type="checkbox" />',
  889. 'email' => __( 'Requester' ),
  890. 'status' => __( 'Status' ),
  891. 'created_timestamp' => __( 'Requested' ),
  892. 'next_steps' => __( 'Next Steps' ),
  893. );
  894. return $columns;
  895. }
  896. /**
  897. * Get a list of sortable columns.
  898. *
  899. * @since 4.9.6
  900. *
  901. * @return array Default sortable columns.
  902. */
  903. protected function get_sortable_columns() {
  904. return array();
  905. }
  906. /**
  907. * Default primary column.
  908. *
  909. * @since 4.9.6
  910. *
  911. * @return string Default primary column name.
  912. */
  913. protected function get_default_primary_column_name() {
  914. return 'email';
  915. }
  916. /**
  917. * Count number of requests for each status.
  918. *
  919. * @since 4.9.6
  920. *
  921. * @return object Number of posts for each status.
  922. */
  923. protected function get_request_counts() {
  924. global $wpdb;
  925. $cache_key = $this->post_type . '-' . $this->request_type;
  926. $counts = wp_cache_get( $cache_key, 'counts' );
  927. if ( false !== $counts ) {
  928. return $counts;
  929. }
  930. $query = "
  931. SELECT post_status, COUNT( * ) AS num_posts
  932. FROM {$wpdb->posts}
  933. WHERE post_type = %s
  934. AND post_name = %s
  935. GROUP BY post_status";
  936. $results = (array) $wpdb->get_results( $wpdb->prepare( $query, $this->post_type, $this->request_type ), ARRAY_A );
  937. $counts = array_fill_keys( get_post_stati(), 0 );
  938. foreach ( $results as $row ) {
  939. $counts[ $row['post_status'] ] = $row['num_posts'];
  940. }
  941. $counts = (object) $counts;
  942. wp_cache_set( $cache_key, $counts, 'counts' );
  943. return $counts;
  944. }
  945. /**
  946. * Get an associative array ( id => link ) with the list of views available on this table.
  947. *
  948. * @since 4.9.6
  949. *
  950. * @return array Associative array of views in the format of $view_name => $view_markup.
  951. */
  952. protected function get_views() {
  953. $current_status = isset( $_REQUEST['filter-status'] ) ? sanitize_text_field( $_REQUEST['filter-status'] ) : '';
  954. $statuses = _wp_privacy_statuses();
  955. $views = array();
  956. $admin_url = admin_url( 'tools.php?page=' . $this->request_type );
  957. $counts = $this->get_request_counts();
  958. $current_link_attributes = empty( $current_status ) ? ' class="current" aria-current="page"' : '';
  959. $views['all'] = '<a href="' . esc_url( $admin_url ) . "\" $current_link_attributes>" . esc_html__( 'All' ) . ' (' . absint( array_sum( (array) $counts ) ) . ')</a>';
  960. foreach ( $statuses as $status => $label ) {
  961. $current_link_attributes = $status === $current_status ? ' class="current" aria-current="page"' : '';
  962. $views[ $status ] = '<a href="' . esc_url( add_query_arg( 'filter-status', $status, $admin_url ) ) . "\" $current_link_attributes>" . esc_html( $label ) . ' (' . absint( $counts->$status ) . ')</a>';
  963. }
  964. return $views;
  965. }
  966. /**
  967. * Get bulk actions.
  968. *
  969. * @since 4.9.6
  970. *
  971. * @return array List of bulk actions.
  972. */
  973. protected function get_bulk_actions() {
  974. return array(
  975. 'delete' => __( 'Remove' ),
  976. 'resend' => __( 'Resend email' ),
  977. );
  978. }
  979. /**
  980. * Process bulk actions.
  981. *
  982. * @since 4.9.6
  983. */
  984. public function process_bulk_action() {
  985. $action = $this->current_action();
  986. $request_ids = isset( $_REQUEST['request_id'] ) ? wp_parse_id_list( wp_unslash( $_REQUEST['request_id'] ) ) : array();
  987. $count = 0;
  988. if ( $request_ids ) {
  989. check_admin_referer( 'bulk-privacy_requests' );
  990. }
  991. switch ( $action ) {
  992. case 'delete':
  993. foreach ( $request_ids as $request_id ) {
  994. if ( wp_delete_post( $request_id, true ) ) {
  995. $count ++;
  996. }
  997. }
  998. add_settings_error(
  999. 'bulk_action',
  1000. 'bulk_action',
  1001. /* translators: %d: number of requests */
  1002. sprintf( _n( 'Deleted %d request', 'Deleted %d requests', $count ), $count ),
  1003. 'updated'
  1004. );
  1005. break;
  1006. case 'resend':
  1007. foreach ( $request_ids as $request_id ) {
  1008. $resend = _wp_privacy_resend_request( $request_id );
  1009. if ( $resend && ! is_wp_error( $resend ) ) {
  1010. $count++;
  1011. }
  1012. }
  1013. add_settings_error(
  1014. 'bulk_action',
  1015. 'bulk_action',
  1016. /* translators: %d: number of requests */
  1017. sprintf( _n( 'Re-sent %d request', 'Re-sent %d requests', $count ), $count ),
  1018. 'updated'
  1019. );
  1020. break;
  1021. }
  1022. }
  1023. /**
  1024. * Prepare items to output.
  1025. *
  1026. * @since 4.9.6
  1027. */
  1028. public function prepare_items() {
  1029. global $wpdb;
  1030. $primary = $this->get_primary_column_name();
  1031. $this->_column_headers = array(
  1032. $this->get_columns(),
  1033. array(),
  1034. $this->get_sortable_columns(),
  1035. $primary,
  1036. );
  1037. $this->items = array();
  1038. $posts_per_page = $this->get_items_per_page( $this->request_type . '_requests_per_page' );
  1039. $args = array(
  1040. 'post_type' => $this->post_type,
  1041. 'post_name__in' => array( $this->request_type ),
  1042. 'posts_per_page' => $posts_per_page,
  1043. 'offset' => isset( $_REQUEST['paged'] ) ? max( 0, absint( $_REQUEST['paged'] ) - 1 ) * $posts_per_page : 0,
  1044. 'post_status' => 'any',
  1045. 's' => isset( $_REQUEST['s'] ) ? sanitize_text_field( $_REQUEST['s'] ) : '',
  1046. );
  1047. if ( ! empty( $_REQUEST['filter-status'] ) ) {
  1048. $filter_status = isset( $_REQUEST['filter-status'] ) ? sanitize_text_field( $_REQUEST['filter-status'] ) : '';
  1049. $args['post_status'] = $filter_status;
  1050. }
  1051. $requests_query = new WP_Query( $args );
  1052. $requests = $requests_query->posts;
  1053. foreach ( $requests as $request ) {
  1054. $this->items[] = wp_get_user_request_data( $request->ID );
  1055. }
  1056. $this->items = array_filter( $this->items );
  1057. $this->set_pagination_args(
  1058. array(
  1059. 'total_items' => $requests_query->found_posts,
  1060. 'per_page' => $posts_per_page,
  1061. )
  1062. );
  1063. }
  1064. /**
  1065. * Checkbox column.
  1066. *
  1067. * @since 4.9.6
  1068. *
  1069. * @param WP_User_Request $item Item being shown.
  1070. * @return string Checkbox column markup.
  1071. */
  1072. public function column_cb( $item ) {
  1073. return sprintf( '<input type="checkbox" name="request_id[]" value="%1$s" /><span class="spinner"></span>', esc_attr( $item->ID ) );
  1074. }
  1075. /**
  1076. * Status column.
  1077. *
  1078. * @since 4.9.6
  1079. *
  1080. * @param WP_User_Request $item Item being shown.
  1081. * @return string Status column markup.
  1082. */
  1083. public function column_status( $item ) {
  1084. $status = get_post_status( $item->ID );
  1085. $status_object = get_post_status_object( $status );
  1086. if ( ! $status_object || empty( $status_object->label ) ) {
  1087. return '-';
  1088. }
  1089. $timestamp = false;
  1090. switch ( $status ) {
  1091. case 'request-confirmed':
  1092. $timestamp = $item->confirmed_timestamp;
  1093. break;
  1094. case 'request-completed':
  1095. $timestamp = $item->completed_timestamp;
  1096. break;
  1097. }
  1098. echo '<span class="status-label status-' . esc_attr( $status ) . '">';
  1099. echo esc_html( $status_object->label );
  1100. if ( $timestamp ) {
  1101. echo ' (' . $this->get_timestamp_as_date( $timestamp ) . ')';
  1102. }
  1103. echo '</span>';
  1104. }
  1105. /**
  1106. * Convert timestamp for display.
  1107. *
  1108. * @since 4.9.6
  1109. *
  1110. * @param int $timestamp Event timestamp.
  1111. * @return string Human readable date.
  1112. */
  1113. protected function get_timestamp_as_date( $timestamp ) {
  1114. if ( empty( $timestamp ) ) {
  1115. return '';
  1116. }
  1117. $time_diff = current_time( 'timestamp', true ) - $timestamp;
  1118. if ( $time_diff >= 0 && $time_diff < DAY_IN_SECONDS ) {
  1119. /* translators: human readable timestamp */
  1120. return sprintf( __( '%s ago' ), human_time_diff( $timestamp ) );
  1121. }
  1122. return date_i18n( get_option( 'date_format' ), $timestamp );
  1123. }
  1124. /**
  1125. * Default column handler.
  1126. *
  1127. * @since 4.9.6
  1128. *
  1129. * @param WP_User_Request $item Item being shown.
  1130. * @param string $column_name Name of column being shown.
  1131. * @return string Default column output.
  1132. */
  1133. public function column_default( $item, $column_name ) {
  1134. $cell_value = $item->$column_name;
  1135. if ( in_array( $column_name, array( 'created_timestamp' ), true ) ) {
  1136. return $this->get_timestamp_as_date( $cell_value );
  1137. }
  1138. return $cell_value;
  1139. }
  1140. /**
  1141. * Actions column. Overridden by children.
  1142. *
  1143. * @since 4.9.6
  1144. *
  1145. * @param WP_User_Request $item Item being shown.
  1146. * @return string Email column markup.
  1147. */
  1148. public function column_email( $item ) {
  1149. return sprintf( '<a href="%1$s">%2$s</a> %3$s', esc_url( 'mailto:' . $item->email ), $item->email, $this->row_actions( array() ) );
  1150. }
  1151. /**
  1152. * Next steps column. Overridden by children.
  1153. *
  1154. * @since 4.9.6
  1155. *
  1156. * @param WP_User_Request $item Item being shown.
  1157. */
  1158. public function column_next_steps( $item ) {}
  1159. /**
  1160. * Generates content for a single row of the table,
  1161. *
  1162. * @since 4.9.6
  1163. *
  1164. * @param WP_User_Request $item The current item.
  1165. */
  1166. public function single_row( $item ) {
  1167. $status = $item->status;
  1168. echo '<tr id="request-' . esc_attr( $item->ID ) . '" class="status-' . esc_attr( $status ) . '">';
  1169. $this->single_row_columns( $item );
  1170. echo '</tr>';
  1171. }
  1172. /**
  1173. * Embed scripts used to perform actions. Overridden by children.
  1174. *
  1175. * @since 4.9.6
  1176. */
  1177. public function embed_scripts() {}
  1178. }
  1179. /**
  1180. * WP_Privacy_Data_Export_Requests_Table class.
  1181. *
  1182. * @since 4.9.6
  1183. */
  1184. class WP_Privacy_Data_Export_Requests_Table extends WP_Privacy_Requests_Table {
  1185. /**
  1186. * Action name for the requests this table will work with.
  1187. *
  1188. * @since 4.9.6
  1189. *
  1190. * @var string $request_type Name of action.
  1191. */
  1192. protected $request_type = 'export_personal_data';
  1193. /**
  1194. * Post type for the requests.
  1195. *
  1196. * @since 4.9.6
  1197. *
  1198. * @var string $post_type The post type.
  1199. */
  1200. protected $post_type = 'user_request';
  1201. /**
  1202. * Actions column.
  1203. *
  1204. * @since 4.9.6
  1205. *
  1206. * @param WP_User_Request $item Item being shown.
  1207. * @return string Email column markup.
  1208. */
  1209. public function column_email( $item ) {
  1210. /** This filter is documented in wp-admin/includes/ajax-actions.php */
  1211. $exporters = apply_filters( 'wp_privacy_personal_data_exporters', array() );
  1212. $exporters_count = count( $exporters );
  1213. $request_id = $item->ID;
  1214. $nonce = wp_create_nonce( 'wp-privacy-export-personal-data-' . $request_id );
  1215. $download_data_markup = '<div class="export-personal-data" ' .
  1216. 'data-exporters-count="' . esc_attr( $exporters_count ) . '" ' .
  1217. 'data-request-id="' . esc_attr( $request_id ) . '" ' .
  1218. 'data-nonce="' . esc_attr( $nonce ) .
  1219. '">';
  1220. $download_data_markup .= '<span class="export-personal-data-idle"><button type="button" class="button-link export-personal-data-handle">' . __( 'Download Personal Data' ) . '</button></span>' .
  1221. '<span style="display:none" class="export-personal-data-processing" >' . __( 'Downloading Data...' ) . '</span>' .
  1222. '<span style="display:none" class="export-personal-data-success"><button type="button" class="button-link export-personal-data-handle">' . __( 'Download Personal Data Again' ) . '</button></span>' .
  1223. '<span style="display:none" class="export-personal-data-failed">' . __( 'Download has failed.' ) . ' <button type="button" class="button-link">' . __( 'Retry' ) . '</button></span>';
  1224. $download_data_markup .= '</div>';
  1225. $row_actions = array(
  1226. 'download-data' => $download_data_markup,
  1227. );
  1228. return sprintf( '<a href="%1$s">%2$s</a> %3$s', esc_url( 'mailto:' . $item->email ), $item->email, $this->row_actions( $row_actions ) );
  1229. }
  1230. /**
  1231. * Displays the next steps column.
  1232. *
  1233. * @since 4.9.6
  1234. *
  1235. * @param WP_User_Request $item Item being shown.
  1236. */
  1237. public function column_next_steps( $item ) {
  1238. $status = $item->status;
  1239. switch ( $status ) {
  1240. case 'request-pending':
  1241. esc_html_e( 'Waiting for confirmation' );
  1242. break;
  1243. case 'request-confirmed':
  1244. /** This filter is documented in wp-admin/includes/ajax-actions.php */
  1245. $exporters = apply_filters( 'wp_privacy_personal_data_exporters', array() );
  1246. $exporters_count = count( $exporters );
  1247. $request_id = $item->ID;
  1248. $nonce = wp_create_nonce( 'wp-privacy-export-personal-data-' . $request_id );
  1249. echo '<div class="export-personal-data" ' .
  1250. 'data-send-as-email="1" ' .
  1251. 'data-exporters-count="' . esc_attr( $exporters_count ) . '" ' .
  1252. 'data-request-id="' . esc_attr( $request_id ) . '" ' .
  1253. 'data-nonce="' . esc_attr( $nonce ) .
  1254. '">';
  1255. ?>
  1256. <span class="export-personal-data-idle"><button type="button" class="button export-personal-data-handle"><?php _e( 'Email Data' ); ?></button></span>
  1257. <span style="display:none" class="export-personal-data-processing button updating-message" ><?php _e( 'Sending Email...' ); ?></span>
  1258. <span style="display:none" class="export-personal-data-success success-message" ><?php _e( 'Email sent.' ); ?></span>
  1259. <span style="display:none" class="export-personal-data-failed"><?php _e( 'Email could not be sent.' ); ?> <button type="button" class="button export-personal-data-handle"><?php _e( 'Retry' ); ?></button></span>
  1260. <?php
  1261. echo '</div>';
  1262. break;
  1263. case 'request-failed':
  1264. submit_button( __( 'Retry' ), 'secondary', 'privacy_action_email_retry[' . $item->ID . ']', false );
  1265. break;
  1266. case 'request-completed':
  1267. echo '<a href="' . esc_url( wp_nonce_url( add_query_arg( array(
  1268. 'action' => 'delete',
  1269. 'request_id' => array( $item->ID ),
  1270. ), admin_url( 'tools.php?page=export_personal_data' ) ), 'bulk-privacy_requests' ) ) . '" class="button">' . esc_html__( 'Remove request' ) . '</a>';
  1271. break;
  1272. }
  1273. }
  1274. }
  1275. /**
  1276. * WP_Privacy_Data_Removal_Requests_Table class.
  1277. *
  1278. * @since 4.9.6
  1279. */
  1280. class WP_Privacy_Data_Removal_Requests_Table extends WP_Privacy_Requests_Table {
  1281. /**
  1282. * Action name for the requests this table will work with.
  1283. *
  1284. * @since 4.9.6
  1285. *
  1286. * @var string $request_type Name of action.
  1287. */
  1288. protected $request_type = 'remove_personal_data';
  1289. /**
  1290. * Post type for the requests.
  1291. *
  1292. * @since 4.9.6
  1293. *
  1294. * @var string $post_type The post type.
  1295. */
  1296. protected $post_type = 'user_request';
  1297. /**
  1298. * Actions column.
  1299. *
  1300. * @since 4.9.6
  1301. *
  1302. * @param WP_User_Request $item Item being shown.
  1303. * @return string Email column markup.
  1304. */
  1305. public function column_email( $item ) {
  1306. $row_actions = array();
  1307. // Allow the administrator to "force remove" the personal data even if confirmation has not yet been received.
  1308. $status = $item->status;
  1309. if ( 'request-confirmed' !== $status ) {
  1310. /** This filter is documented in wp-admin/includes/ajax-actions.php */
  1311. $erasers = apply_filters( 'wp_privacy_personal_data_erasers', array() );
  1312. $erasers_count = count( $erasers );
  1313. $request_id = $item->ID;
  1314. $nonce = wp_create_nonce( 'wp-privacy-erase-personal-data-' . $request_id );
  1315. $remove_data_markup = '<div class="remove-personal-data force-remove-personal-data" ' .
  1316. 'data-erasers-count="' . esc_attr( $erasers_count ) . '" ' .
  1317. 'data-request-id="' . esc_attr( $request_id ) . '" ' .
  1318. 'data-nonce="' . esc_attr( $nonce ) .
  1319. '">';
  1320. $remove_data_markup .= '<span class="remove-personal-data-idle"><button type="button" class="button-link remove-personal-data-handle">' . __( 'Force Erase Personal Data' ) . '</button></span>' .
  1321. '<span style="display:none" class="remove-personal-data-processing" >' . __( 'Erasing Data...' ) . '</span>' .
  1322. '<span style="display:none" class="remove-personal-data-failed">' . __( 'Force Erase has failed.' ) . ' <button type="button" class="button-link remove-personal-data-handle">' . __( 'Retry' ) . '</button></span>';
  1323. $remove_data_markup .= '</div>';
  1324. $row_actions = array(
  1325. 'remove-data' => $remove_data_markup,
  1326. );
  1327. }
  1328. return sprintf( '<a href="%1$s">%2$s</a> %3$s', esc_url( 'mailto:' . $item->email ), $item->email, $this->row_actions( $row_actions ) );
  1329. }
  1330. /**
  1331. * Next steps column.
  1332. *
  1333. * @since 4.9.6
  1334. *
  1335. * @param WP_User_Request $item Item being shown.
  1336. */
  1337. public function column_next_steps( $item ) {
  1338. $status = $item->status;
  1339. switch ( $status ) {
  1340. case 'request-pending':
  1341. esc_html_e( 'Waiting for confirmation' );
  1342. break;
  1343. case 'request-confirmed':
  1344. /** This filter is documented in wp-admin/includes/ajax-actions.php */
  1345. $erasers = apply_filters( 'wp_privacy_personal_data_erasers', array() );
  1346. $erasers_count = count( $erasers );
  1347. $request_id = $item->ID;
  1348. $nonce = wp_create_nonce( 'wp-privacy-erase-personal-data-' . $request_id );
  1349. echo '<div class="remove-personal-data" ' .
  1350. 'data-force-erase="1" ' .
  1351. 'data-erasers-count="' . esc_attr( $erasers_count ) . '" ' .
  1352. 'data-request-id="' . esc_attr( $request_id ) . '" ' .
  1353. 'data-nonce="' . esc_attr( $nonce ) .
  1354. '">';
  1355. ?>
  1356. <span class="remove-personal-data-idle"><button type="button" class="button remove-personal-data-handle"><?php _e( 'Erase Personal Data' ); ?></button></span>
  1357. <span style="display:none" class="remove-personal-data-processing button updating-message" ><?php _e( 'Erasing Data...' ); ?></span>
  1358. <span style="display:none" class="remove-personal-data-failed"><?php _e( 'Erasing Data has failed.' ); ?> <button type="button" class="button remove-personal-data-handle"><?php _e( 'Retry' ); ?></button></span>
  1359. <?php
  1360. echo '</div>';
  1361. break;
  1362. case 'request-failed':
  1363. submit_button( __( 'Retry' ), 'secondary', 'privacy_action_email_retry[' . $item->ID . ']', false );
  1364. break;
  1365. case 'request-completed':
  1366. echo '<a href="' . esc_url( wp_nonce_url( add_query_arg( array(
  1367. 'action' => 'delete',
  1368. 'request_id' => array( $item->ID ),
  1369. ), admin_url( 'tools.php?page=remove_personal_data' ) ), 'bulk-privacy_requests' ) ) . '" class="button">' . esc_html__( 'Remove request' ) . '</a>';
  1370. break;
  1371. }
  1372. }
  1373. }