WordPress.org

Make WordPress Core

Changeset 43101


Ignore:
Timestamp:
05/02/2018 02:58:12 AM (17 months ago)
Author:
SergeyBiryukov
Message:

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 [42980] to the 4.9 branch.
See #43620.

Location:
branches/4.9
Files:
10 edited

Legend:

Unmodified
Added
Removed
  • branches/4.9

  • branches/4.9/src/wp-admin/css/common.css

    r42883 r43101  
    29822982.sidebar-name .toggle-indicator:before,
    29832983.js .meta-box-sortables .postbox .toggle-indicator:before,
    2984 .bulk-action-notice .toggle-indicator:before {
     2984.bulk-action-notice .toggle-indicator:before,
     2985.privacy-text-box .toggle-indicator:before {
    29852986    content: "\f142";
    29862987    display: inline-block;
     
    29942995.js .widgets-holder-wrap.closed .toggle-indicator:before,
    29952996.js .meta-box-sortables .postbox.closed .handlediv .toggle-indicator:before,
    2996 .bulk-action-notice .bulk-action-errors-collapsed .toggle-indicator:before {
     2997.bulk-action-notice .bulk-action-errors-collapsed .toggle-indicator:before,
     2998.privacy-text-box.closed .toggle-indicator:before {
    29972999    content: "\f140";
    29983000}
  • branches/4.9/src/wp-admin/css/edit.css

    r41859 r43101  
    645645.edit-form-section {
    646646    margin-bottom: 20px;
     647}
     648
     649/* Sugested text for privacy policy postbox */
     650.privacy-text-box {
     651    margin: 10px 0 0;
     652   
     653}
     654
     655.privacy-text-box-head {
     656    border-left: 4px solid #ffb900;
     657    border-bottom: 1px solid #e5e5e5;
     658    box-shadow: 0 1px 1px rgba(0,0,0,0.04);
     659   
     660}
     661
     662.privacy-text-box .privacy-text-box-head.hndle {
     663    cursor: pointer;
     664}
     665
     666.privacy-text-box-head p {
     667    padding: 0 12px 14px;
     668    margin: 0;
     669}
     670
     671.privacy-text-box-body {
     672    height: 320px;
     673    overflow: auto;
     674   
     675}
     676
     677#privacy-text-box .inside {
     678    margin: 0;
     679}
     680
     681.privacy-text-box h3 {
     682    font-size: 1em;
     683    margin: 1em 0;
     684}
     685
     686.privacy-text-section .privacy-text-copy-button {
     687    float: right;
     688    margin-top: -1.8em;
     689}
     690
     691.privacy-text-section {
     692    padding: 1px 14px 1px 12px;
     693    border-top: 1px solid #e3e3e3;
     694    border-left: 4px solid transparent;
     695}
     696
     697.privacy-text-section.text-updated {
     698    border-left-color: #46b450;
     699    background-color: #ecf7ed;
     700}
     701
     702.privacy-text-section.text-removed {
     703    border-left-color: #dc3232;
     704    background-color: #fbeaea;
     705}
     706
     707.closed .privacy-text-box-body {
     708    display: none;
    647709}
    648710
  • branches/4.9/src/wp-admin/includes/admin-filters.php

    r43092 r43101  
    136136add_filter( 'wp_privacy_personal_data_export_page', 'wp_privacy_process_personal_data_export_page', 10, 6 );
    137137add_action( 'wp_privacy_personal_data_export_file', 'wp_privacy_generate_personal_data_export_file', 10 );
     138
     139// Privacy policy text changes check.
     140add_action( 'admin_init', array( 'WP_Privacy_Policy_Content', 'text_change_check' ), 20 );
     141
     142// Show a "postbox" with the text suggestions for a privacy policy.
     143add_action( 'edit_form_after_title', array( 'WP_Privacy_Policy_Content', 'privacy_policy_postbox' ) );
     144
     145// Add the suggested policy text from WordPress.
     146add_action( 'admin_init', array( 'WP_Privacy_Policy_Content', 'add_suggested_content' ), 15 );
     147
     148// Stop checking for text changes after the policy page is updated.
     149add_action( 'post_updated', array( 'WP_Privacy_Policy_Content', '_policy_page_updated' ) );
     150
  • branches/4.9/src/wp-admin/includes/misc.php

    r42831 r43101  
    12141214    }
    12151215}
     1216
     1217/**
     1218 * WP_Privacy_Policy_Content class.
     1219 * TODO: move this to a new file.
     1220 *
     1221 * @since 4.9.6
     1222 */
     1223final class WP_Privacy_Policy_Content {
     1224
     1225    private static $policy_content = array();
     1226
     1227    /**
     1228     * Constructor
     1229     *
     1230     * @since 4.9.6
     1231     */
     1232    private function __construct() {}
     1233
     1234    /**
     1235     * Add privacy information to the postbox shown when editing the privacy policy.
     1236     *
     1237     * Intended for use from `wp_add_privacy_policy_content()`.
     1238     *
     1239     * $since 5.0.0
     1240     *
     1241     * @param string $plugin_name The plugin'as name. Will be shown in the privacy policy metabox.
     1242     * @param string $policy_text The content that should appear in the site's privacy policy.
     1243     */
     1244    public static function add( $plugin_name, $policy_text ) {
     1245        if ( empty( $plugin_name ) || empty( $policy_text ) ) {
     1246            return;
     1247        }
     1248
     1249        $data = array(
     1250            'plugin_name' => $plugin_name,
     1251            'policy_text' => $policy_text,
     1252        );
     1253
     1254        if ( ! in_array( $data, self::$policy_content, true ) ) {
     1255            self::$policy_content[] = $data;
     1256        }
     1257    }
     1258
     1259    /**
     1260     * Quick check if any privacy info has changed.
     1261     *
     1262     * @since 4.9.6
     1263     */
     1264    public static function text_change_check() {
     1265
     1266        $policy_page_id = (int) get_option( 'wp_page_for_privacy_policy' );
     1267
     1268        // The site doesn't have a privacy policy.
     1269        if ( empty( $policy_page_id ) ) {
     1270            return;
     1271        }
     1272
     1273        if ( ! current_user_can( 'edit_post', $policy_page_id ) ) {
     1274            return;
     1275        }
     1276
     1277        // Also run when the option doesn't exist yet.
     1278        if ( get_option( '_wp_privacy_text_change_check' ) === 'no-check' ) {
     1279            return;
     1280        }
     1281
     1282        $old = (array) get_post_meta( $policy_page_id, '_wp_suggested_privacy_policy_content' );
     1283        $new = self::$policy_content;
     1284
     1285        // Remove the extra values added to the meta.
     1286        foreach ( $old as $key => $data ) {
     1287            $old[ $key ] = array(
     1288                'plugin_name' => $data['plugin_name'],
     1289                'policy_text' => $data['policy_text'],
     1290            );
     1291        }
     1292
     1293        // The == operator (equal, not identical) was used intentionally.
     1294        // See http://php.net/manual/en/language.operators.array.php
     1295        if ( $new != $old ) {
     1296            // A plugin was activated or deactivated, or some policy text has changed.
     1297            // Show a notice on all screens in wp-admin.
     1298            add_action( 'admin_notices', array( 'WP_Privacy_Policy_Content', 'policy_text_changed_notice' ) );
     1299        } else {
     1300            // Stop checking.
     1301            update_option( '_wp_privacy_text_change_check', 'no-check' );
     1302        }
     1303    }
     1304
     1305    /**
     1306     * Output an admin notice when some privacy info has changed.
     1307     *
     1308     * @since 4.9.6
     1309     */
     1310    public static function policy_text_changed_notice() {
     1311        global $post;
     1312        $policy_page_id = (int) get_option( 'wp_page_for_privacy_policy' );
     1313
     1314        ?>
     1315        <div class="policy-text-updated notice notice-warning is-dismissible">
     1316            <p><?php
     1317
     1318                _e( 'The suggested privacy policy text has changed.' );
     1319
     1320                if ( empty( $post ) || $post->ID != $policy_page_id ) {
     1321                    ?>
     1322                    <a href="<?php echo get_edit_post_link( $policy_page_id ); ?>"><?php _e( 'Edit the privacy policy.' ); ?></a>
     1323                    <?php
     1324                }
     1325
     1326            ?></p>
     1327        </div>
     1328        <?php
     1329    }
     1330
     1331    /**
     1332     * Stop checking for changed privacy info when the policy page is updated.
     1333     *
     1334     * @since 4.9.6
     1335     * @access private
     1336     */
     1337    public static function _policy_page_updated( $post_id ) {
     1338        $policy_page_id = (int) get_option( 'wp_page_for_privacy_policy' );
     1339
     1340        if ( ! $policy_page_id || $policy_page_id !== (int) $post_id ) {
     1341            return;
     1342        }
     1343
     1344        // The policy page was updated.
     1345        // Stop checking for text changes.
     1346        update_option( '_wp_privacy_text_change_check', 'no-check' );
     1347
     1348        // Remove updated|removed status.
     1349        $old = (array) get_post_meta( $policy_page_id, '_wp_suggested_privacy_policy_content' );
     1350        $done = array();
     1351        $update_cache = false;
     1352
     1353        foreach ( $old as $old_key => $old_data ) {
     1354            if ( ! empty( $old_data['removed'] ) ) {
     1355                // Remove the old policy text.
     1356                $update_cache = true;
     1357                continue;
     1358            }
     1359
     1360            if ( ! empty( $old_data['updated'] ) ) {
     1361                // 'updated' is now 'added'.
     1362                $done[] = array(
     1363                    'plugin_name' => $old_data['plugin_name'],
     1364                    'policy_text' => $old_data['policy_text'],
     1365                    'added'       => $old_data['updated'],
     1366                );
     1367                $update_cache = true;
     1368            } else {
     1369                $done[] = $old_data;
     1370            }
     1371        }
     1372
     1373        if ( $update_cache ) {
     1374            delete_post_meta( $policy_page_id, '_wp_suggested_privacy_policy_content' );
     1375            // Update the cache.
     1376            foreach ( $done as $data ) {
     1377                add_post_meta( $policy_page_id, '_wp_suggested_privacy_policy_content', $data );
     1378            }
     1379        }
     1380    }
     1381
     1382    /**
     1383     * Check for updated, added or removed privacy policy information from plugins.
     1384     *
     1385     * Caches the current info in post_meta of the policy page.
     1386     *
     1387     * @since 4.9.6
     1388     *
     1389     * @return array The privacy policy text/informtion added by core and plugins.
     1390     */
     1391    public static function get_suggested_policy_text() {
     1392        $policy_page_id = (int) get_option( 'wp_page_for_privacy_policy' );
     1393        $new = self::$policy_content;
     1394        $old = (array) get_post_meta( $policy_page_id, '_wp_suggested_privacy_policy_content' );
     1395        $checked = array();
     1396        $time = time();
     1397        $update_cache = false;
     1398
     1399        // Check for no-changes and updates.
     1400        foreach ( $new as $new_key => $new_data ) {
     1401            foreach ( $old as $old_key => $old_data ) {
     1402                $found = false;
     1403
     1404                if ( $new_data['policy_text'] === $old_data['policy_text'] ) {
     1405                    // Use the new plugin name in case it was changed, translated, etc.
     1406                    if ( $old_data['plugin_name'] !== $new_data['plugin_name'] ) {
     1407                        $old_data['plugin_name'] = $new_data['plugin_name'];
     1408                        $update_cache = true;
     1409                    }
     1410
     1411                    // A plugin was re-activated.
     1412                    if ( ! empty( $old_data['removed'] ) ) {
     1413                        unset( $old_data['removed'] );
     1414                        $old_data['added'] = $time;
     1415                        $update_cache = true;
     1416                    }
     1417
     1418                    $checked[] = $old_data;
     1419                    $found = true;
     1420                } elseif ( $new_data['plugin_name'] === $old_data['plugin_name'] ) {
     1421                    // The info for the policy was updated.
     1422                    $checked[] = array(
     1423                        'plugin_name' => $new_data['plugin_name'],
     1424                        'policy_text' => $new_data['policy_text'],
     1425                        'updated'     => $time,
     1426                    );
     1427                    $found = $update_cache = true;
     1428                }
     1429
     1430                if ( $found ) {
     1431                    unset( $new[ $new_key ], $old[ $old_key ] );
     1432                    continue 2;
     1433                }
     1434            }
     1435        }
     1436
     1437        if ( ! empty( $new ) ) {
     1438            // A plugin was activated.
     1439            foreach ( $new as $new_data ) {
     1440                if ( ! empty( $new_data['plugin_name'] ) && ! empty( $new_data['policy_text'] ) ) {
     1441                    $new_data['added'] = $time;
     1442                    array_unshift( $checked, $new_data );
     1443                }
     1444            }
     1445            $update_cache = true;
     1446        }
     1447
     1448        if ( ! empty( $old ) ) {
     1449            // A plugin was deactivated.
     1450            foreach ( $old as $old_data ) {
     1451                if ( ! empty( $old_data['plugin_name'] ) && ! empty( $old_data['policy_text'] ) ) {
     1452                    $data = array(
     1453                        'plugin_name' => $old_data['plugin_name'],
     1454                        'policy_text' => $old_data['policy_text'],
     1455                        'removed'     => $time,
     1456                    );
     1457                    array_unshift( $checked, $data );
     1458                }
     1459            }
     1460            $update_cache = true;
     1461        }
     1462
     1463        if ( $update_cache ) {
     1464            delete_post_meta( $policy_page_id, '_wp_suggested_privacy_policy_content' );
     1465            // Update the cache.
     1466            foreach ( $checked as $data ) {
     1467                add_post_meta( $policy_page_id, '_wp_suggested_privacy_policy_content', $data );
     1468            }
     1469        }
     1470
     1471        // Stop checking for changes after the postbox has been loaded.
     1472        // TODO make this user removable?
     1473        if ( get_option( '_wp_privacy_text_change_check' ) !== 'no-check' ) {
     1474            update_option( '_wp_privacy_text_change_check', 'no-check' );
     1475        }
     1476
     1477        return $checked;
     1478    }
     1479
     1480    /**
     1481     * Output the postbox when editing the privacy policy page
     1482     *
     1483     * @since 4.9.6
     1484     *
     1485     * @param $post WP_Post The currently edited post.
     1486     */
     1487    public static function privacy_policy_postbox( $post ) {
     1488        if ( ! ( $post instanceof WP_Post ) ) {
     1489            return;
     1490        }
     1491
     1492        $policy_page_id = (int) get_option( 'wp_page_for_privacy_policy' );
     1493
     1494        if ( ! $policy_page_id || $policy_page_id != $post->ID ) {
     1495            return;
     1496        }
     1497
     1498        $content_array = self::get_suggested_policy_text();
     1499
     1500        $content = '';
     1501        $date_format = get_option( 'date_format' );
     1502        $copy = __( 'Copy' );
     1503
     1504        foreach ( $content_array as $section ) {
     1505            $class = $meta = '';
     1506
     1507            if ( ! empty( $section['removed'] ) ) {
     1508                $class = ' text-removed';
     1509                $date = date_i18n( $date_format, $section['removed'] );
     1510                $meta  = sprintf( __( 'Policy text removed %s.' ), $date );
     1511            } elseif ( ! empty( $section['updated'] ) ) {
     1512                $class = ' text-updated';
     1513                $date = date_i18n( $date_format, $section['updated'] );
     1514                $meta  = sprintf( __( 'Policy text last updated %s.' ), $date );
     1515            } elseif ( ! empty( $section['added'] ) ) {
     1516                $class = ' text-added';
     1517                $date = date_i18n( $date_format, $section['added'] );
     1518                $meta  = sprintf( __( 'Policy text added %s.' ), $date );
     1519            }
     1520
     1521            $content .= '<div class="privacy-text-section' . $class . '">';
     1522            $content .= '<h3>' . $section['plugin_name'] . '</h3>';
     1523            $content .= '<button type="button" class="privacy-text-copy-button button">';
     1524            $content .= $copy;
     1525            $content .= '<span class="screen-reader-text">' . sprintf( __( 'Copy suggested policy text from %s.' ), $section['plugin_name'] ) . '</span>';
     1526            $content .= '</button>';
     1527
     1528            if ( ! empty( $meta ) ) {
     1529                $content .= '<span class="privacy-text-meta">' . $meta . '</span>';
     1530            }
     1531
     1532            $content .= '<div class="policy-text">' . $section['policy_text'] . '</div>';
     1533            $content .= "</div>\n";
     1534        }
     1535
     1536        ?>
     1537        <div id="privacy-text-box" class="privacy-text-box postbox <?php echo postbox_classes( 'privacy-text-box', 'page' ); ?>">
     1538            <button type="button" class="handlediv" aria-expanded="true">
     1539                <span class="screen-reader-text"><?php _e( 'Toggle panel: Suggested privacy policy text' ); ?></span>
     1540                <span class="toggle-indicator" aria-hidden="true"></span>
     1541            </button>
     1542            <div class="privacy-text-box-head hndle">
     1543                <h2><?php _e( 'This suggested privacy policy text comes from plugins you have installed.' ); ?></h2>
     1544                <p>
     1545                    <?php _e( 'We suggest reviewing this text then copying and pasting it into your privacy policy page.' ); ?>
     1546                    <?php _e( 'Please remember you are responsible for the policies you choose to adopt, so review the content and make any necessary edits.' ); ?>
     1547                </p>
     1548            </div>
     1549
     1550            <div class="privacy-text-box-body">
     1551                <?php echo $content; ?>
     1552            </div>
     1553        </div>
     1554        <?php
     1555    }
     1556
     1557    /**
     1558     * Return the default suggested privacy policy content.
     1559     *
     1560     * @since 4.9.6     
     1561     *
     1562     * @return string The defauld policy content.
     1563     */
     1564    public static function get_default_content() {
     1565        $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";
     1566        $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";
     1567        $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";
     1568        $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";
     1569
     1570        /**
     1571         * Filters the default content suggested for inclusion in a privacy policy.
     1572         *
     1573         * @since 4.9.6
     1574         *
     1575         * @param $content string The defauld policy content.
     1576         */
     1577        return apply_filters( 'wp_get_default_privcy_policy_content', $content );
     1578    }
     1579
     1580    /**
     1581     * Add the suggested privacy policy text to the policy postbox.
     1582     *
     1583     * @since 4.9.6
     1584     */
     1585    public static function add_suggested_content() {
     1586        $content = self::get_default_content();
     1587        wp_add_privacy_policy_content(  'WordPress', $content );
     1588    }
     1589}
  • branches/4.9/src/wp-admin/includes/plugin.php

    r42243 r43101  
    18971897    include( WP_PLUGIN_DIR . '/' . $plugin );
    18981898}
     1899
     1900/**
     1901 * Helper function for adding plugin specific information to the postbox shown when editing the privacy policy.
     1902 *
     1903 * Intended for use with the `'admin_init'` action.
     1904 *
     1905 * @since 4.9.6
     1906 *
     1907 * @param string $plugin_name The plugin'as name. Will be shown in the privacy policy metabox.
     1908 * @param string $policy_text The content that should appear in the site's privacy policy.
     1909 *                            For more information see the Plugins Handbook https://developer.wordpress.org/plugins/.
     1910 */
     1911function wp_add_privacy_policy_content( $plugin_name, $policy_text ) {
     1912    if ( ! class_exists( 'WP_Privacy_Policy_Content' ) ) {
     1913        require_once( ABSPATH . 'wp-admin/includes/misc.php' );
     1914    }
     1915
     1916    WP_Privacy_Policy_Content::add( $plugin_name, $policy_text );
     1917}
  • branches/4.9/src/wp-admin/js/post.js

    r38893 r43101  
    12651265        update();
    12661266    } );
     1267
     1268    // Privacy policy postbox, copy button.
     1269    $( document ).on( 'click', function( event ) {
     1270        var $target = $( event.target );
     1271        var node, range;
     1272
     1273        if ( $target.is( 'button.privacy-text-copy-button' ) ) {
     1274            node = $target.parent().find( 'div.policy-text' )[0];
     1275
     1276            if ( node ) {
     1277                try {
     1278                    window.getSelection().removeAllRanges();
     1279                    range = document.createRange(); 
     1280                    range.selectNode( node ); 
     1281                    window.getSelection().addRange( range );
     1282
     1283                    document.execCommand( 'copy' );
     1284                    window.getSelection().removeAllRanges();
     1285                } catch ( er ) {}
     1286            }
     1287        }
     1288    });
     1289
    12671290} )( jQuery, new wp.utils.WordCounter() );
  • branches/4.9/src/wp-includes/default-filters.php

    r43095 r43101  
    533533add_filter( 'user_has_cap', 'wp_maybe_grant_install_languages_cap', 1 );
    534534
     535// Trigger the check for policy text changes after active plugins change.
     536add_action( 'update_site_option_active_sitewide_plugins', '_wp_privacy_active_plugins_change' );
     537add_action( 'update_option_active_plugins', '_wp_privacy_active_plugins_change' );
     538
    535539unset( $filter, $action );
  • branches/4.9/src/wp-includes/functions.php

    r43097 r43101  
    59375937
    59385938/**
     5939 * Trigger the check for policy text changes.
     5940 *
     5941 * @since 4.9.6
     5942 * @access private
     5943 */
     5944function _wp_privacy_active_plugins_change() {
     5945    update_option( '_wp_privacy_text_change_check', 'check' );
     5946}
     5947
     5948/**
    59395949 * Schedule a `WP_Cron` job to delete expired export files.
    59405950 *
  • branches/4.9/src/wp-includes/link-template.php

    r41589 r43101  
    41054105    return apply_filters( 'parent_theme_file_path', $path, $file );
    41064106}
     4107
     4108/**
     4109 * Retrieves the URL to the privacy policy page.
     4110 *
     4111 * @since 4.9.6
     4112 *
     4113 * @return string The URL to the privacy policy page. Empty string if it doesn't exist.
     4114 */
     4115function get_privacy_policy_url() {
     4116    $policy_page_id = (int) get_option( 'wp_page_for_privacy_policy' );
     4117
     4118    if ( empty( $policy_page_id ) ) {
     4119        return '';
     4120    }
     4121
     4122    return get_permalink( $policy_page_id );
     4123}
Note: See TracChangeset for help on using the changeset viewer.