<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN"
"http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head><meta http-equiv="content-type" content="text/html; charset=utf-8" />
<title>[43101] branches/4.9/src: Privacy: add a postbox that is shown when editing the privacy policy page, and where plugins and core will output suggested content and additional privacy info.</title>
</head>
<body>

<style type="text/css"><!--
#msg dl.meta { border: 1px #006 solid; background: #369; padding: 6px; color: #fff; }
#msg dl.meta dt { float: left; width: 6em; font-weight: bold; }
#msg dt:after { content:':';}
#msg dl, #msg dt, #msg ul, #msg li, #header, #footer, #logmsg { font-family: verdana,arial,helvetica,sans-serif; font-size: 10pt;  }
#msg dl a { font-weight: bold}
#msg dl a:link    { color:#fc3; }
#msg dl a:active  { color:#ff0; }
#msg dl a:visited { color:#cc6; }
h3 { font-family: verdana,arial,helvetica,sans-serif; font-size: 10pt; font-weight: bold; }
#msg pre { white-space: pre-line; overflow: auto; background: #ffc; border: 1px #fa0 solid; padding: 6px; }
#logmsg { background: #ffc; border: 1px #fa0 solid; padding: 1em 1em 0 1em; }
#logmsg p, #logmsg pre, #logmsg blockquote { margin: 0 0 1em 0; }
#logmsg p, #logmsg li, #logmsg dt, #logmsg dd { line-height: 14pt; }
#logmsg h1, #logmsg h2, #logmsg h3, #logmsg h4, #logmsg h5, #logmsg h6 { margin: .5em 0; }
#logmsg h1:first-child, #logmsg h2:first-child, #logmsg h3:first-child, #logmsg h4:first-child, #logmsg h5:first-child, #logmsg h6:first-child { margin-top: 0; }
#logmsg ul, #logmsg ol { padding: 0; list-style-position: inside; margin: 0 0 0 1em; }
#logmsg ul { text-indent: -1em; padding-left: 1em; }#logmsg ol { text-indent: -1.5em; padding-left: 1.5em; }
#logmsg > ul, #logmsg > ol { margin: 0 0 1em 0; }
#logmsg pre { background: #eee; padding: 1em; }
#logmsg blockquote { border: 1px solid #fa0; border-left-width: 10px; padding: 1em 1em 0 1em; background: white;}
#logmsg dl { margin: 0; }
#logmsg dt { font-weight: bold; }
#logmsg dd { margin: 0; padding: 0 0 0.5em 0; }
#logmsg dd:before { content:'\00bb';}
#logmsg table { border-spacing: 0px; border-collapse: collapse; border-top: 4px solid #fa0; border-bottom: 1px solid #fa0; background: #fff; }
#logmsg table th { text-align: left; font-weight: normal; padding: 0.2em 0.5em; border-top: 1px dotted #fa0; }
#logmsg table td { text-align: right; border-top: 1px dotted #fa0; padding: 0.2em 0.5em; }
#logmsg table thead th { text-align: center; border-bottom: 1px solid #fa0; }
#logmsg table th.Corner { text-align: left; }
#logmsg hr { border: none 0; border-top: 2px dashed #fa0; height: 1px; }
#header, #footer { color: #fff; background: #636; border: 1px #300 solid; padding: 6px; }
#patch { width: 100%; }
#patch h4 {font-family: verdana,arial,helvetica,sans-serif;font-size:10pt;padding:8px;background:#369;color:#fff;margin:0;}
#patch .propset h4, #patch .binary h4 {margin:0;}
#patch pre {padding:0;line-height:1.2em;margin:0;}
#patch .diff {width:100%;background:#eee;padding: 0 0 10px 0;overflow:auto;}
#patch .propset .diff, #patch .binary .diff  {padding:10px 0;}
#patch span {display:block;padding:0 10px;}
#patch .modfile, #patch .addfile, #patch .delfile, #patch .propset, #patch .binary, #patch .copfile {border:1px solid #ccc;margin:10px 0;}
#patch ins {background:#dfd;text-decoration:none;display:block;padding:0 10px;}
#patch del {background:#fdd;text-decoration:none;display:block;padding:0 10px;}
#patch .lines, .info {color:#888;background:#fff;}
--></style>
<div id="msg">
<dl class="meta" style="font-size: 105%">
<dt style="float: left; width: 6em; font-weight: bold">Revision</dt> <dd><a style="font-weight: bold" href="https://core.trac.wordpress.org/changeset/43101">43101</a><script type="application/ld+json">{"@context":"http://schema.org","@type":"EmailMessage","description":"Review this Commit","action":{"@type":"ViewAction","url":"https://core.trac.wordpress.org/changeset/43101","name":"Review Commit"}}</script></dd>
<dt style="float: left; width: 6em; font-weight: bold">Author</dt> <dd>SergeyBiryukov</dd>
<dt style="float: left; width: 6em; font-weight: bold">Date</dt> <dd>2018-05-02 02:58:12 +0000 (Wed, 02 May 2018)</dd>
</dl>

