WordPress.org

Make WordPress Core

Ticket #37974: 37974.18.diff

File 37974.18.diff, 53.6 KB (added by bradyvercher, 3 years ago)
  • src/wp-admin/css/customize-controls.css

     
    687687
    688688/* Style for custom settings */
    689689
     690#customize-control-front_page_sections {
     691        border-top: 1px solid #ddd;
     692        margin-top: 8px;
     693        padding-top: 16px;
     694}
     695
    690696/**
    691697 * Dropdowns
    692698 */
     
    12051211 */
    12061212
    12071213/* higher specificity than .wp-core-ui .button */
     1214#customize-theme-controls .add-new-item,
    12081215#customize-theme-controls .add-new-widget,
    12091216#customize-theme-controls .add-new-menu-item {
    12101217        cursor: pointer;
     
    12191226        outline: none;
    12201227}
    12211228
     1229.reordering .add-new-item,
    12221230.reordering .add-new-widget,
    12231231.reordering .add-new-menu-item {
    12241232        opacity: 0.2;
     
    12261234        cursor: not-allowed; /* doesn't work in conjunction with pointer-events */
    12271235}
    12281236
     1237.add-new-item:before,
    12291238.add-new-widget:before,
    12301239.add-new-menu-item:before,
    12311240#available-menu-items .new-content-item .add-content:before {
     
    12821291        color: #00a0d2;
    12831292}
    12841293
     1294.wp-reorder-nav {
     1295        display: none;
     1296        background-color: #fff;
     1297        position: absolute;
     1298        top: 0;
     1299        right: 0;
     1300}
     1301
     1302.reordering .wp-reorder-nav,
     1303.wp-reorder-nav.is-active {
     1304        display: block;
     1305}
     1306
     1307.wp-reorder-nav button,
    12851308.widget-reorder-nav span,
    12861309.menu-item-reorder-nav button {
    12871310        position: relative;
     
    12961319        outline: none;
    12971320}
    12981321
     1322.wp-reorder-nav button,
    12991323.menu-item-reorder-nav button {
    13001324        width: 30px;
    13011325        height: 40px;
     
    13051329        box-shadow: none;
    13061330}
    13071331
     1332.wp-reorder-nav button:before,
    13081333.widget-reorder-nav span:before,
    13091334.menu-item-reorder-nav button:before {
    13101335        display: inline-block;
     
    13201345        -moz-osx-font-smoothing: grayscale;
    13211346}
    13221347
     1348.wp-reorder-nav button:hover,
     1349.wp-reorder-nav button:focus,
    13231350.widget-reorder-nav span:hover,
    13241351.widget-reorder-nav span:focus,
    13251352.menu-item-reorder-nav button:hover,
     
    13281355        background: #eee;
    13291356}
    13301357
     1358.wp-reorder-nav button {
     1359        width: 33px;
     1360        height: 38px;
     1361}
     1362
     1363.wp-reorder-nav button:before {
     1364        font: normal 20px/38px dashicons;
     1365}
     1366
     1367.move-item-down:before,
    13311368.move-widget-down:before,
    13321369.menus-move-down:before {
    13331370        content: "\f347";
    13341371}
    13351372
     1373.move-item-up:before,
    13361374.move-widget-up:before,
    13371375.menus-move-up:before {
    13381376        content: "\f343";
     
    13431381.move-up-disabled .menus-move-up,
    13441382.move-down-disabled .menus-move-down,
    13451383.move-right-disabled .menus-move-right,
    1346 .move-left-disabled .menus-move-left {
     1384.move-left-disabled .menus-move-left,
     1385.wp-item:first-child .move-item-up,
     1386.wp-item:last-child .move-item-down {
    13471387        color: #d5d5d5;
    13481388        background-color: #fff;
    13491389        cursor: default;
     
    13511391}
    13521392
    13531393/**
    1354  * New widget and Add-menu-items modes and panels
     1394 * New widget, Add-menu-items, and Drawer modes and panels
    13551395 */
    13561396
    13571397.wp-full-overlay-main {
     
    13591399        width: 100%;
    13601400}
    13611401
     1402.customize-control.is-drawer-open .add-new-item,
     1403.customize-control.is-drawer-open .add-new-item:hover,
    13621404body.adding-widget .add-new-widget,
    13631405body.adding-widget .add-new-widget:hover,
    13641406.adding-menu-items .add-new-menu-item,
     
    13721414        box-shadow: inset 0 2px 5px -3px rgba(0, 0, 0, 0.5);
    13731415}
    13741416
     1417.customize-control.is-drawer-open .add-new-item:before,
    13751418body.adding-widget .add-new-widget:before,
    13761419.adding-menu-items .add-new-menu-item:before,
    13771420#accordion-section-add_menu .add-new-menu-item.open:before {
     
    13801423        transform: rotate(45deg);
    13811424}
    13821425
     1426.customize-drawer,
    13831427#available-widgets,
    13841428#available-menu-items {
    13851429        position: absolute;
     
    13981442        border-right: 1px solid #ddd;
    13991443}
    14001444
     1445.customize-drawer .customize-section-title,
    14011446#available-widgets .customize-section-title,
    14021447#available-menu-items .customize-section-title {
    14031448        display: none;
     
    14251470}
    14261471
    14271472/* search field container */
     1473.search-group,
    14281474#available-widgets-filter,
    14291475#available-menu-items-search .accordion-section-title {
    14301476        padding: 13px 15px;
     
    14331479        box-sizing: border-box;
    14341480}
    14351481
     1482.search-group {
     1483        position: relative;
     1484}
     1485
     1486.search-group input,
    14361487#available-widgets-filter input,
    14371488#available-menu-items-search input {
    14381489        width: 100%;
     
    14411492        padding: 6px 30px;
    14421493}
    14431494
     1495.search-group input::-ms-clear,
    14441496#available-widgets-filter input::-ms-clear,
    14451497#available-menu-items-search input::-ms-clear {
    14461498        display: none; /* remove the "x" in IE, which conflicts with the "x" icon on button.clear-results */
    14471499}
    14481500
     1501.search-group .search-icon,
    14491502#available-menu-items-search .search-icon,
    14501503#available-widgets-filter .search-icon {
    14511504        display: block;
     
    14591512        color: #72777c;
    14601513}
    14611514
     1515.search-group .clear-results,
    14621516#available-widgets-filter .clear-results,
    14631517#available-menu-items-search .clear-results {
    14641518        position: absolute;
     
    14751529        outline: 0;
    14761530}
    14771531
     1532.search-group .clear-results,
    14781533#available-widgets-filter .clear-results,
    14791534#available-menu-items-search .clear-results,
    14801535#available-menu-items-search.loading .clear-results.is-visible {
     
    14811536        display: none;
    14821537}
    14831538
     1539.search-group .clear-results.is-visible,
    14841540#available-widgets-filter .clear-results.is-visible,
    14851541#available-menu-items-search .clear-results.is-visible {
    14861542        display: block;
    14871543}
    14881544
     1545.search-group .clear-results:before,
    14891546#available-widgets-filter .clear-results:before,
    14901547#available-menu-items-search .clear-results:before {
    14911548        content: "\f335";
     
    14951552        -moz-osx-font-smoothing: grayscale;
    14961553}
    14971554
     1555.search-group .clear-results:hover,
     1556.search-group .clear-results:focus,
    14981557#available-widgets-filter .clear-results:hover,
    14991558#available-widgets-filter .clear-results:focus,
    15001559#available-menu-items-search .clear-results:hover,
     
    15021561        color: #dc3232;
    15031562}
    15041563
     1564.search-group .clear-results:focus,
    15051565#available-widgets-filter .clear-results:focus,
    15061566#available-menu-items-search .clear-results:focus {
    15071567        -webkit-box-shadow:
     
    15121572                0 0 2px 1px rgba(30, 140, 190, .8);
    15131573}
    15141574
     1575.search-group .search-icon:after,
    15151576#available-menu-items-search .search-icon:after,
    15161577#available-widgets-filter .search-icon:after {
    15171578        content: "\f179";
     
    15211582        -moz-osx-font-smoothing: grayscale;
    15221583}
    15231584
     1585.search-group .spinner {
     1586        margin: 0;
     1587        position: absolute;
     1588        top: 20px;
     1589        right: 20px;
     1590}
     1591
     1592.search-group.is-searching .clear-results {
     1593        display: none;
     1594}
     1595
    15241596.no-widgets-found-message {
    15251597        display: none;
    15261598        margin: 0;
     
    15581630        position: static;
    15591631}
    15601632
     1633.customize-drawer.is-open {
     1634        left: 0;
     1635        visibility: visible;
     1636}
    15611637
     1638body.drawer-is-open .wp-full-overlay-main {
     1639        left: 300px;
     1640}
     1641
     1642body.drawer-is-open #customize-preview {
     1643        opacity: 0.4;
     1644}
     1645
     1646.ios .customize-drawer {
     1647        -webkit-transition: left 0s;
     1648        transition: left 0s;
     1649}
     1650
     1651.customize-drawer-notice {
     1652        padding: 15px;
     1653}
     1654
     1655/* Sortable list items. */
     1656
     1657.wp-items-list {
     1658        list-style: none;
     1659        margin: 0 0 10px 0;
     1660        padding: 0;
     1661        position: relative;
     1662}
     1663
     1664.wp-item {
     1665        background: #fff;
     1666        margin: -1px 0 0 0;
     1667        padding: 0;
     1668}
     1669
     1670.wp-item-header {
     1671        border: 1px solid #dfdfdf;
     1672        background: #fff;
     1673        position: relative;
     1674}
     1675
     1676.wp-item-delete {
     1677        color: #a00;
     1678        height: 38px;
     1679        position: absolute;
     1680        top: 0;
     1681        right: 0;
     1682        text-align: center;
     1683        vertical-align: middle;
     1684        width: 33px;
     1685}
     1686
     1687.wp-item-delete:hover {
     1688        color: #f00;
     1689}
     1690
     1691.wp-item-delete:before {
     1692        content: "\f335";
     1693}
     1694
     1695.wp-item-title {
     1696        cursor: move;
     1697        margin: 0;
     1698        padding: 10px 20px;
     1699        position: relative;
     1700        word-wrap: break-word;
     1701}
     1702
     1703/* Corner knockout to account for controls */
     1704.wp-item-title:before {
     1705        content: "";
     1706        float: right;
     1707        height: 28px;
     1708        width: 79px;
     1709}
     1710
     1711.wp-item .wp-reorder-nav {
     1712        right: 33px;
     1713}
     1714
     1715.wp-item.ui-sortable-helper {
     1716        background: #f9f9f9;
     1717        border: 1px solid #dfdfdf;
     1718}
     1719
     1720.wp-item.ui-sortable-placeholder {
     1721        background: transparent;
     1722        border: 1px dashed #a0a5aa;
     1723        margin-top: 0;
     1724        margin-bottom: 1px;
     1725}
     1726
     1727.wp-item:hover .wp-item-header {
     1728        border-color: #999;
     1729        z-index: 1;
     1730}
     1731
     1732.wp-item.hide-delete .wp-item-delete {
     1733        display: none;
     1734}
     1735
     1736.wp-item.hide-delete .wp-reorder-nav {
     1737        right: 0;
     1738}
     1739
     1740/* Corner knockout to account for controls */
     1741.wp-item.hide-delete .wp-item-title:before {
     1742        width: 46px;
     1743}
     1744
     1745/* Search results. */
     1746
     1747.search-results {
     1748        padding: 1px 0 15px;
     1749}
     1750
     1751.search-results ul {
     1752        margin: -1px 0 0;
     1753}
     1754
     1755.search-results-item {
     1756        background: #fff;
     1757        border-color: #ddd;
     1758        border-style: solid;
     1759        border-width: 1px 0;
     1760        clear: both;
     1761        cursor: pointer;
     1762        line-height: 10px;
     1763        margin: -1px 0 0 0;
     1764        padding: 10px 15px;
     1765        position: relative;
     1766}
     1767
     1768.search-results-item-title {
     1769        display: block;
     1770        font-size: 13px;
     1771        font-weight: 600;
     1772        line-height: 20px;
     1773        padding-left: 20px;
     1774        word-wrap: break-word;
     1775}
     1776
     1777.search-results-item-type {
     1778        color: #666;
     1779        float: right;
     1780        font-size: 12px;
     1781        line-height: 20px;
     1782        padding-left: 10px;
     1783        text-align: right;
     1784}
     1785
     1786.search-results-item-add {
     1787        color: #82878c;
     1788        height: 38px;
     1789        position: absolute;
     1790        top: 1px;
     1791        left: 1px;
     1792        width: 30px;
     1793}
     1794
     1795.search-results-item-add:before {
     1796        -webkit-border-radius: 50%;
     1797        border-radius: 50%;
     1798        content: "\f543";
     1799        height: 20px;
     1800        position: relative;
     1801        top: 0;
     1802        left: 2px;
     1803}
     1804
     1805.search-results-item:hover {
     1806        border-color: #999;
     1807        color: #0073aa;
     1808        z-index: 1;
     1809}
     1810
     1811.search-results-item:hover .search-results-item-add:before {
     1812        color: #0073aa;
     1813}
     1814
     1815.search-results-item.is-selected .search-results-item-add:before {
     1816        content: "\f147";
     1817}
     1818
     1819.search-results-item-add,
     1820.wp-item-delete {
     1821        cursor: pointer;
     1822        display: inline-block;
     1823        font-family: dashicons;
     1824        font-size: 20px;
     1825        -webkit-font-smoothing: antialiased;
     1826        font-style: normal;
     1827        font-weight: normal;
     1828        line-height: 1;
     1829        text-align: center;
     1830        text-decoration: inherit;
     1831        vertical-align: top;
     1832}
     1833
     1834.search-results.hide-type-label .search-results-item-type {
     1835        display: none;
     1836}
     1837
     1838
    15621839/* Responsive */
    15631840.customize-controls-preview-toggle {
    15641841        display: none;
     
    16791956                margin-top: 6px;
    16801957        }
    16811958
     1959        body.drawer-is-open .customize-drawer,
    16821960        body.adding-widget div#available-widgets,
    16831961        body.adding-menu-items div#available-menu-items {
    16841962                top: 46px;
     
    16871965                width: 100%;
    16881966        }
    16891967
     1968        .customize-drawer .customize-section-title,
    16901969        #available-widgets .customize-section-title,
    16911970        #available-menu-items .customize-section-title {
    16921971                display: block;
     
    16931972                margin: 0;
    16941973        }
    16951974
     1975        .customize-drawer .customize-section-back,
    16961976        #available-widgets .customize-section-back,
    16971977        #available-menu-items .customize-section-back {
    16981978                height: 69px;
    16991979        }
    17001980
     1981        .customize-drawer .customize-section-title h3,
    17011982        #available-widgets .customize-section-title h3,
    17021983        #available-menu-items .customize-section-title h3 {
    17031984                font-size: 20px;
     
    17121993                text-overflow: ellipsis;
    17131994        }
    17141995
     1996        .customize-drawer .customize-section-title .customize-action,
    17151997        #available-widgets .customize-section-title .customize-action,
    17161998        #available-menu-items .customize-section-title .customize-action {
    17171999                font-size: 13px;
  • src/wp-admin/includes/ajax-actions.php

     
    17511751function wp_ajax_find_posts() {
    17521752        check_ajax_referer( 'find-posts' );
    17531753
    1754         $post_types = get_post_types( array( 'public' => true ), 'objects' );
    1755         unset( $post_types['attachment'] );
     1754        $post_types = array();
     1755        if ( empty( $_POST['post_types'] ) ) {
     1756                $post_types = get_post_types( array( 'public' => true ), 'objects' );
     1757                unset( $post_types['attachment'] );
     1758        } else {
     1759                $post_type_names = array_map( 'sanitize_text_field', wp_unslash( $_POST['post_types'] ) );
     1760                foreach ( $post_type_names as $post_type ) {
     1761                        $post_types[ $post_type ] = get_post_type_object( $post_type );
     1762                }
     1763        }
    17561764
    1757         $s = wp_unslash( $_POST['ps'] );
    17581765        $args = array(
    1759                 'post_type' => array_keys( $post_types ),
    1760                 'post_status' => 'any',
     1766                'post_type'      => array_keys( $post_types ),
     1767                'post_status'    => isset( $_POST['status'] ) ? sanitize_text_field( $_POST['status'] ) : 'any',
    17611768                'posts_per_page' => 50,
    17621769        );
    1763         if ( '' !== $s )
    1764                 $args['s'] = $s;
    17651770
     1771        if ( ! empty( $_POST['ps'] ) ) {
     1772                $args['s'] = wp_unslash( $_POST['ps'] );
     1773        }
     1774
    17661775        $posts = get_posts( $args );
    17671776
    17681777        if ( ! $posts ) {
     
    17691778                wp_send_json_error( __( 'No items found.' ) );
    17701779        }
    17711780
     1781        if ( isset( $_POST['format'] ) && 'json' === $_POST['format'] ) {
     1782                foreach ( $posts as $post ) {
     1783                        $data[] = array(
     1784                                'id'    => $post->ID,
     1785                                'title' => $post->post_title,
     1786                                'type'  => $post_types[ $post->post_type ]->labels->singular_name,
     1787                        );
     1788                }
     1789
     1790                wp_send_json_success( $data );
     1791        }
     1792
    17721793        $html = '<table class="widefat"><thead><tr><th class="found-radio"><br /></th><th>'.__('Title').'</th><th class="no-break">'.__('Type').'</th><th class="no-break">'.__('Date').'</th><th class="no-break">'.__('Status').'</th></tr></thead><tbody>';
    17731794        $alt = '';
    17741795        foreach ( $posts as $post ) {
  • src/wp-admin/includes/post.php

     
    12851285
    12861286                /** This filter is documented in wp-admin/edit-tag-form.php */
    12871287                $uri = apply_filters( 'editable_slug', $uri, $post );
    1288                 if ( !empty($uri) )
    1289                         $uri .= '/';
     1288                if ( ! empty( $uri ) ) {
     1289                        $uri .= is_front_page_section( $post->ID ) ? '.' : '/';
     1290                }
    12901291                $permalink = str_replace('%pagename%', "{$uri}%pagename%", $permalink);
    12911292        }
    12921293
  • src/wp-admin/includes/template.php

     
    17121712        if ( 'page' === get_option( 'show_on_front' ) ) {
    17131713                if ( intval( get_option( 'page_on_front' ) ) === $post->ID ) {
    17141714                        $post_states['page_on_front'] = __( 'Front Page' );
     1715                } elseif ( in_array( $post->ID, $front_page_sections = array_filter( wp_parse_id_list( get_option( 'front_page_sections' ) ) ), true ) ) {
     1716                        $post_states['front_page_section'] = __( 'Front Page Section' );
    17151717                }
    17161718
    17171719                if ( intval( get_option( 'page_for_posts' ) ) === $post->ID ) {
  • src/wp-admin/js/customize-post-collection.js

     
     1(function( wp, $ ) {
     2
     3        if ( ! wp || ! wp.customize ) { return; }
     4
     5        var api = wp.customize;
     6
     7        api.PostCollection = api.PostCollection || {};
     8
     9        api.PostModel = Backbone.Model.extend({
     10                defaults: {
     11                        sortableOrder: 0,
     12                        title: ''
     13                }
     14        });
     15
     16        api.PostsCollection = Backbone.Collection.extend({
     17                model: api.PostModel,
     18                comparator: 'sortableOrder'
     19        });
     20
     21        api.Drawer = api.Class.extend({
     22                type: 'drawer',
     23
     24                initialize: function( id, options ) {
     25                        var drawer = this;
     26
     27                        _.extend( this, options || {} );
     28                        this.id = id;
     29
     30                        _.bindAll( this, 'collapseOtherDrawers' );
     31                        this.container = $( '<div class="customize-drawer" />' );
     32
     33                        this.deferred = {
     34                                embedded: new $.Deferred()
     35                        };
     36
     37                        this.control = new api.Value();
     38                        this.control.set( options.control );
     39
     40                        this.expanded = new api.Value();
     41                        this.expanded.set( false );
     42                        this.expanded.bind( this.collapseOtherDrawers );
     43
     44                        drawer.embed();
     45                        drawer.deferred.embedded.done(function () {
     46                                drawer.ready();
     47                        });
     48
     49                        // Collapse the drawer when the control's section is collapsed.
     50                        api.control( this.control(), function( control ) {
     51                                api.section( control.section() ).expanded.bind(function( isExpanded ) {
     52                                        if ( ! isExpanded ) {
     53                                                drawer.collapse();
     54                                        }
     55                                });
     56                        });
     57                },
     58
     59                embed: function () {
     60                        $( '.wp-full-overlay' ).append( this.container );
     61
     62                        this.view = new wp.Backbone.View({
     63                                el: this.container
     64                        });
     65
     66                        this.view.views.add(
     67                                new this.TitleView({
     68                                        drawer: this
     69                                })
     70                        );
     71
     72                        this.deferred.embedded.resolve();
     73                },
     74
     75                ready: function() {},
     76
     77                collapse: function() {
     78                        this.expanded.set( false );
     79                        this.container.removeClass( 'is-open' );
     80                        $( document.body ).removeClass( 'drawer-is-open' );
     81                        api.control( this.control() ).container.removeClass( 'is-drawer-open' );
     82                },
     83
     84                expand: function() {
     85                        this.expanded.set( true );
     86                        this.container.addClass( 'is-open' );
     87                        $( document.body ).addClass( 'drawer-is-open' );
     88                        api.control( this.control() ).container.addClass( 'is-drawer-open' );
     89                },
     90
     91                toggle: function() {
     92                        if ( this.expanded() ) {
     93                                this.collapse();
     94                        } else {
     95                                this.expand();
     96                        }
     97                },
     98
     99                collapseOtherDrawers: function( isExpanded ) {
     100                        if ( isExpanded ) {
     101                                api.drawer.each(function( drawer ) {
     102                                        if ( drawer.id !== this.id ) {
     103                                                drawer.collapse();
     104                                        }
     105                                }, this );
     106
     107                                if ( this.expanded() ) {
     108                                        $( document.body ).addClass( 'drawer-is-open' );
     109                                }
     110                        }
     111                },
     112
     113                TitleView: wp.Backbone.View.extend({
     114                        className: 'customize-drawer-title customize-section-title',
     115                        template: wp.template( 'customize-drawer-title' ),
     116
     117                        events: {
     118                                'click .customize-section-back': 'collapseDrawer'
     119                        },
     120
     121                        initialize: function( options ) {
     122                                this.drawer = options.drawer;
     123                        },
     124
     125                        render: function() {
     126                                this.$el.html( this.template( this.drawer.labels ) );
     127                                return this;
     128                        },
     129
     130                        collapseDrawer: function( e ) {
     131                                e.preventDefault();
     132                                this.drawer.collapse();
     133                        }
     134                })
     135        });
     136
     137        api.PostSearchDrawer = api.Drawer.extend({
     138                type: 'post-search-drawer',
     139
     140                ready: function() {
     141                        var drawer = this;
     142
     143                        this.results = new api.PostsCollection();
     144
     145                        this.state = new Backbone.Model({
     146                                notice: ''
     147                        });
     148
     149                        this.view.views.add([
     150                                new this.SearchFormView({
     151                                        collection: this.results,
     152                                        drawer: this
     153                                }),
     154                                new this.NoticeView({
     155                                        drawer: this
     156                                }),
     157                                new this.SearchResultsView({
     158                                        collection: this.results,
     159                                        drawer: this,
     160                                        selection: this.selection
     161                                })
     162                        ]);
     163
     164                        this.expanded.bind(function( isExpanded ) {
     165                                if ( isExpanded && drawer.results.length < 1 ) {
     166                                        drawer.search();
     167                                }
     168                        });
     169                },
     170
     171                search: function( query ) {
     172                        var drawer = this;
     173
     174                        return wp.ajax.post( 'find_posts', {
     175                                ps: query,
     176                                post_types: this.postTypes,
     177                                post_status: 'publish',
     178                                format: 'json',
     179                                _ajax_nonce: this.searchNonce
     180                        }).done(function( response ) {
     181                                drawer.results.reset( response );
     182                                drawer.state.set( 'notice', '' );
     183                        }).fail(function( response ) {
     184                                drawer.results.reset();
     185                                drawer.state.set( 'notice', response );
     186                        });
     187                },
     188
     189                NoticeView: wp.Backbone.View.extend({
     190                        tagName: 'div',
     191                        className: 'customize-drawer-notice',
     192
     193                        initialize: function( options ) {
     194                                this.drawer = options.drawer;
     195                                this.listenTo( this.drawer.state, 'change:notice', this.render );
     196                        },
     197
     198                        render: function() {
     199                                var notice = this.drawer.state.get( 'notice' );
     200                                this.$el.toggle( !! notice.length ).text( notice );
     201                                return this;
     202                        }
     203                }),
     204
     205                SearchFormView: wp.Backbone.View.extend({
     206                        tagName: 'div',
     207                        className: 'search-group',
     208                        template: wp.template( 'search-group' ),
     209
     210                        events: {
     211                                'click .clear-results' : 'clearResults',
     212                                'input input': 'search'
     213                        },
     214
     215                        initialize: function( options ) {
     216                                this.collection = options.collection;
     217                                this.drawer = options.drawer;
     218
     219                                this.listenTo( this.collection, 'add remove reset', this.updateClearResultsVisibility );
     220                        },
     221
     222                        render: function() {
     223                                this.$el.html( this.template({ labels: this.drawer.labels }) );
     224                                this.$clearResults = this.$( '.clear-results' );
     225                                this.$field = this.$( '.search-group-field' );
     226                                this.$spinner = this.$el.append( '<span class="search-group-spinner spinner" />' ).find( '.spinner' );
     227                                this.updateClearResultsVisibility();
     228                                return this;
     229                        },
     230
     231                        clearResults: function() {
     232                                this.collection.reset();
     233                                this.$field.val( '' ).trigger( 'input' ).focus();
     234                        },
     235
     236                        search: function() {
     237                                var view = this;
     238
     239                                this.$el.addClass( 'is-searching' );
     240                                this.$spinner.addClass( 'is-active' );
     241
     242                                clearTimeout( this.timeout );
     243                                this.timeout = setTimeout(function() {
     244                                        view.drawer.search( view.$field.val() )
     245                                                .always(function() {
     246                                                        view.$el.removeClass( 'is-searching' );
     247                                                        view.$spinner.removeClass( 'is-active' );
     248                                                });
     249                                }, 300 );
     250                        },
     251
     252                        updateClearResultsVisibility: function() {
     253                                this.$clearResults.toggleClass( 'is-visible', !! this.collection.length && '' !== this.$field.val() );
     254                        }
     255                }),
     256
     257                SearchResultsView: wp.Backbone.View.extend({
     258                        tagName: 'div',
     259                        className: 'search-results',
     260
     261                        initialize: function( options ) {
     262                                this.collection = options.collection;
     263                                this.drawer = options.drawer;
     264                                this.selection = options.selection;
     265
     266                                this.listenTo( this.collection, 'reset', this.render );
     267                        },
     268
     269                        render: function() {
     270                                this.$list = this.$el.html( '<ul />' ).find( 'ul' );
     271                                this.$el.toggleClass( 'hide-type-label', 1 === this.drawer.postTypes.length );
     272
     273                                if ( this.collection.length ) {
     274                                        this.collection.each( this.addItem, this );
     275                                } else {
     276                                        this.$el.empty();
     277                                }
     278
     279                                return this;
     280                        },
     281
     282                        addItem: function( model ) {
     283                                this.views.add( 'ul', new this.drawer.SearchResultView({
     284                                        drawer: this.drawer,
     285                                        model: model,
     286                                        selection: this.selection
     287                                }));
     288                        }
     289                }),
     290
     291                SearchResultView: wp.Backbone.View.extend({
     292                        tagName: 'li',
     293                        className: 'search-results-item',
     294                        template: wp.template( 'search-result' ),
     295
     296                        events: {
     297                                'click': 'addItem'
     298                        },
     299
     300                        initialize: function( options ) {
     301                                this.drawer = options.drawer;
     302                                this.model = options.model;
     303                                this.selection = options.selection;
     304
     305                                this.listenTo( this.selection, 'add remove reset', this.updateSelectedClass );
     306                        },
     307
     308                        render: function() {
     309                                var data = _.extend( this.model.toJSON(), {
     310                                        labels: this.drawer.labels
     311                                });
     312
     313                                this.$el.html( this.template( data ) );
     314                                this.updateSelectedClass();
     315
     316                                return this;
     317                        },
     318
     319                        addItem: function() {
     320                                this.selection.add( this.model );
     321                        },
     322
     323                        updateSelectedClass: function() {
     324                                this.$el.toggleClass( 'is-selected', !! this.selection.get( this.model.id ) );
     325                        }
     326                })
     327        });
     328
     329        api.PostCollectionControl = api.Control.extend({
     330                ready: function() {
     331                        var control = this;
     332
     333                        this.posts = new api.PostsCollection( this.params.posts );
     334                        delete this.params.posts;
     335
     336                        this.drawer = new api.PostSearchDrawer( this.id, {
     337                                control: this.id,
     338                                labels: _.extend( this.params.labels, {
     339                                        customizeAction: this.params.labels.addPosts,
     340                                        title: this.params.label
     341                                }),
     342                                postTypes: this.params.postTypes,
     343                                searchNonce: this.params.searchNonce,
     344                                selection: this.posts
     345                        });
     346                        api.drawer.add( this.id, this.drawer );
     347
     348                        // Update the setting when the post collection is modified.
     349                        this.posts.on( 'add remove reset sort', function() {
     350                                var ids = this.posts.pluck( 'id' );
     351
     352                                if ( this.setting() !== ids ) {
     353                                        this.setting.set( ids );
     354                                }
     355                        }, this );
     356
     357                        if ( this.params.includeFrontPage ) {
     358                                // Add the front page when it changes.
     359                                api( 'page_on_front', function( setting ) {
     360                                        setting.bind( _.bind( control.onPageOnFrontChange, control ) );
     361                                });
     362                        }
     363
     364                        this.view = new wp.Backbone.View({
     365                                el: this.container
     366                        });
     367
     368                        this.view.views.add([
     369                                new this.ListView({
     370                                        collection: this.posts,
     371                                        control: this
     372                                }),
     373                                new this.AddNewItemButtonView({
     374                                        control: this
     375                                })
     376                        ]);
     377                },
     378
     379                onPageOnFrontChange: function( value ) {
     380                        var id = parseInt( value, 10 ),
     381                                posts = this.posts.toJSON(),
     382                                pageOnFrontControl = api.control( 'page_on_front' );
     383
     384                        if ( id > 1 && ! this.posts.findWhere({ id: id }) ) {
     385                                posts.unshift({
     386                                        id: id,
     387                                        // @todo Find a better way to grab this title.
     388                                        title: pageOnFrontControl.container.find( 'option:selected' ).text()
     389                                });
     390                        }
     391
     392                        // Remove the previous front page if it was the only post in the list.
     393                        if ( 2 === posts.length ) {
     394                                posts = posts.shift();
     395                        }
     396
     397                        // Reset the collection to re-render the view.
     398                        this.posts.reset( posts );
     399                },
     400
     401                AddNewItemButtonView: wp.Backbone.View.extend({
     402                        className: 'add-new-item button button-secondary alignright',
     403                        tagName: 'button',
     404
     405                        events: {
     406                                click: 'toggleDrawer'
     407                        },
     408
     409                        initialize: function( options ) {
     410                                this.control = options.control;
     411                        },
     412
     413                        render: function() {
     414                                this.$el.text( this.control.params.labels.addPosts );
     415                                return this;
     416                        },
     417
     418                        toggleDrawer: function( e ) {
     419                                e.preventDefault();
     420                                this.control.drawer.toggle();
     421                        }
     422                }),
     423
     424                ListView: wp.Backbone.View.extend({
     425                        className: 'wp-items-list',
     426                        tagName: 'ol',
     427
     428                        initialize: function( options ) {
     429                                var view = this;
     430
     431                                this.control = options.control;
     432
     433                                this.listenTo( this.collection, 'add', this.addItem );
     434                                this.listenTo( this.collection, 'add remove', this.updateOrder );
     435                                this.listenTo( this.collection, 'reset', this.render );
     436                        },
     437
     438                        render: function() {
     439                                this.$el.empty();
     440                                this.collection.each( this.addItem, this );
     441                                this.initializeSortable();
     442                                return this;
     443                        },
     444
     445                        initializeSortable: function() {
     446                                this.$el.sortable({
     447                                        axis: 'y',
     448                                        delay: 150,
     449                                        forceHelperSize: true,
     450                                        forcePlaceholderSize: true,
     451                                        opacity: 0.6,
     452                                        start: function( e, ui ) {
     453                                                ui.placeholder.css( 'visibility', 'visible' );
     454                                        },
     455                                        update: _.bind(function() {
     456                                                this.updateOrder();
     457                                        }, this )
     458                                });
     459                        },
     460
     461                        addItem: function( item ) {
     462                                var itemView = new this.control.ItemView({
     463                                        control: this.control,
     464                                        model: item,
     465                                        parent: this
     466                                });
     467
     468                                this.$el.append( itemView.render().el );
     469                        },
     470
     471                        moveDown: function( model ) {
     472                                var index = this.collection.indexOf( model ),
     473                                        $items = this.$el.children();
     474
     475                                if ( index < this.collection.length - 1 ) {
     476                                        $items.eq( index ).insertAfter( $items.eq( index + 1 ) );
     477                                        this.updateOrder();
     478                                        wp.a11y.speak( this.control.params.labels.movedDown );
     479                                }
     480                        },
     481
     482                        moveUp: function( model ) {
     483                                var index = this.collection.indexOf( model ),
     484                                        $items = this.$el.children();
     485
     486                                if ( index > 0 ) {
     487                                        $items.eq( index ).insertBefore( $items.eq( index - 1 ) );
     488                                        this.updateOrder();
     489                                        wp.a11y.speak( this.control.params.labels.movedUp );
     490                                }
     491                        },
     492
     493                        updateOrder: function() {
     494                                _.each( this.$el.children(), function( item, index ) {
     495                                        var id = $( item ).data( 'post-id' );
     496                                        this.collection.get( id ).set( 'sortableOrder', index );
     497                                }, this );
     498
     499                                this.collection.sort();
     500                        }
     501                }),
     502
     503                ItemView: wp.Backbone.View.extend({
     504                        tagName: 'li',
     505                        className: 'wp-item',
     506                        template: wp.template( 'wp-item' ),
     507
     508                        events: {
     509                                'click .wp-item-delete': 'destroy',
     510                                'click .move-item-up': 'moveUp',
     511                                'click .move-item-down': 'moveDown'
     512                        },
     513
     514                        initialize: function( options ) {
     515                                this.control = options.control;
     516                                this.parent = options.parent;
     517                                this.listenTo( this.model, 'destroy', this.remove );
     518                        },
     519
     520                        render: function() {
     521                                var isFrontPage = this.model.get( 'id' ) == api( 'page_on_front' )(),
     522                                        canDelete = ! this.control.params.includeFrontPage || ! isFrontPage,
     523                                        data = _.extend( this.model.toJSON(), {
     524                                                labels: this.control.params.labels,
     525                                                includeFrontPage: this.control.params.includeFrontPage,
     526                                                showDeleteButton: canDelete
     527                                        });
     528
     529                                this.$el.html( this.template( data ) );
     530                                this.$el.data( 'post-id', this.model.get( 'id' ) );
     531
     532                                if ( ! canDelete ) {
     533                                        this.$el.addClass( 'hide-delete' );
     534                                }
     535
     536                                return this;
     537                        },
     538
     539                        moveDown: function( e ) {
     540                                e.preventDefault();
     541                                this.parent.moveDown( this.model );
     542                        },
     543
     544                        moveUp: function( e ) {
     545                                e.preventDefault();
     546                                this.parent.moveUp( this.model );
     547                        },
     548
     549                        /**
     550                         * Destroy the view's model.
     551                         *
     552                         * Avoid syncing to the server by triggering an event instead of
     553                         * calling destroy() directly on the model.
     554                         */
     555                        destroy: function() {
     556                                this.model.trigger( 'destroy', this.model );
     557                        },
     558
     559                        remove: function() {
     560                                this.$el.remove();
     561                        }
     562                })
     563        });
     564
     565        /**
     566         * Toggle the front page sections control based on front page settings.
     567         */
     568        function toggleFrontPageSectionsControl() {
     569                var controlId = 'front_page_sections',
     570                        showOnFront = api( 'show_on_front' )(),
     571                        pageOnFront = api( 'page_on_front' )(),
     572                        isVisible = 'page' === showOnFront && parseInt( pageOnFront ) > 0;
     573
     574                if ( api.control.has( controlId ) ) {
     575                        api.control( controlId ).container.toggle( isVisible );
     576                }
     577        }
     578
     579        /**
     580         * Create the collection for Drawers.
     581         */
     582        api.drawer = new api.Values({ defaultConstructor: api.Drawer });
     583
     584        /**
     585         * Extends wp.customize.controlConstructor with control constructor for
     586         * post_collection.
     587         */
     588        $.extend( api.controlConstructor, {
     589                post_collection: api.PostCollectionControl
     590        });
     591
     592        /**
     593         * Bind events to toggle visibilty of the front page sections control.
     594         */
     595        api.bind( 'ready', function() {
     596                api( 'show_on_front' ).bind( toggleFrontPageSectionsControl );
     597                api( 'page_on_front' ).bind( toggleFrontPageSectionsControl );
     598                api.section( 'static_front_page' ).expanded.bind( toggleFrontPageSectionsControl );
     599        });
     600
     601})( window.wp, jQuery );
  • src/wp-includes/canonical.php

     
    655655                exit;
    656656        }
    657657}
     658
     659/**
     660 * Redirect front page section permalinks to the anchor on the front page.
     661 *
     662 * @since 4.7.0
     663 */
     664function wp_redirect_front_page_sections() {
     665        $object_id = get_queried_object_id();
     666
     667        if (
     668                   is_front_page()
     669                || is_home()
     670                || ! is_singular()
     671                || ! is_front_page_section( $object_id )
     672        ) {
     673                return;
     674        }
     675
     676        wp_redirect( get_permalink( $object_id ) );
     677        exit;
     678}
  • src/wp-includes/class-wp-customize-manager.php

     
    227227                require_once( ABSPATH . WPINC . '/customize/class-wp-customize-nav-menu-name-control.php' );
    228228                require_once( ABSPATH . WPINC . '/customize/class-wp-customize-nav-menu-auto-add-control.php' );
    229229                require_once( ABSPATH . WPINC . '/customize/class-wp-customize-new-menu-control.php' );
     230                require_once( ABSPATH . WPINC . '/customize/class-wp-customize-post-collection-control.php' );
    230231
    231232                require_once( ABSPATH . WPINC . '/customize/class-wp-customize-nav-menus-panel.php' );
    232233
     
    19701971                $this->register_control_type( 'WP_Customize_Cropped_Image_Control' );
    19711972                $this->register_control_type( 'WP_Customize_Site_Icon_Control' );
    19721973                $this->register_control_type( 'WP_Customize_Theme_Control' );
     1974                $this->register_control_type( 'WP_Customize_Post_Collection_Control' );
    19731975
    19741976                /* Themes */
    19751977
     
    23022304                        'section' => 'static_front_page',
    23032305                        'type' => 'dropdown-pages',
    23042306                ) );
     2307
     2308                $this->add_setting( 'front_page_sections', array(
     2309                        'type'              => 'option',
     2310                        'capability'        => 'manage_options',
     2311                        'sanitize_callback' => array( $this, 'sanitize_id_list' ),
     2312                ) );
     2313
     2314                $this->add_control( new WP_Customize_Post_Collection_Control( $this, 'front_page_sections', array(
     2315                        'label'              => __( 'Front page sections' ),
     2316                        'description'        => '',
     2317                        'section'            => 'static_front_page',
     2318                        'settings'           => 'front_page_sections',
     2319                        'post_types'         => apply_filters( 'front_page_sections_post_types', array( 'page' ) ),
     2320                        'include_front_page' => true,
     2321                        'labels'             => array(
     2322                                'addPost'                => __( 'Add Section' ),
     2323                                'addPosts'               => __( 'Add Sections' ),
     2324                                'movedUp'                => __( 'Section moved up' ),
     2325                                'movedDown'              => __( 'Section moved down' ),
     2326                                'removePost'             => __( 'Remove Section' ),
     2327                                'searchPosts'            => __( 'Search Sections' ),
     2328                                'searchPostsPlaceholder' => __( 'Search sections&hellip;' ),
     2329                        ),
     2330                ) ) );
    23052331        }
    23062332
    23072333        /**
     
    23812407        public function _render_custom_logo_partial() {
    23822408                return get_custom_logo();
    23832409        }
     2410
     2411        /**
     2412         * Sanitization callback for lists of IDs.
     2413         *
     2414         * @since 4.7.0
     2415         *
     2416         * @param string $value Setting value.
     2417         * @return string Comma-separated list of IDs.
     2418         */
     2419        public function sanitize_id_list( $value ) {
     2420                return implode( ',', array_unique( array_filter( wp_parse_id_list( $value ) ) ) );
     2421        }
    23842422}
  • src/wp-includes/class-wp-query.php

     
    487487        private $compat_methods = array( 'init_query_flags', 'parse_tax_query' );
    488488
    489489        /**
     490         * Whether we're currently showing a static front page with sections.
     491         *
     492         * @since 4.7.0
     493         * @access private
     494         * @var boolean
     495         */
     496        private $is_front_page_with_sections = false;
     497
     498        /**
    490499         * Resets query flags to false.
    491500         *
    492501         * The query flags are what page info WordPress was able to figure out.
     
    521530                $this->is_robots = false;
    522531                $this->is_posts_page = false;
    523532                $this->is_post_type_archive = false;
     533                $this->is_front_page_with_sections = false;
    524534        }
    525535
    526536        /**
     
    976986                                        $qv['page'] = $qv['paged'];
    977987                                        unset($qv['paged']);
    978988                                }
     989
     990                                // Add section pages, if they exist.
     991                                if ( $this->is_main_query() ) {
     992                                        $front_page_sections = array_filter( wp_parse_id_list( get_option( 'front_page_sections' ) ) );
     993                                        if ( $front_page_sections ) {
     994                                                $this->is_front_page_with_sections = true;
     995                                                if ( ! in_array( $qv['page_id'], $front_page_sections ) ) {
     996                                                        array_unshift( $front_page_sections, $qv['page_id'] );
     997                                                }
     998
     999                                                $qv['post__in'] = $front_page_sections;
     1000                                                $qv['orderby'] = 'post__in';
     1001                                        }
     1002                                }
    9791003                        }
    9801004                }
    9811005
     
    10071031                        }
    10081032                }
    10091033
    1010                 if ( $qv['page_id'] ) {
     1034                if ( ! $this->is_front_page_with_sections && ! empty( $qv['page_id'] ) ) {
    10111035                        if  ( 'page' == get_option('show_on_front') && $qv['page_id'] == get_option('page_for_posts') ) {
    10121036                                $this->is_page = false;
    10131037                                $this->is_home = true;
     
    19701994                        $where .= " AND {$wpdb->posts}.post_parent NOT IN ($post_parent__not_in)";
    19711995                }
    19721996
    1973                 if ( $q['page_id'] ) {
     1997                if ( ! $this->is_front_page_with_sections && $q['page_id'] ) {
    19741998                        if  ( ('page' != get_option('show_on_front') ) || ( $q['page_id'] != get_option('page_for_posts') ) ) {
    19751999                                $q['p'] = $q['page_id'];
    19762000                                $where = " AND {$wpdb->posts}.ID = " . $q['page_id'];
     
    30063030                        if ( $q['cache_results'] )
    30073031                                update_post_caches($this->posts, $post_type, $q['update_post_term_cache'], $q['update_post_meta_cache']);
    30083032
    3009                         $this->post = reset( $this->posts );
     3033                        if ( $this->is_front_page_with_sections ) {
     3034                                $this->post = reset( $this->posts );
     3035                                do {
     3036                                        if ( $this->post && $this->post->ID == get_option( 'page_on_front' ) ) {
     3037                                                reset( $this->posts );
     3038                                                break;
     3039                                        }
     3040                                } while ( $this->post = next( $this->posts ) );
     3041                                if ( ! $this->post ) {
     3042                                        $this->post = reset( $this->posts );
     3043                                }
     3044                        } else {
     3045                                $this->post = reset( $this->posts );
     3046                        }
    30103047                } else {
    30113048                        $this->post_count = 0;
    30123049                        $this->posts = array();
     
    31073144
    31083145                $post = $this->next_post();
    31093146                $this->setup_postdata( $post );
     3147
     3148                if ( $this->is_front_page_with_sections && $post ) {
     3149                        echo '<a id="' . str_replace( '/', '.', get_page_uri( $post->ID ) ) . '"></a>';
     3150                }
    31103151        }
    31113152
    31123153        /**
  • src/wp-includes/customize/class-wp-customize-post-collection-control.php

     
     1<?php
     2/**
     3 * Customize API: WP_Customize_Post_Collection_Control class
     4 *
     5 * @package WordPress
     6 * @subpackage Customize
     7 * @since 4.7.0
     8 */
     9
     10/**
     11 * Customize Post Collection Control class.
     12 *
     13 * @since 4.7.0
     14 *
     15 * @see WP_Customize_Control
     16 */
     17class WP_Customize_Post_Collection_Control extends WP_Customize_Control {
     18        /**
     19         * Control type.
     20         *
     21         * @since 4.7.0
     22         * @var string
     23         */
     24        public $type = 'post_collection';
     25
     26        /**
     27         * Post types that can be added as sections.
     28         *
     29         * @since 4.7.0
     30         * @var array
     31         */
     32        public $post_types = array( 'page', 'post' );
     33
     34        /**
     35         * Whether to include the front page in the post collection.
     36         *
     37         * @since 4.7.0
     38         * @var bool
     39         */
     40        public $include_front_page = false;
     41
     42        /**
     43         * Labels.
     44         *
     45         * @since 4.7.0
     46         * @access public
     47         * @var array
     48         */
     49        public $labels = array();
     50
     51        /**
     52         * Constructor.
     53         *
     54         * @since 4.7.0
     55         *
     56         * @param WP_Customize_Manager $manager Customizer bootstrap instance.
     57         * @param string               $id      Control ID.
     58         * @param array                $args    Optional. Arguments to override class property defaults.
     59         */
     60        public function __construct( $manager, $id, $args = array() ) {
     61                parent::__construct( $manager, $id, $args );
     62
     63                $this->labels = wp_parse_args( $this->labels, array(
     64                        'addPost'                => __( 'Add Post' ),
     65                        'addPosts'               => __( 'Add Posts' ),
     66                        'clearResults'           => __( 'Clear Results' ),
     67                        'moveUp'                 => __( 'Move up' ),
     68                        'moveDown'               => __( 'Move down' ),
     69                        'movedUp'                => __( 'Post moved up' ),
     70                        'movedDown'              => __( 'Post moved down' ),
     71                        'removePost'             => __( 'Remove Post' ),
     72                        'searchPosts'            => __( 'Search Posts' ),
     73                        'searchPostsPlaceholder' => __( 'Search posts&hellip;' ),
     74                ) );
     75        }
     76
     77        /**
     78         * Enqueue control related scripts/styles.
     79         *
     80         * @since 4.7.0
     81         */
     82        public function enqueue() {
     83                wp_enqueue_style( 'customize-post-collection' );
     84                wp_enqueue_script( 'customize-post-collection' );
     85
     86                add_action( 'customize_controls_print_footer_scripts', array( 'WP_Customize_Post_Collection_Control', 'print_templates' ) );
     87        }
     88
     89        /**
     90         * Refresh the parameters passed to the JavaScript via JSON.
     91         *
     92         * @since 4.7.0
     93         * @uses WP_Customize_Control::to_json()
     94         */
     95        public function to_json() {
     96                parent::to_json();
     97
     98                $this->json['labels']            = $this->labels;
     99                $this->json['posts']            = $this->get_posts();
     100                $this->json['postTypes']        = $this->post_types;
     101                $this->json['includeFrontPage'] = $this->include_front_page;
     102                $this->json['searchNonce']      = wp_create_nonce( 'find-posts' );
     103        }
     104
     105        /**
     106         * Don't render any content for this control from PHP.
     107         *
     108         * @since 4.7.0
     109         *
     110         * @see WP_Customize_Post_Collection_Control::content_template()
     111         */
     112        public function render_content() {}
     113
     114        /**
     115         * An Underscore (JS) template for this control's content (but not its container).
     116         *
     117         * @see WP_Customize_Control::print_template()
     118         *
     119         * @since 4.7.0
     120         */
     121        protected function content_template() {
     122                ?>
     123                <label>
     124                        <# if ( data.label ) { #>
     125                                <span class="customize-control-title">{{ data.label }}</span>
     126                        <# } #>
     127                        <# if ( data.description ) { #>
     128                                <span class="description customize-control-description">{{{ data.description }}}</span>
     129                        <# } #>
     130                </label>
     131                <?php
     132        }
     133
     134        /**
     135         * Print JavaScript templates in the Customizer footer.
     136         *
     137         * @since 4.7.0
     138         */
     139        public static function print_templates() {
     140                ?>
     141                <script type="text/html" id="tmpl-wp-item">
     142                        <div class="wp-item-header">
     143                                <h4 class="wp-item-title"><span>{{ data.title }}</span></h4>
     144
     145                                <# if ( data.showDeleteButton ) { #>
     146                                        <button type="button" class="wp-item-delete button-link">
     147                                                <span class="screen-reader-text">{{ data.labels.removePost }}</span>
     148                                        </button>
     149                                <# } #>
     150
     151                                <div class="wp-reorder-nav is-active">
     152                                        <button class="move-item-down" tabindex="0">{{ data.labels.moveDown }}</button>
     153                                        <button class="move-item-up" tabindex="0">{{ data.labels.moveUp }}</button>
     154                                </div>
     155                        </div>
     156                </script>
     157
     158                <script type="text/html" id="tmpl-customize-drawer-title">
     159                        <button type="button" class="customize-section-back" tabindex="-1">
     160                                <span class="screen-reader-text"><?php _e( 'Back' ); ?></span>
     161                        </button>
     162                        <h3>
     163                                <span class="customize-action">
     164                                        <?php
     165                                        /* translators: &#9656; is the unicode right-pointing triangle, and %s is the control label in the Customizer */
     166                                        printf( __( 'Customizing &#9656; %s' ), '{{ data.customizeAction }}' );
     167                                        ?>
     168                                </span>
     169                                {{ data.title }}
     170                        </h3>
     171                </script>
     172
     173                <script type="text/html" id="tmpl-search-group">
     174                        <label class="screen-reader-text" for="search-group-field">{{ data.labels.searchPosts }}</label>
     175                        <input type="text" id="search-group-field" placeholder="{{{ data.labels.searchPostsPlaceholder }}}" class="search-group-field">
     176                        <div class="search-icon" aria-hidden="true"></div>
     177                        <button type="button" class="clear-results"><span class="screen-reader-text">{{ data.labels.clearResults }}</span></button>
     178                </script>
     179
     180                <script type="text/html" id="tmpl-search-result">
     181                        <span class="search-results-item-type">{{ data.type }}</span>
     182                        <span class="search-results-item-title">{{ data.title }}</span>
     183
     184                        <button type="button" class="search-results-item-add button-link">
     185                                <span class="screen-reader-text">{{ data.labels.addPost }}</span>
     186                        </button>
     187                </script>
     188                <?php
     189        }
     190
     191        /**
     192         * Retrieve posts.
     193         *
     194         * @since 4.7.0
     195         *
     196         * @return array
     197         */
     198        protected function get_posts() {
     199                $data     = array();
     200                $value    = $this->value();
     201                $post_ids = array_filter( array_map( 'absint', explode( ',', $value ) ) );
     202
     203                if ( $this->include_front_page ) {
     204                        $front_page = get_option( 'page_on_front' );
     205                        if ( ! in_array( $front_page, $post_ids ) ) {
     206                                array_unshift( $post_ids, $front_page );
     207                        }
     208                }
     209
     210                if ( ! empty( $post_ids ) ) {
     211                        $posts = get_posts( array(
     212                                'post_type'      => $this->post_types,
     213                                'post_status'    => 'any',
     214                                'post__in'       => $post_ids,
     215                                'orderby'        => 'post__in',
     216                                'posts_per_page' => 20,
     217                        ) );
     218                }
     219
     220                if ( ! empty( $posts ) ) {
     221                        $i = 0;
     222                        foreach ( $posts as $post ) {
     223                                $data[] = array(
     224                                        'id'    => $post->ID,
     225                                        'title' => $post->post_title,
     226                                        'order' => ++$i,
     227                                );
     228                        }
     229                }
     230
     231                return $data;
     232        }
     233}
  • src/wp-includes/default-filters.php

     
    427427
    428428// Canonical
    429429add_action( 'template_redirect', 'redirect_canonical' );
     430add_action( 'template_redirect', 'wp_redirect_front_page_sections' );
    430431add_action( 'template_redirect', 'wp_redirect_admin_locations', 1000 );
    431432
    432433// Shortcodes
  • src/wp-includes/link-template.php

     
    312312function get_page_link( $post = false, $leavename = false, $sample = false ) {
    313313        $post = get_post( $post );
    314314
    315         if ( 'page' == get_option( 'show_on_front' ) && $post->ID == get_option( 'page_on_front' ) )
     315        if ( 'page' == get_option( 'show_on_front' ) && $post->ID == get_option( 'page_on_front' ) ) {
    316316                $link = home_url('/');
    317         else
     317        } else {
    318318                $link = _get_page_link( $post, $leavename, $sample );
     319        }
    319320
    320321        /**
    321322         * Filters the permalink for a page.
     
    359360                        $link = str_replace('%pagename%', get_page_uri( $post ), $link);
    360361                }
    361362
    362                 $link = home_url($link);
    363                 $link = user_trailingslashit($link, 'page');
     363                if ( is_front_page_section( $post->ID ) ) {
     364                        $link = home_url( '#' . str_replace( '/', '.', $link ) );
     365                } else {
     366                        $link = home_url($link);
     367                        $link = user_trailingslashit($link, 'page');
     368                }
    364369        } else {
    365370                $link = home_url( '?page_id=' . $post->ID );
    366371        }
  • src/wp-includes/post-template.php

     
    479479                        $classes[] = 'format-standard';
    480480        }
    481481
     482        // Front page sections.
     483        if ( is_front_page() && is_front_page_section( $post->ID ) ) {
     484                $classes[] = 'front-page-section';
     485        }
     486
    482487        $post_password_required = post_password_required( $post->ID );
    483488
    484489        // Post requires password.
     
    18021807        echo $rows;
    18031808        echo "</ul>";
    18041809}
     1810
     1811/**
     1812 * Whether a post is a front page section.
     1813 *
     1814 * @since 4.7.0
     1815 *
     1816 * @param int $post_id Post ID.
     1817 * @return bool
     1818 */
     1819function is_front_page_section( $post_id ) {
     1820        $section_ids = array_filter( wp_parse_id_list( get_option( 'front_page_sections' ) ) );
     1821        return in_array( intval( $post_id ), $section_ids, true );
     1822}
  • src/wp-includes/script-loader.php

     
    479479        $scripts->add( 'customize-nav-menus', "/wp-admin/js/customize-nav-menus$suffix.js", array( 'jquery', 'wp-backbone', 'customize-controls', 'accordion', 'nav-menu' ), false, 1 );
    480480        $scripts->add( 'customize-preview-nav-menus', "/wp-includes/js/customize-preview-nav-menus$suffix.js", array( 'jquery', 'wp-util', 'customize-preview', 'customize-selective-refresh' ), false, 1 );
    481481
     482        $scripts->add( 'customize-post-collection', "/wp-admin/js/customize-post-collection$suffix.js", array( 'jquery', 'jquery-ui-sortable', 'jquery-ui-droppable', 'wp-backbone', 'customize-controls' ), false, 1 );
     483
    482484        $scripts->add( 'accordion', "/wp-admin/js/accordion$suffix.js", array( 'jquery' ), false, 1 );
    483485
    484486        $scripts->add( 'shortcode', "/wp-includes/js/shortcode$suffix.js", array( 'underscore' ), false, 1 );
  • tests/phpunit/tests/ajax/FindPosts.php

     
     1<?php
     2
     3/**
     4 * Testing ajax post finding.
     5 *
     6 * @group ajax
     7 */
     8class Tests_Ajax_Find_Posts extends WP_Ajax_UnitTestCase {
     9
     10        public function test_wp_ajax_find_posts_returns_public_posts() {
     11
     12                $this->_setRole( 'administrator' );
     13
     14                $page_id = self::factory()->post->create( array(
     15                        'post_type' => 'page',
     16                ) );
     17                $page = get_post( $page_id );
     18
     19                $post_id = $this->front_page_section = self::factory()->post->create();
     20                $post = get_post( $post_id );
     21
     22                // Set up a default request
     23                $_POST['_ajax_nonce'] = wp_create_nonce( 'find-posts' );
     24
     25                // Make the request
     26                try {
     27                        $this->_handleAjax( 'find_posts' );
     28                } catch ( WPAjaxDieContinueException $e ) {
     29                        unset( $e );
     30                }
     31
     32                // Get the response.
     33                $response = json_decode( $this->_last_response, true );
     34
     35                $this->assertThat( $response['data'], $this->stringContains( $page->post_title ) );
     36                $this->assertThat( $response['data'], $this->stringContains( $post->post_title ) );
     37        }
     38
     39        public function test_wp_ajax_find_posts_does_not_return_attachments() {
     40
     41                $this->_setRole( 'administrator' );
     42
     43                $attachment_id = $this->front_page_section = self::factory()->post->create(array(
     44                        'post_type' => 'attachment',
     45                ) );
     46                $attachment = get_post( $attachment_id );
     47
     48                // Set up a default request
     49                $_POST['_ajax_nonce'] = wp_create_nonce( 'find-posts' );
     50
     51                // Make the request
     52                try {
     53                        $this->_handleAjax( 'find_posts' );
     54                } catch ( WPAjaxDieContinueException $e ) {
     55                        unset( $e );
     56                }
     57
     58                // Get the response.
     59                $response = json_decode( $this->_last_response, true );
     60
     61                $this->assertThat( $response['data'], $this->logicalNot( $this->stringContains( $attachment->post_title ) ) );
     62        }
     63
     64        public function test_wp_ajax_find_posts_searches() {
     65
     66                $this->_setRole( 'administrator' );
     67
     68                $post_id = $this->front_page_section = self::factory()->post->create();
     69                $post = get_post( $post_id );
     70
     71                $post2_id = $this->front_page_section = self::factory()->post->create();
     72                $post2 = get_post( $post2_id );
     73
     74                // Set up a default request
     75                $_POST['_ajax_nonce'] = wp_create_nonce( 'find-posts' );
     76                $_POST['ps']          = $post->post_title;
     77
     78                // Make the request
     79                try {
     80                        $this->_handleAjax( 'find_posts' );
     81                } catch ( WPAjaxDieContinueException $e ) {
     82                        unset( $e );
     83                }
     84
     85                // Get the response.
     86                $response = json_decode( $this->_last_response, true );
     87
     88                $this->assertThat( $response['data'], $this->stringContains( $post->post_title ) );
     89                $this->assertThat( $response['data'], $this->logicalNot( $this->stringContains( $post2->post_title ) ) );
     90        }
     91
     92        public function test_wp_ajax_find_posts_filters_by_status() {
     93
     94                $this->_setRole( 'administrator' );
     95
     96                $draft_id = $this->front_page_section = self::factory()->post->create( array(
     97                        'post_status' => 'draft',
     98                ) );
     99                $draft = get_post( $draft_id );
     100
     101                $post2_id = $this->front_page_section = self::factory()->post->create();
     102                $post2 = get_post( $post2_id );
     103
     104                // Set up a default request
     105                $_POST['_ajax_nonce'] = wp_create_nonce( 'find-posts' );
     106                $_POST['status']      = 'draft';
     107
     108                // Make the request
     109                try {
     110                        $this->_handleAjax( 'find_posts' );
     111                } catch ( WPAjaxDieContinueException $e ) {
     112                        unset( $e );
     113                }
     114
     115                // Get the response.
     116                $response = json_decode( $this->_last_response, true );
     117
     118                $this->assertThat( $response['data'], $this->stringContains( $draft->post_title ) );
     119                $this->assertThat( $response['data'], $this->logicalNot( $this->stringContains( $post2->post_title ) ) );
     120        }
     121
     122        public function test_wp_ajax_find_posts_returns_json() {
     123
     124                $this->_setRole( 'administrator' );
     125
     126                $post_id = $this->front_page_section = self::factory()->post->create();
     127                $post = get_post( $post_id );
     128
     129                // Set up a default request
     130                $_POST['_ajax_nonce'] = wp_create_nonce( 'find-posts' );
     131                $_POST['format']      = 'json';
     132
     133                // Make the request
     134                try {
     135                        $this->_handleAjax( 'find_posts' );
     136                } catch ( WPAjaxDieContinueException $e ) {
     137                        unset( $e );
     138                }
     139
     140                // Get the response.
     141                $response = json_decode( $this->_last_response, true );
     142
     143                $post_type = get_post_type_object( $post->post_type );
     144                $expected = array(
     145                        'id'    => $post->ID,
     146                        'title' => $post->post_title,
     147                        'type'  => $post_type->labels->singular_name,
     148                );
     149
     150                $this->assertContains( $expected, $response['data'] );
     151        }
     152}
  • tests/phpunit/tests/query/frontPageSections.php

     
     1<?php
     2
     3/**
     4 * @group query
     5 * @group front-page-sections
     6 */
     7
     8class Front_Page_Sections_Query extends WP_UnitTestCase {
     9        private $page_on_front;
     10        private $front_page_section;
     11
     12        function setUp() {
     13                $this->page_on_front = self::factory()->post->create( array(
     14                        'post_type' => 'page',
     15                ) );
     16                $this->front_page_section = self::factory()->post->create( array(
     17                        'post_type' => 'page',
     18                ) );
     19
     20                update_option( 'show_on_front', 'page' );
     21                update_option( 'page_on_front', $this->page_on_front );
     22                update_option( 'front_page_sections', $this->front_page_section );
     23        }
     24
     25        function tearDown() {
     26                global $wp_the_query;
     27                $wp_the_query->init();
     28
     29                update_option( 'show_on_front', 'posts' );
     30                delete_option( 'page_on_front' );
     31                delete_option( 'front_page_sections' );
     32        }
     33
     34        function test_page_id_is_set() {
     35                global $wp_the_query;
     36                $wp_the_query->query( array() );
     37
     38                $this->assertEquals( $this->page_on_front, $wp_the_query->query_vars['page_id'] );
     39        }
     40
     41        function test_all_posts_are_returned() {
     42                global $wp_the_query;
     43                $wp_the_query->query( array() );
     44
     45                $this->assertCount( 2, $wp_the_query->posts );
     46        }
     47
     48        function test_posts_are_ordered() {
     49                global $wp_the_query;
     50
     51                update_option( 'front_page_sections', "$this->front_page_section,$this->page_on_front" );
     52
     53                $wp_the_query->query( array() );
     54
     55                $this->assertCount( 2, $wp_the_query->posts );
     56                $this->assertEquals( $this->front_page_section, $wp_the_query->posts[0]->ID );
     57                $this->assertEquals( $this->page_on_front, $wp_the_query->posts[1]->ID );
     58                $this->assertEquals( $this->page_on_front, $wp_the_query->post->ID );
     59        }
     60
     61        function test_lots_of_subsections_are_returned() {
     62                global $wp_the_query;
     63
     64                $subsections = array();
     65                for( $i = 0; $i < 10; $i++ ) {
     66                        $subsections[] = self::factory()->post->create( array(
     67                                'post_type' => 'page',
     68                        ) );
     69                }
     70
     71                update_option( 'front_page_sections', implode(',', $subsections ) );
     72
     73                $wp_the_query->query( array() );
     74
     75                $this->assertCount( 11, $wp_the_query->posts );
     76        }
     77
     78        function test_get_post_doesnt_get_subsections() {
     79                $post = get_post( $this->page_on_front );
     80                $this->assertEquals( $this->page_on_front, $post->ID );
     81        }
     82
     83        function test_get_pages_doesnt_get_subsections() {
     84                $pages = get_pages( array( 'include' => array( $this->page_on_front ) ) );
     85
     86                $this->assertCount( 1, $pages );
     87                $this->assertEquals( $this->page_on_front, $pages[0]->ID );
     88        }
     89
     90        function test_get_posts_doesnt_get_subsections() {
     91                $pages = get_posts( array( 'include' => array( $this->page_on_front ), 'post_type' => 'page' ) );
     92
     93                $this->assertCount( 1, $pages );
     94                $this->assertEquals( $this->page_on_front, $pages[0]->ID );
     95        }
     96
     97        function test_the_post_outputs_anchor_tag() {
     98                global $wp_the_query;
     99                $wp_the_query->query( array() );
     100
     101                ob_start();
     102                while( $wp_the_query->have_posts() ) {
     103                        $wp_the_query->the_post();
     104                }
     105                $actual = ob_get_contents();
     106                ob_end_clean();
     107
     108                $expected  = '<a id="' . str_replace( '/', '.', get_page_uri( $this->page_on_front ) ) . '"></a>';
     109                $expected .= '<a id="' . str_replace( '/', '.', get_page_uri( $this->front_page_section ) ) . '"></a>';
     110
     111                $this->assertEquals( $expected, $actual );
     112        }
     113
     114        function test_new_wp_query_doesnt_output_anchor_tag() {
     115                $query = new WP_Query();
     116                $query->query( array() );
     117
     118                ob_start();
     119                while( $query->have_posts() ) {
     120                        $query->the_post();
     121                }
     122                $actual = ob_get_contents();
     123                ob_end_clean();
     124
     125                $this->assertEmpty( $actual );
     126        }
     127}
     128 No newline at end of file