Make WordPress Core

Changeset 42980


Ignore:
Timestamp:
04/16/2018 08:52:18 AM (7 years ago)
Author:
azaozz
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.
See #43620.

Location:
trunk/src
Files:
9 edited

Legend:

Unmodified
Added
Removed
  • trunk/src/wp-admin/css/common.css

    r42864 r42980  
    29912991.sidebar-name .toggle-indicator:before,
    29922992.js .meta-box-sortables .postbox .toggle-indicator:before,
    2993 .bulk-action-notice .toggle-indicator:before {
     2993.bulk-action-notice .toggle-indicator:before,
     2994.privacy-text-box .toggle-indicator:before {
    29942995    content: "\f142";
    29952996    display: inline-block;
     
    30033004.js .widgets-holder-wrap.closed .toggle-indicator:before,
    30043005.js .meta-box-sortables .postbox.closed .handlediv .toggle-indicator:before,
    3005 .bulk-action-notice .bulk-action-errors-collapsed .toggle-indicator:before {
     3006.bulk-action-notice .bulk-action-errors-collapsed .toggle-indicator:before,
     3007.privacy-text-box.closed .toggle-indicator:before {
    30063008    content: "\f140";
    30073009}
  • trunk/src/wp-admin/css/edit.css

    r42641 r42980  
    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
  • trunk/src/wp-admin/includes/admin-filters.php

    r42967 r42980  
    133133add_action( 'upgrader_process_complete', 'wp_update_plugins', 10, 0 );
    134134add_action( 'upgrader_process_complete', 'wp_update_themes', 10, 0 );
     135
     136// Privacy policy text changes check.
     137add_action( 'admin_init', array( 'WP_Privacy_Policy_Content', 'text_change_check' ), 20 );
     138
     139// Show a "postbox" with the text suggestions for a privacy policy.
     140add_action( 'edit_form_after_title', array( 'WP_Privacy_Policy_Content', 'privacy_policy_postbox' ) );
     141
     142// Add the suggested policy text from WordPress.
     143add_action( 'admin_init', array( 'WP_Privacy_Policy_Content', 'add_suggested_content' ), 15 );
     144
     145// Stop checking for text changes after the policy page is updated.
     146add_action( 'post_updated', array( 'WP_Privacy_Policy_Content', '_policy_page_updated' ) );
     147
  • trunk/src/wp-admin/includes/misc.php

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

    r42875 r42980  
    20152015    include( WP_PLUGIN_DIR . '/' . $plugin );
    20162016}
     2017
     2018/**
     2019 * Helper function for adding plugin specific information to the postbox shown when editing the privacy policy.
     2020 *
     2021 * Intended for use with the `'admin_init'` action.
     2022 *
     2023 * @since 4.9.6
     2024 *
     2025 * @param string $plugin_name The plugin'as name. Will be shown in the privacy policy metabox.
     2026 * @param string $policy_text The content that should appear in the site's privacy policy.
     2027 *                            For more information see the Plugins Handbook https://developer.wordpress.org/plugins/.
     2028 */
     2029function wp_add_privacy_policy_content( $plugin_name, $policy_text ) {
     2030    if ( ! class_exists( 'WP_Privacy_Policy_Content' ) ) {
     2031        require_once( ABSPATH . 'wp-admin/includes/misc.php' );
     2032    }
     2033
     2034    WP_Privacy_Policy_Content::add( $plugin_name, $policy_text );
     2035}
  • trunk/src/wp-admin/js/post.js

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

    r42888 r42980  
    553553add_filter( 'user_has_cap', 'wp_maybe_grant_install_languages_cap', 1 );
    554554
     555// Trigger the check for policy text changes after active plugins change.
     556add_action( 'update_site_option_active_sitewide_plugins', '_wp_privacy_active_plugins_change' );
     557add_action( 'update_option_active_plugins', '_wp_privacy_active_plugins_change' );
     558
    555559unset( $filter, $action );
  • trunk/src/wp-includes/functions.php

    r42971 r42980  
    62466246    return apply_filters( 'wp_privacy_anonymize_data', $anonymous, $type, $data );
    62476247}
     6248
     6249/**
     6250 * Trigger the check for policy text changes.
     6251 *
     6252 * @since 4.9.6
     6253 * @access private
     6254 */
     6255function _wp_privacy_active_plugins_change() {
     6256    update_option( '_wp_privacy_text_change_check', 'check' );
     6257}
  • trunk/src/wp-includes/link-template.php

    r42828 r42980  
    42744274    return apply_filters( 'parent_theme_file_path', $path, $file );
    42754275}
     4276
     4277/**
     4278 * Retrieves the URL to the privacy policy page.
     4279 *
     4280 * @since 4.9.6
     4281 *
     4282 * @return string The URL to the privacy policy page. Empty string if it doesn't exist.
     4283 */
     4284function get_privacy_policy_url() {
     4285    $policy_page_id = (int) get_option( 'wp_page_for_privacy_policy' );
     4286
     4287    if ( empty( $policy_page_id ) ) {
     4288        return '';
     4289    }
     4290
     4291    return get_permalink( $policy_page_id );
     4292}
Note: See TracChangeset for help on using the changeset viewer.