<pre style='padding-left: 1em; margin: 2em 0; border-left: 2px solid #ccc; line-height: 1.25; font-size: 105%; font-family: sans-serif'>Privacy: add a postbox that is shown when editing the privacy policy page, and where plugins and core will output suggested content and additional privacy info. First run.

Props melchoyce, azaozz.
Merges <a href="https://core.trac.wordpress.org/changeset/42980">[42980]</a> to the 4.9 branch.
See <a href="https://core.trac.wordpress.org/ticket/43620">#43620</a>.</pre>

<h3>Modified Paths</h3>
<ul>
<li><a href="#branches49srcwpadmincsscommoncss">branches/4.9/src/wp-admin/css/common.css</a></li>
<li><a href="#branches49srcwpadmincsseditcss">branches/4.9/src/wp-admin/css/edit.css</a></li>
<li><a href="#branches49srcwpadminincludesadminfiltersphp">branches/4.9/src/wp-admin/includes/admin-filters.php</a></li>
<li><a href="#branches49srcwpadminincludesmiscphp">branches/4.9/src/wp-admin/includes/misc.php</a></li>
<li><a href="#branches49srcwpadminincludespluginphp">branches/4.9/src/wp-admin/includes/plugin.php</a></li>
<li><a href="#branches49srcwpadminjspostjs">branches/4.9/src/wp-admin/js/post.js</a></li>
<li><a href="#branches49srcwpincludesdefaultfiltersphp">branches/4.9/src/wp-includes/default-filters.php</a></li>
<li><a href="#branches49srcwpincludesfunctionsphp">branches/4.9/src/wp-includes/functions.php</a></li>
<li><a href="#branches49srcwpincludeslinktemplatephp">branches/4.9/src/wp-includes/link-template.php</a></li>
</ul>

<h3>Property Changed</h3>
<ul>
<li><a href="#branches49">branches/4.9/</a></li>
</ul>

</div>
<div id="patch">
<h3>Diff</h3>
<span class="cx" style="display: block; padding: 0 10px">Index: branches/4.9
</span><span class="cx" style="display: block; padding: 0 10px">===================================================================
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">--- branches/4.9 2018-05-02 02:49:55 UTC (rev 43100)
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+++ branches/4.9  2018-05-02 02:58:12 UTC (rev 43101)
</ins><a id="branches49"></a>
<div class="propset"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Property changes: branches/4.9</h4>
<pre class="diff"><span>
</span></pre></div>
<a id="svnmergeinfo"></a>
<div class="modfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Modified: svn:mergeinfo</h4></div>
<span class="cx" style="display: block; padding: 0 10px"> /branches/3.1:18031
</span><span class="cx" style="display: block; padding: 0 10px"> /branches/3.3:20543
</span><span class="cx" style="display: block; padding: 0 10px"> /branches/3.4:21757
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-/trunk:18512,42132,42134,42136,42138,42140,42144,42146,42148,42150,42152-42153,42155,42157,42159,42161,42163,42169,42171,42173,42175,42177,42181,42183,42185,42187,42189,42191,42193,42199,42203,42210,42214,42220,42222,42226,42242,42244,42247,42251,42256-42261,42350,42358,42362,42364-42368,42374,42388,42390,42401,42417,42421,42423-42425,42430,42433,42437,42441,42443,42446-42447,42449,42451,42453,42457,42459,42462,42491,42521,42529,42531,42533,42536,42538,42541,42543,42545,42549,42566,42568,42570,42572,42574,42576,42579,42581-42582,42584-42585,42587-42588,42590,42592,42594-42595,42598-42599,42602,42604,42606,42611-42613,42615,42617,42624-42625,42639,42648,42652-42653,42665,42687,42711,42713,42719,42722,42739-42740,42744,42758,42791-42792,42801,42814,42817-42818,42823,42830,42837,42839,42841,42844,42851-42852,42860,42864,42881,42888-42889,42892-42894,42930,42964,42967,42971-42972,42977-42978
 ,42986-42987,42989,42994,42998,43001,43004,43007-43008,43011-43012,43014-43015,43025,43027,43030,43032,43034,43036,43039,43045-43047,43056-43059,43062-43063,43065,43081,43089,43091
</del><span class="cx" style="display: block; padding: 0 10px">\ No newline at end of property
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+/trunk:18512,42132,42134,42136,42138,42140,42144,42146,42148,42150,42152-42153,42155,42157,42159,42161,42163,42169,42171,42173,42175,42177,42181,42183,42185,42187,42189,42191,42193,42199,42203,42210,42214,42220,42222,42226,42242,42244,42247,42251,42256-42261,42350,42358,42362,42364-42368,42374,42388,42390,42401,42417,42421,42423-42425,42430,42433,42437,42441,42443,42446-42447,42449,42451,42453,42457,42459,42462,42491,42521,42529,42531,42533,42536,42538,42541,42543,42545,42549,42566,42568,42570,42572,42574,42576,42579,42581-42582,42584-42585,42587-42588,42590,42592,42594-42595,42598-42599,42602,42604,42606,42611-42613,42615,42617,42624-42625,42639,42648,42652-42653,42665,42687,42711,42713,42719,42722,42739-42740,42744,42758,42791-42792,42801,42814,42817-42818,42823,42830,42837,42839,42841,42844,42851-42852,42860,42864,42881,42888-42889,42892-42894,42930,42964,42967,42971-42972,42977-42978
 ,42980,42986-42987,42989,42994,42998,43001,43004,43007-43008,43011-43012,43014-43015,43025,43027,43030,43032,43034,43036,43039,43045-43047,43056-43059,43062-43063,43065,43081,43089,43091
</ins><span class="cx" style="display: block; padding: 0 10px">\ No newline at end of property
</span><a id="branches49srcwpadmincsscommoncss"></a>
<div class="modfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Modified: branches/4.9/src/wp-admin/css/common.css</h4>
<pre class="diff"><span>
<span class="info" style="display: block; padding: 0 10px; color: #888">--- branches/4.9/src/wp-admin/css/common.css  2018-05-02 02:49:55 UTC (rev 43100)
+++ branches/4.9/src/wp-admin/css/common.css    2018-05-02 02:58:12 UTC (rev 43101)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -2981,7 +2981,8 @@
</span><span class="cx" style="display: block; padding: 0 10px"> /* Metabox collapse arrow indicators */
</span><span class="cx" style="display: block; padding: 0 10px"> .sidebar-name .toggle-indicator:before,
</span><span class="cx" style="display: block; padding: 0 10px"> .js .meta-box-sortables .postbox .toggle-indicator:before,
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-.bulk-action-notice .toggle-indicator:before {
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+.bulk-action-notice .toggle-indicator:before,
+.privacy-text-box .toggle-indicator:before {
</ins><span class="cx" style="display: block; padding: 0 10px">         content: "\f142";
</span><span class="cx" style="display: block; padding: 0 10px">        display: inline-block;
</span><span class="cx" style="display: block; padding: 0 10px">        font: normal 20px/1 dashicons;
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -2993,7 +2994,8 @@
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px"> .js .widgets-holder-wrap.closed .toggle-indicator:before,
</span><span class="cx" style="display: block; padding: 0 10px"> .js .meta-box-sortables .postbox.closed .handlediv .toggle-indicator:before,
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-.bulk-action-notice .bulk-action-errors-collapsed .toggle-indicator:before {
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+.bulk-action-notice .bulk-action-errors-collapsed .toggle-indicator:before,
+.privacy-text-box.closed .toggle-indicator:before {
</ins><span class="cx" style="display: block; padding: 0 10px">         content: "\f140";
</span><span class="cx" style="display: block; padding: 0 10px"> }
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span></span></pre></div>
<a id="branches49srcwpadmincsseditcss"></a>
<div class="modfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Modified: branches/4.9/src/wp-admin/css/edit.css</h4>
<pre class="diff"><span>
<span class="info" style="display: block; padding: 0 10px; color: #888">--- branches/4.9/src/wp-admin/css/edit.css    2018-05-02 02:49:55 UTC (rev 43100)
+++ branches/4.9/src/wp-admin/css/edit.css      2018-05-02 02:58:12 UTC (rev 43101)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -646,6 +646,68 @@
</span><span class="cx" style="display: block; padding: 0 10px">        margin-bottom: 20px;
</span><span class="cx" style="display: block; padding: 0 10px"> }
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+/* Sugested text for privacy policy postbox */
+.privacy-text-box {
+       margin: 10px 0 0;
+       
+}
+
+.privacy-text-box-head {
+       border-left: 4px solid #ffb900;
+       border-bottom: 1px solid #e5e5e5;
+       box-shadow: 0 1px 1px rgba(0,0,0,0.04);
+       
+}
+
+.privacy-text-box .privacy-text-box-head.hndle {
+       cursor: pointer;
+}
+
+.privacy-text-box-head p {
+       padding: 0 12px 14px;
+       margin: 0;
+}
+
+.privacy-text-box-body {
+       height: 320px;
+       overflow: auto;
+       
+}
+
+#privacy-text-box .inside {
+       margin: 0;
+}
+
+.privacy-text-box h3 {
+       font-size: 1em;
+       margin: 1em 0;
+}
+
+.privacy-text-section .privacy-text-copy-button {
+       float: right;
+       margin-top: -1.8em;
+}
+
+.privacy-text-section {
+       padding: 1px 14px 1px 12px;
+       border-top: 1px solid #e3e3e3;
+       border-left: 4px solid transparent;
+}
+
+.privacy-text-section.text-updated {
+       border-left-color: #46b450;
+       background-color: #ecf7ed;
+}
+
+.privacy-text-section.text-removed {
+       border-left-color: #dc3232;
+       background-color: #fbeaea;
+}
+
+.closed .privacy-text-box-body {
+       display: none;
+}
+
</ins><span class="cx" style="display: block; padding: 0 10px"> /*------------------------------------------------------------------------------
</span><span class="cx" style="display: block; padding: 0 10px">   11.1 - Custom Fields
</span><span class="cx" style="display: block; padding: 0 10px"> ------------------------------------------------------------------------------*/
</span></span></pre></div>
<a id="branches49srcwpadminincludesadminfiltersphp"></a>
<div class="modfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Modified: branches/4.9/src/wp-admin/includes/admin-filters.php</h4>
<pre class="diff"><span>
<span class="info" style="display: block; padding: 0 10px; color: #888">--- branches/4.9/src/wp-admin/includes/admin-filters.php      2018-05-02 02:49:55 UTC (rev 43100)
+++ branches/4.9/src/wp-admin/includes/admin-filters.php        2018-05-02 02:58:12 UTC (rev 43101)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -134,4 +134,17 @@
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px"> // Privacy hooks
</span><span class="cx" style="display: block; padding: 0 10px"> add_filter( 'wp_privacy_personal_data_export_page', 'wp_privacy_process_personal_data_export_page', 10, 6 );
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-add_action( 'wp_privacy_personal_data_export_file', 'wp_privacy_generate_personal_data_export_file', 10 );
</del><span class="cx" style="display: block; padding: 0 10px">\ No newline at end of file
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+add_action( 'wp_privacy_personal_data_export_file', 'wp_privacy_generate_personal_data_export_file', 10 );
+
+// Privacy policy text changes check.
+add_action( 'admin_init', array( 'WP_Privacy_Policy_Content', 'text_change_check' ), 20 );
+
+// Show a "postbox" with the text suggestions for a privacy policy.
+add_action( 'edit_form_after_title', array( 'WP_Privacy_Policy_Content', 'privacy_policy_postbox' ) );
+
+// Add the suggested policy text from WordPress.
+add_action( 'admin_init', array( 'WP_Privacy_Policy_Content', 'add_suggested_content' ), 15 );
+
+// Stop checking for text changes after the policy page is updated.
+add_action( 'post_updated', array( 'WP_Privacy_Policy_Content', '_policy_page_updated' ) );
+
</ins></span></pre></div>
<a id="branches49srcwpadminincludesmiscphp"></a>
<div class="modfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Modified: branches/4.9/src/wp-admin/includes/misc.php</h4>
<pre class="diff"><span>
<span class="info" style="display: block; padding: 0 10px; color: #888">--- branches/4.9/src/wp-admin/includes/misc.php       2018-05-02 02:49:55 UTC (rev 43100)
+++ branches/4.9/src/wp-admin/includes/misc.php 2018-05-02 02:58:12 UTC (rev 43101)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -1213,3 +1213,377 @@
</span><span class="cx" style="display: block; padding: 0 10px">                restore_previous_locale();
</span><span class="cx" style="display: block; padding: 0 10px">        }
</span><span class="cx" style="display: block; padding: 0 10px"> }
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+
+/**
+ * WP_Privacy_Policy_Content class.
+ * TODO: move this to a new file.
+ *
+ * @since 4.9.6
+ */
+final class WP_Privacy_Policy_Content {
+
+       private static $policy_content = array();
+
+       /**
+        * Constructor
+        *
+        * @since 4.9.6
+        */
+       private function __construct() {}
+
+       /**
+        * Add privacy information to the postbox shown when editing the privacy policy.
+        *
+        * Intended for use from `wp_add_privacy_policy_content()`.
+        *
+        * $since 5.0.0
+        *
+        * @param string $plugin_name The plugin'as name. Will be shown in the privacy policy metabox.
+        * @param string $policy_text The content that should appear in the site's privacy policy.
+        */
+       public static function add( $plugin_name, $policy_text ) {
+               if ( empty( $plugin_name ) || empty( $policy_text ) ) {
+                       return;
+               }
+
+               $data = array(
+                       'plugin_name' => $plugin_name,
+                       'policy_text' => $policy_text,
+               );
+
+               if ( ! in_array( $data, self::$policy_content, true ) ) {
+                       self::$policy_content[] = $data;
+               }
+       }
+
+       /**
+        * Quick check if any privacy info has changed.
+        *
+        * @since 4.9.6
+        */
+       public static function text_change_check() {
+
+               $policy_page_id = (int) get_option( 'wp_page_for_privacy_policy' );
+
+               // The site doesn't have a privacy policy.
+               if ( empty( $policy_page_id ) ) {
+                       return;
+               }
+
+               if ( ! current_user_can( 'edit_post', $policy_page_id ) ) {
+                       return;
+               }
+
+               // Also run when the option doesn't exist yet.
+               if ( get_option( '_wp_privacy_text_change_check' ) === 'no-check' ) {
+                       return;
+               }
+
+               $old = (array) get_post_meta( $policy_page_id, '_wp_suggested_privacy_policy_content' );
+               $new = self::$policy_content;
+
+               // Remove the extra values added to the meta.
+               foreach ( $old as $key => $data ) {
+                       $old[ $key ] = array(
+                               'plugin_name' => $data['plugin_name'],
+                               'policy_text' => $data['policy_text'],
+                       );
+               }
+
+               // The == operator (equal, not identical) was used intentionally.
+               // See http://php.net/manual/en/language.operators.array.php
+               if ( $new != $old ) {
+                       // A plugin was activated or deactivated, or some policy text has changed.
+                       // Show a notice on all screens in wp-admin.
+                       add_action( 'admin_notices', array( 'WP_Privacy_Policy_Content', 'policy_text_changed_notice' ) );
+               } else {
+                       // Stop checking.
+                       update_option( '_wp_privacy_text_change_check', 'no-check' );
+               }
+       }
+
+       /**
+        * Output an admin notice when some privacy info has changed.
+        *
+        * @since 4.9.6
+        */
+       public static function policy_text_changed_notice() {
+               global $post;
+               $policy_page_id = (int) get_option( 'wp_page_for_privacy_policy' );
+
+               ?>
+               <div class="policy-text-updated notice notice-warning is-dismissible">
+                       <p><?php
+
+                               _e( 'The suggested privacy policy text has changed.' );
+
+                               if ( empty( $post ) || $post->ID != $policy_page_id ) {
+                                       ?>
+                                       <a href="<?php echo get_edit_post_link( $policy_page_id ); ?>"><?php _e( 'Edit the privacy policy.' ); ?></a>
+                                       <?php
+                               }
+
+                       ?></p>
+               </div>
+               <?php
+       }
+
+       /**
+        * Stop checking for changed privacy info when the policy page is updated.
+        *
+        * @since 4.9.6
+        * @access private
+        */
+       public static function _policy_page_updated( $post_id ) {
+               $policy_page_id = (int) get_option( 'wp_page_for_privacy_policy' );
+
+               if ( ! $policy_page_id || $policy_page_id !== (int) $post_id ) {
+                       return;
+               }
+
+               // The policy page was updated.
+               // Stop checking for text changes.
+               update_option( '_wp_privacy_text_change_check', 'no-check' );
+
+               // Remove updated|removed status.
+               $old = (array) get_post_meta( $policy_page_id, '_wp_suggested_privacy_policy_content' );
+               $done = array();
+               $update_cache = false;
+
+               foreach ( $old as $old_key => $old_data ) {
+                       if ( ! empty( $old_data['removed'] ) ) {
+                               // Remove the old policy text.
+                               $update_cache = true;
+                               continue;
+                       }
+
+                       if ( ! empty( $old_data['updated'] ) ) {
+                               // 'updated' is now 'added'.
+                               $done[] = array(
+                                       'plugin_name' => $old_data['plugin_name'],
+                                       'policy_text' => $old_data['policy_text'],
+                                       'added'       => $old_data['updated'],
+                               );
+                               $update_cache = true;
+                       } else {
+                               $done[] = $old_data;
+                       }
+               }
+
+               if ( $update_cache ) {
+                       delete_post_meta( $policy_page_id, '_wp_suggested_privacy_policy_content' );
+                       // Update the cache.
+                       foreach ( $done as $data ) {
+                               add_post_meta( $policy_page_id, '_wp_suggested_privacy_policy_content', $data );
+                       }
+               }
+       }
+
+       /**
+        * Check for updated, added or removed privacy policy information from plugins.
+        *
+        * Caches the current info in post_meta of the policy page.
+        *
+        * @since 4.9.6
+        *
+        * @return array The privacy policy text/informtion added by core and plugins.
+        */
+       public static function get_suggested_policy_text() {
+               $policy_page_id = (int) get_option( 'wp_page_for_privacy_policy' );
+               $new = self::$policy_content;
+               $old = (array) get_post_meta( $policy_page_id, '_wp_suggested_privacy_policy_content' );
+               $checked = array();
+               $time = time();
+               $update_cache = false;
+
+               // Check for no-changes and updates.
+               foreach ( $new as $new_key => $new_data ) {
+                       foreach ( $old as $old_key => $old_data ) {
+                               $found = false;
+
+                               if ( $new_data['policy_text'] === $old_data['policy_text'] ) {
+                                       // Use the new plugin name in case it was changed, translated, etc.
+                                       if ( $old_data['plugin_name'] !== $new_data['plugin_name'] ) {
+                                               $old_data['plugin_name'] = $new_data['plugin_name'];
+                                               $update_cache = true;
+                                       }
+
+                                       // A plugin was re-activated.
+                                       if ( ! empty( $old_data['removed'] ) ) {
+                                               unset( $old_data['removed'] );
+                                               $old_data['added'] = $time;
+                                               $update_cache = true;
+                                       }
+
+                                       $checked[] = $old_data;
+                                       $found = true;
+                               } elseif ( $new_data['plugin_name'] === $old_data['plugin_name'] ) {
+                                       // The info for the policy was updated.
+                                       $checked[] = array(
+                                               'plugin_name' => $new_data['plugin_name'],
+                                               'policy_text' => $new_data['policy_text'],
+                                               'updated'     => $time,
+                                       );
+                                       $found = $update_cache = true;
+                               }
+
+                               if ( $found ) {
+                                       unset( $new[ $new_key ], $old[ $old_key ] );
+                                       continue 2;
+                               }
+                       }
+               }
+
+               if ( ! empty( $new ) ) {
+                       // A plugin was activated.
+                       foreach ( $new as $new_data ) {
+                               if ( ! empty( $new_data['plugin_name'] ) && ! empty( $new_data['policy_text'] ) ) {
+                                       $new_data['added'] = $time;
+                                       array_unshift( $checked, $new_data );
+                               }
+                       }
+                       $update_cache = true;
+               }
+
+               if ( ! empty( $old ) ) {
+                       // A plugin was deactivated.
+                       foreach ( $old as $old_data ) {
+                               if ( ! empty( $old_data['plugin_name'] ) && ! empty( $old_data['policy_text'] ) ) {
+                                       $data = array(
+                                               'plugin_name' => $old_data['plugin_name'],
+                                               'policy_text' => $old_data['policy_text'],
+                                               'removed'     => $time,
+                                       );
+                                       array_unshift( $checked, $data );
+                               }
+                       }
+                       $update_cache = true;
+               }
+
+               if ( $update_cache ) {
+                       delete_post_meta( $policy_page_id, '_wp_suggested_privacy_policy_content' );
+                       // Update the cache.
+                       foreach ( $checked as $data ) {
+                               add_post_meta( $policy_page_id, '_wp_suggested_privacy_policy_content', $data );
+                       }
+               }
+
+               // Stop checking for changes after the postbox has been loaded.
+               // TODO make this user removable?
+               if ( get_option( '_wp_privacy_text_change_check' ) !== 'no-check' ) {
+                       update_option( '_wp_privacy_text_change_check', 'no-check' );
+               }
+
+               return $checked;
+       }
+
+       /**
+        * Output the postbox when editing the privacy policy page
+        *
+        * @since 4.9.6
+        *
+        * @param $post WP_Post The currently edited post.
+        */
+       public static function privacy_policy_postbox( $post ) {
+               if ( ! ( $post instanceof WP_Post ) ) {
+                       return;
+               }
+
+               $policy_page_id = (int) get_option( 'wp_page_for_privacy_policy' );
+
+               if ( ! $policy_page_id || $policy_page_id != $post->ID ) {
+                       return;
+               }
+
+               $content_array = self::get_suggested_policy_text();
+
+               $content = '';
+               $date_format = get_option( 'date_format' );
+               $copy = __( 'Copy' );
+
+               foreach ( $content_array as $section ) {
+                       $class = $meta = '';
+
+                       if ( ! empty( $section['removed'] ) ) {
+                               $class = ' text-removed';
+                               $date = date_i18n( $date_format, $section['removed'] );
+                               $meta  = sprintf( __( 'Policy text removed %s.' ), $date );
+                       } elseif ( ! empty( $section['updated'] ) ) {
+                               $class = ' text-updated';
+                               $date = date_i18n( $date_format, $section['updated'] );
+                               $meta  = sprintf( __( 'Policy text last updated %s.' ), $date );
+                       } elseif ( ! empty( $section['added'] ) ) {
+                               $class = ' text-added';
+                               $date = date_i18n( $date_format, $section['added'] );
+                               $meta  = sprintf( __( 'Policy text added %s.' ), $date );
+                       }
+
+                       $content .= '<div class="privacy-text-section' . $class . '">';
+                       $content .= '<h3>' . $section['plugin_name'] . '</h3>';
+                       $content .= '<button type="button" class="privacy-text-copy-button button">';
+                       $content .= $copy;
+                       $content .= '<span class="screen-reader-text">' . sprintf( __( 'Copy suggested policy text from %s.' ), $section['plugin_name'] ) . '</span>';
+                       $content .= '</button>';
+
+                       if ( ! empty( $meta ) ) {
+                               $content .= '<span class="privacy-text-meta">' . $meta . '</span>';
+                       }
+
+                       $content .= '<div class="policy-text">' . $section['policy_text'] . '</div>';
+                       $content .= "</div>\n";
+               }
+
+               ?>
+               <div id="privacy-text-box" class="privacy-text-box postbox <?php echo postbox_classes( 'privacy-text-box', 'page' ); ?>">
+                       <button type="button" class="handlediv" aria-expanded="true">
+                               <span class="screen-reader-text"><?php _e( 'Toggle panel: Suggested privacy policy text' ); ?></span>
+                               <span class="toggle-indicator" aria-hidden="true"></span>
+                       </button>
+                       <div class="privacy-text-box-head hndle">
+                               <h2><?php _e( 'This suggested privacy policy text comes from plugins you have installed.' ); ?></h2>
+                               <p>
+                                       <?php _e( 'We suggest reviewing this text then copying and pasting it into your privacy policy page.' ); ?>
+                                       <?php _e( 'Please remember you are responsible for the policies you choose to adopt, so review the content and make any necessary edits.' ); ?>
+                               </p>
+                       </div>
+
+                       <div class="privacy-text-box-body">
+                               <?php echo $content; ?>
+                       </div>
+               </div>
+               <?php
+       }
+
+       /**
+        * Return the default suggested privacy policy content.
+        *
+        * @since 4.9.6          
+        *
+        * @return string The defauld policy content.
+        */
+       public static function get_default_content() {
+               $content  = '<p>' . __( 'Lorem ipsum dolor sit amet consectetuer id elit enim neque est. Sodales tincidunt Nulla leo penatibus Vestibulum adipiscing est cursus Nam Vestibulum. Orci Vivamus mollis eget pretium dictumst Donec Integer auctor sociis rutrum. Mauris felis Donec neque cursus tellus odio adipiscing netus elit Donec. Vestibulum Cras ligula vitae pretium Curabitur eros Nam Lorem eros non. Sed id mauris justo tristique orci neque eleifend lacus lorem.' ) . "</p>\n";
+               $content .= '<p>' . __( 'Sed consequat Nullam et vel platea semper id mauris Nam eget. Sem neque a amet eu ipsum id dignissim neque eu pulvinar. Mauris nulla egestas et laoreet penatibus ipsum lobortis convallis congue libero. Tortor nibh pellentesque tellus odio Morbi cursus eros tincidunt tincidunt sociis. Egestas at In Donec mi dignissim Nam rutrum felis metus Maecenas. Sed tellus consectetuer.' ) . "</p>\n";
+               $content .= '<p>' . __( 'Justo orci pulvinar mauris tincidunt sed Pellentesque dis sapien tempor ligula. Dolor laoreet fames eros accumsan Integer feugiat nec augue Phasellus rutrum. Id Sed facilisi elit mus nulla at dapibus ut enim sociis. Fringilla ridiculus dui justo eu Maecenas ipsum ut aliquet magna non. Id magna adipiscing Vestibulum Curabitur vel pretium ac justo platea neque. Maecenas Donec Quisque urna interdum.' ) . "</p>\n";
+               $content .= '<p>' . __( 'Tellus sagittis leo adipiscing ante facilisis Aliquam tellus at at elit. Ut dignissim tempus eu Fusce Vestibulum at eros ante dis tempus. Sed libero orci at id ut pretium metus adipiscing justo malesuada. In tempus vitae commodo libero In neque sagittis turpis In In. Eleifend elit dis ac eros urna auctor semper quis odio pretium. Ut Aenean cursus.' ) . "</p>\n";
+
+               /**
+                * Filters the default content suggested for inclusion in a privacy policy.
+                *
+                * @since 4.9.6
+                *
+                * @param $content string The defauld policy content.
+                */
+               return apply_filters( 'wp_get_default_privcy_policy_content', $content );
+       }
+
+       /**
+        * Add the suggested privacy policy text to the policy postbox.
+        *
+        * @since 4.9.6
+        */
+       public static function add_suggested_content() {
+               $content = self::get_default_content();
+               wp_add_privacy_policy_content(  'WordPress', $content );
+       }
+}
</ins></span></pre></div>
<a id="branches49srcwpadminincludespluginphp"></a>
<div class="modfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Modified: branches/4.9/src/wp-admin/includes/plugin.php</h4>
<pre class="diff"><span>
<span class="info" style="display: block; padding: 0 10px; color: #888">--- branches/4.9/src/wp-admin/includes/plugin.php     2018-05-02 02:49:55 UTC (rev 43100)
+++ branches/4.9/src/wp-admin/includes/plugin.php       2018-05-02 02:58:12 UTC (rev 43101)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -1896,3 +1896,22 @@
</span><span class="cx" style="display: block; padding: 0 10px">        wp_register_plugin_realpath( WP_PLUGIN_DIR . '/' . $plugin );
</span><span class="cx" style="display: block; padding: 0 10px">        include( WP_PLUGIN_DIR . '/' . $plugin );
</span><span class="cx" style="display: block; padding: 0 10px"> }
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+
+/**
+ * Helper function for adding plugin specific information to the postbox shown when editing the privacy policy.
+ *
+ * Intended for use with the `'admin_init'` action.
+ *
+ * @since 4.9.6
+ *
+ * @param string $plugin_name The plugin'as name. Will be shown in the privacy policy metabox.
+ * @param string $policy_text The content that should appear in the site's privacy policy.
+ *                            For more information see the Plugins Handbook https://developer.wordpress.org/plugins/. 
+ */
+function wp_add_privacy_policy_content( $plugin_name, $policy_text ) {
+       if ( ! class_exists( 'WP_Privacy_Policy_Content' ) ) {
+               require_once( ABSPATH . 'wp-admin/includes/misc.php' );
+       }
+
+       WP_Privacy_Policy_Content::add( $plugin_name, $policy_text );
+}
</ins></span></pre></div>
<a id="branches49srcwpadminjspostjs"></a>
<div class="modfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Modified: branches/4.9/src/wp-admin/js/post.js</h4>
<pre class="diff"><span>
<span class="info" style="display: block; padding: 0 10px; color: #888">--- branches/4.9/src/wp-admin/js/post.js      2018-05-02 02:49:55 UTC (rev 43100)
+++ branches/4.9/src/wp-admin/js/post.js        2018-05-02 02:58:12 UTC (rev 43101)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -1264,4 +1264,27 @@
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px">                update();
</span><span class="cx" style="display: block; padding: 0 10px">        } );
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+
+       // Privacy policy postbox, copy button.
+       $( document ).on( 'click', function( event ) {
+               var $target = $( event.target );
+               var node, range;
+
+               if ( $target.is( 'button.privacy-text-copy-button' ) ) {
+                       node = $target.parent().find( 'div.policy-text' )[0];
+
+                       if ( node ) {
+                               try {
+                                       window.getSelection().removeAllRanges();
+                                       range = document.createRange();  
+                                       range.selectNode( node );  
+                                       window.getSelection().addRange( range );
+
+                                       document.execCommand( 'copy' );
+                                       window.getSelection().removeAllRanges();
+                               } catch ( er ) {}
+                       }
+               }
+       });
+
</ins><span class="cx" style="display: block; padding: 0 10px"> } )( jQuery, new wp.utils.WordCounter() );
</span></span></pre></div>
<a id="branches49srcwpincludesdefaultfiltersphp"></a>
<div class="modfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Modified: branches/4.9/src/wp-includes/default-filters.php</h4>
<pre class="diff"><span>
<span class="info" style="display: block; padding: 0 10px; color: #888">--- branches/4.9/src/wp-includes/default-filters.php  2018-05-02 02:49:55 UTC (rev 43100)
+++ branches/4.9/src/wp-includes/default-filters.php    2018-05-02 02:58:12 UTC (rev 43101)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -532,4 +532,8 @@
</span><span class="cx" style="display: block; padding: 0 10px"> // Capabilities
</span><span class="cx" style="display: block; padding: 0 10px"> add_filter( 'user_has_cap', 'wp_maybe_grant_install_languages_cap', 1 );
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+// Trigger the check for policy text changes after active plugins change.
+add_action( 'update_site_option_active_sitewide_plugins', '_wp_privacy_active_plugins_change' );
+add_action( 'update_option_active_plugins', '_wp_privacy_active_plugins_change' );
+
</ins><span class="cx" style="display: block; padding: 0 10px"> unset( $filter, $action );
</span></span></pre></div>
<a id="branches49srcwpincludesfunctionsphp"></a>
<div class="modfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Modified: branches/4.9/src/wp-includes/functions.php</h4>
<pre class="diff"><span>
<span class="info" style="display: block; padding: 0 10px; color: #888">--- branches/4.9/src/wp-includes/functions.php        2018-05-02 02:49:55 UTC (rev 43100)
+++ branches/4.9/src/wp-includes/functions.php  2018-05-02 02:58:12 UTC (rev 43101)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -5936,6 +5936,16 @@
</span><span class="cx" style="display: block; padding: 0 10px"> }
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px"> /**
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ * Trigger the check for policy text changes.
+ *
+ * @since 4.9.6
+ * @access private 
+ */
+function _wp_privacy_active_plugins_change() {
+       update_option( '_wp_privacy_text_change_check', 'check' );
+}
+
+/**
</ins><span class="cx" style="display: block; padding: 0 10px">  * Schedule a `WP_Cron` job to delete expired export files.
</span><span class="cx" style="display: block; padding: 0 10px">  *
</span><span class="cx" style="display: block; padding: 0 10px">  * @since 4.9.6
</span></span></pre></div>
<a id="branches49srcwpincludeslinktemplatephp"></a>
<div class="modfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Modified: branches/4.9/src/wp-includes/link-template.php</h4>
<pre class="diff"><span>
<span class="info" style="display: block; padding: 0 10px; color: #888">--- branches/4.9/src/wp-includes/link-template.php    2018-05-02 02:49:55 UTC (rev 43100)
+++ branches/4.9/src/wp-includes/link-template.php      2018-05-02 02:58:12 UTC (rev 43101)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -4104,3 +4104,20 @@
</span><span class="cx" style="display: block; padding: 0 10px">         */
</span><span class="cx" style="display: block; padding: 0 10px">        return apply_filters( 'parent_theme_file_path', $path, $file );
</span><span class="cx" style="display: block; padding: 0 10px"> }
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+
+/**
+ * Retrieves the URL to the privacy policy page.
+ *
+ * @since 4.9.6
+ *
+ * @return string The URL to the privacy policy page. Empty string if it doesn't exist.
+ */
+function get_privacy_policy_url() {
+       $policy_page_id = (int) get_option( 'wp_page_for_privacy_policy' );
+
+       if ( empty( $policy_page_id ) ) {
+               return '';
+       }
+
+       return get_permalink( $policy_page_id );
+}
</ins></span></pre>
</div>
</div>

</body>
</html>