Make WordPress Core

Changeset 40607


Ignore:
Timestamp:
05/10/2017 08:03:01 PM (7 years ago)
Author:
azaozz
Message:

Dashboard: Update the existing WordPress News dashboard widget to also include upcoming meetup events and WordCamps near the current user’s location.

Props @afercia, @andreamiddleton, @azaozz, @camikaos, @coreymckrill, @chanthaboune, @courtneypk, @dd32, @iandunn, @iseulde, @mapk, @mayukojpn, @melchoyce, @nao, @obenland, @pento, @samuelsidler, @stephdau, @tellyworth.
See #40702.

Location:
trunk
Files:
2 added
10 edited

Legend:

Unmodified
Added
Removed
  • trunk/src/wp-admin/admin-ajax.php

    r38159 r40607  
    6565    'press-this-add-category', 'crop-image', 'generate-password', 'save-wporg-username', 'delete-plugin',
    6666    'search-plugins', 'search-install-plugins', 'activate-plugin', 'update-theme', 'delete-theme',
    67     'install-theme', 'get-post-thumbnail-html',
     67    'install-theme', 'get-post-thumbnail-html', 'get-community-events',
    6868);
    6969
  • trunk/src/wp-admin/css/dashboard.css

    r40556 r40607  
    302302}
    303303
     304/* Dashboard WordPress events */
     305
     306.community-events-errors {
     307    margin: 0;
     308}
     309
     310.community-events-loading {
     311    padding: 10px 12px 8px;
     312}
     313
     314.community-events {
     315    margin-bottom: 6px;
     316    padding: 0 12px;
     317}
     318
     319.community-events .spinner {
     320    float: none;
     321    margin: 0;
     322    padding-bottom: 3px;
     323}
     324
     325.community-events-errors[aria-hidden="true"],
     326.community-events-errors *[aria-hidden="true"],
     327.community-events-loading[aria-hidden="true"],
     328.community-events[aria-hidden="true"],
     329.community-events *[aria-hidden="true"] {
     330    display: none;
     331}
     332
     333.community-events .activity-block:first-child,
     334.community-events h2 {
     335    padding-top: 12px;
     336    padding-bottom: 10px;
     337}
     338
     339.community-events-form {
     340    margin: 15px 0 5px;
     341}
     342
     343.community-events-form .regular-text {
     344    width: 40%;
     345    height: 28px;
     346}
     347
     348.community-events li.event-none {
     349    border-left: 4px solid #0070AE;
     350}
     351
     352.community-events-form label {
     353    display: inline-block;
     354    padding-bottom: 3px;
     355}
     356
     357.community-events .activity-block > p {
     358    margin-bottom: 0;
     359    display: inline;
     360}
     361
     362#community-events-submit {
     363    margin-left: 2px;
     364}
     365
     366.community-events .button-link:hover,
     367.community-events .button-link:active {
     368    color: #00a0d2;
     369}
     370
     371.community-events-cancel.button.button-link {
     372    color: #0073aa;
     373    text-decoration: underline;
     374    margin-left: 2px;
     375}
     376
     377.community-events ul {
     378    background-color: #fafafa;
     379    padding-left: 0;
     380    padding-right: 0;
     381    padding-bottom: 0;
     382}
     383
     384.community-events li {
     385    margin: 0;
     386    padding: 8px 12px;
     387    color: #72777c;
     388}
     389.community-events li:first-child {
     390    border-top: 1px solid #eee;
     391}
     392
     393.community-events li ~ li {
     394    border-top: 1px solid #eee;
     395}
     396
     397.community-events .activity-block.last {
     398    border-bottom: 1px solid #eee;
     399    padding-top: 0;
     400    margin-top: -1px;
     401}
     402
     403.community-events .event-info {
     404    display: block;
     405}
     406
     407.event-icon {
     408    height: 18px;
     409    padding-right: 10px;
     410    width: 18px;
     411    display: none; /* Hide on smaller screens */
     412}
     413
     414.event-icon:before {
     415    color: #82878C;
     416    font-size: 18px;
     417}
     418.event-meetup .event-icon:before {
     419    content: "\f484";
     420}
     421.event-wordcamp .event-icon:before {
     422    content: "\f486";
     423}
     424
     425.community-events .event-title {
     426    font-weight: 600;
     427    display: block;
     428}
     429
     430.community-events .event-date,
     431.community-events .event-time {
     432    display: block;
     433}
     434
     435.community-events-footer {
     436    margin-top: 0;
     437    margin-bottom: 0;
     438    padding: 12px;
     439    border-top: 1px solid #eee;
     440    color: #ddd;
     441}
     442
    304443/* Dashboard WordPress news */
    305444
     
    334473
    335474#dashboard_primary .rss-widget {
    336     border-bottom: 1px solid #eee;
    337475    font-size: 13px;
    338     padding: 8px 12px 10px;
     476    padding: 0 12px 0;
    339477}
    340478
     
    358496
    359497#dashboard_primary .rss-widget ul li {
    360     margin-bottom: 8px;
     498    padding: 4px 0;
     499    margin: 0;
    361500}
    362501
     
    8751014
    8761015a.rsswidget {
    877     font-size: 14px;
     1016    font-size: 13px;
    8781017    font-weight: 600;
    879     line-height: 1.7em;
     1018    line-height: 1.4em;
    8801019}
    8811020
     
    10871226        width: 30px;
    10881227        margin: 4px 10px 5px 0;
     1228    }
     1229
     1230    .community-events-toggle-location {
     1231        height: 38px;
     1232    }
     1233
     1234    .community-events-form .regular-text {
     1235        height: 31px;
    10891236    }
    10901237}
     
    11111258    }
    11121259}
     1260
     1261@media screen and (min-width: 355px) {
     1262    .community-events .event-info {
     1263        display: table-row;
     1264        float: left;
     1265        max-width: 59%;
     1266    }
     1267
     1268    .event-icon,
     1269    .event-icon[aria-hidden="true"] {
     1270        display: table-cell;
     1271    }
     1272
     1273    .event-info-inner {
     1274        display: table-cell;
     1275    }
     1276
     1277    .community-events .event-date-time {
     1278        float: right;
     1279        max-width: 39%;
     1280    }
     1281
     1282    .community-events .event-date,
     1283    .community-events .event-time {
     1284        text-align: right;
     1285    }
     1286}
  • trunk/src/wp-admin/includes/ajax-actions.php

    r40476 r40607  
    295295
    296296    wp_die( wp_json_encode( $return ) );
     297}
     298
     299/**
     300 * Handles AJAX requests for community events
     301 *
     302 * @since 4.8.0
     303 */
     304function wp_ajax_get_community_events() {
     305    require_once( ABSPATH . 'wp-admin/includes/class-wp-community-events.php' );
     306
     307    check_ajax_referer( 'community_events' );
     308
     309    $search         = isset( $_POST['location'] ) ? wp_unslash( $_POST['location'] ) : '';
     310    $timezone       = isset( $_POST['timezone'] ) ? wp_unslash( $_POST['timezone'] ) : '';
     311    $user_id        = get_current_user_id();
     312    $saved_location = get_user_option( 'community-events-location', $user_id );
     313    $events_client  = new WP_Community_Events( $user_id, $saved_location );
     314    $events         = $events_client->get_events( $search, $timezone );
     315
     316    if ( is_wp_error( $events ) ) {
     317        wp_send_json_error( array(
     318            'error' => $events->get_error_message(),
     319        ) );
     320    } else {
     321        if ( isset( $events['location'] ) ) {
     322            // Send only the data that the client will use.
     323            $events['location'] = $events['location']['description'];
     324
     325            // Store the location network-wide, so the user doesn't have to set it on each site.
     326            update_user_option( $user_id, 'community-events-location', $events['location'], true );
     327        }
     328
     329        wp_send_json_success( $events );
     330    }
    297331}
    298332
  • trunk/src/wp-admin/includes/dashboard.php

    r40556 r40607  
    5353    }
    5454
    55     // WordPress News
    56     wp_add_dashboard_widget( 'dashboard_primary', __( 'WordPress News' ), 'wp_dashboard_primary' );
     55    // WordPress Events and News
     56    wp_add_dashboard_widget( 'dashboard_primary', __( 'WordPress Events and News' ), 'wp_dashboard_events_news' );
    5757
    5858    if ( is_network_admin() ) {
     
    128128    /** This action is documented in wp-admin/edit-form-advanced.php */
    129129    do_action( 'do_meta_boxes', $screen->id, 'side', '' );
     130}
     131
     132/**
     133 * Gets the community events data that needs to be passed to dashboard.js.
     134 *
     135 * @since 4.8.0
     136 *
     137 * @return array The script data.
     138 */
     139function wp_get_community_events_script_data() {
     140    require_once( ABSPATH . 'wp-admin/includes/class-wp-community-events.php' );
     141
     142    $user_id       = get_current_user_id();
     143    $user_location = get_user_option( 'community-events-location', $user_id );
     144    $events_client = new WP_Community_Events( $user_id, $user_location );
     145
     146    $script_data = array(
     147        'nonce' => wp_create_nonce( 'community_events' ),
     148        'cache' => $events_client->get_cached_events(),
     149
     150        'l10n' => array(
     151            'enter_closest_city' => __( 'Enter your closest city to find nearby events.' ),
     152            'error_occurred_please_try_again' => __( 'An error occured. Please try again.' ),
     153
     154            /*
     155             * These specific examples were chosen to highlight the fact that a
     156             * state is not needed, even for cities whose name is not unique.
     157             * It would be too cumbersome to include that in the instructions
     158             * to the user, so it's left as an implication.
     159             */
     160            /* translators: %s is the name of the city we couldn't locate. Replace the examples with cities in your locale, but test that they match the expected location before including them. Use endonyms (native locale names) whenever possible. */
     161            'could_not_locate_city' => __( "We couldn't locate %s. Please try another nearby city. For example: Kansas City; Springfield; Portland." ),
     162
     163            // This one is only used with wp.a11y.speak(), so it can/should be more brief.
     164            /* translators: %s is the name of a city. */
     165            'city_updated' => __( 'City updated. Listing events near %s.' ),
     166        )
     167    );
     168
     169    return $script_data;
    130170}
    131171
     
    10701110}
    10711111
     1112
     1113/**
     1114 * Renders the Events and News dashboard widget.
     1115 *
     1116 * @since 4.8.0
     1117 */
     1118function wp_dashboard_events_news() {
     1119    wp_print_community_events_markup();
     1120
     1121    ?>
     1122
     1123    <div class="wordpress-news hide-if-no-js">
     1124        <?php wp_dashboard_primary(); ?>
     1125    </div>
     1126
     1127    <p class="community-events-footer">
     1128        <a href="https://make.wordpress.org/community/meetups-landing-page" target="_blank">
     1129            <?php _e( 'Meetups' ); ?> <span class="dashicons dashicons-external"></span>
     1130        </a>
     1131
     1132        |
     1133
     1134        <a href="https://central.wordcamp.org/schedule/" target="_blank">
     1135            <?php _e( 'WordCamps' ); ?> <span class="dashicons dashicons-external"></span>
     1136        </a>
     1137
     1138        |
     1139
     1140        <?php // translators: If a Rosetta site exists (e.g. https://es.wordpress.org/news/), then use that. Otherwise, leave untranslated. ?>
     1141        <a href="<?php _e( 'https://wordpress.org/news/' ); ?>" target="_blank">
     1142            <?php _e( 'News' ); ?> <span class="dashicons dashicons-external"></span>
     1143        </a>
     1144    </p>
     1145
     1146    <?php
     1147}
     1148
     1149/**
     1150 * Prints the markup for the Community Events section of the Events and News Dashboard widget.
     1151 *
     1152 * @since 4.8.0
     1153 */
     1154function wp_print_community_events_markup() {
     1155    $script_data = wp_get_community_events_script_data();
     1156
     1157    ?>
     1158
     1159    <div class="community-events-errors notice notice-error inline hide-if-js">
     1160        <p class="hide-if-js">
     1161            <?php _e( 'This widget requires JavaScript.'); ?>
     1162        </p>
     1163
     1164        <p class="community-events-error-occurred" aria-hidden="true">
     1165            <?php echo $script_data['l10n']['error_occurred_please_try_again']; ?>
     1166        </p>
     1167
     1168        <p class="community-events-could-not-locate" aria-hidden="true"></p>
     1169    </div>
     1170
     1171    <div class="community-events-loading hide-if-no-js">
     1172        <?php _e( 'Loading&hellip;'); ?>
     1173    </div>
     1174
     1175    <?php
     1176    /*
     1177     * Hide the main element when the page first loads, because the content
     1178     * won't be ready until wp.communityEvents.renderEventsTemplate() has run.
     1179     */
     1180    ?>
     1181    <div id="community-events" class="community-events" aria-hidden="true">
     1182        <div class="activity-block">
     1183            <p>
     1184                <span id="community-events-location-message"></span>
     1185
     1186                <button class="button-link community-events-toggle-location" aria-label="<?php _e( 'Edit city'); ?>" aria-expanded="false">
     1187                    <span class="dashicons dashicons-edit"></span>
     1188                </button>
     1189            </p>
     1190
     1191            <form class="community-events-form" aria-hidden="true" action="<?php echo esc_url( admin_url( 'admin-ajax.php' ) ); ?>" method="post">
     1192                <label for="community-events-location">
     1193                    <?php _e( 'City:' ); ?>
     1194                </label>
     1195                <?php /* translators: Replace with the name of a city in your locale that shows events. Use only the city name itself, without any region or country. Use the endonym instead of the English name. */ ?>
     1196                <input id="community-events-location" class="regular-text" type="text" name="community-events-location" placeholder="<?php _e( 'Cincinnati' ); ?>" />
     1197
     1198                <?php submit_button( __( 'Submit' ), 'secondary', 'community-events-submit', false ); ?>
     1199
     1200                <button class="community-events-cancel button button-link" type="button" aria-expanded="false">
     1201                    <?php _e( 'Cancel' ); ?>
     1202                </button>
     1203
     1204                <span class="spinner"></span>
     1205            </form>
     1206        </div>
     1207
     1208        <ul class="community-events-results activity-block last"></ul>
     1209    </div>
     1210
     1211    <?php
     1212}
     1213
     1214/**
     1215 * Renders the events templates for the Event and News widget.
     1216 *
     1217 * @since 4.8.0
     1218 */
     1219function wp_print_community_events_templates() {
     1220    $script_data = wp_get_community_events_script_data();
     1221
     1222    ?>
     1223
     1224    <script id="tmpl-community-events-attend-event-near" type="text/template">
     1225        <?php printf(
     1226            /* translators: %s is a placeholder for the name of a city. */
     1227            __( 'Attend an upcoming event near %s.' ),
     1228            '<strong>{{ data.location }}</strong>'
     1229        ); ?>
     1230    </script>
     1231
     1232    <script id="tmpl-community-events-could-not-locate" type="text/template">
     1233        <?php printf(
     1234            $script_data['l10n']['could_not_locate_city'],
     1235            '<em>{{data.unknownCity}}</em>'
     1236        ); ?>
     1237    </script>
     1238
     1239    <script id="tmpl-community-events-event-list" type="text/template">
     1240        <# _.each( data.events, function( event ) { #>
     1241            <li class="event event-{{ event.type }} wp-clearfix">
     1242                <div class="event-info">
     1243                    <div class="dashicons event-icon" aria-hidden="true"></div>
     1244                    <div class="event-info-inner">
     1245                        <a class="event-title" href="{{ event.url }}">{{ event.title }}</a>
     1246                        <span class="event-city">{{ event.location.location }}</span>
     1247                    </div>
     1248                </div>
     1249
     1250                <div class="event-date-time">
     1251                    <span class="event-date">{{ event.formatted_date }}</span>
     1252                    <# if ( 'meetup' === event.type ) { #>
     1253                        <span class="event-time">{{ event.formatted_time }}</span>
     1254                    <# } #>
     1255                </div>
     1256            </li>
     1257        <# } ) #>
     1258    </script>
     1259
     1260    <script id="tmpl-community-events-no-upcoming-events" type="text/template">
     1261        <li class="event-none">
     1262            <?php printf(
     1263                /* translators: 1: the city the user searched for, 2: meetup organization documentation URL */
     1264                __( 'There aren&#8217;t any events scheduled near %1$s at the moment. Would you like to <a href="%2$s">organize one</a>?' ),
     1265                '{{data.location}}',
     1266                __( 'https://make.wordpress.org/community/handbook/meetup-organizer/welcome/' )
     1267            ); ?>
     1268        </li>
     1269    </script>
     1270
     1271    <?php
     1272}
     1273
    10721274/**
    10731275 * WordPress News dashboard widget.
    10741276 *
    10751277 * @since 2.7.0
     1278 * @since 4.8.0 Removed popular plugins feed.
    10761279 */
    10771280function wp_dashboard_primary() {
     
    11061309            'title'        => apply_filters( 'dashboard_primary_title', __( 'WordPress Blog' ) ),
    11071310            'items'        => 1,
    1108             'show_summary' => 1,
     1311            'show_summary' => 0,
    11091312            'show_author'  => 0,
    1110             'show_date'    => 1,
     1313            'show_date'    => 0,
    11111314        ),
    11121315        'planet' => array(
     
    11531356    );
    11541357
    1155     if ( ( ! wp_disallow_file_mods( 'dashboard_widget' ) ) && ( ! is_multisite() && is_blog_admin() && current_user_can( 'install_plugins' ) ) || ( is_network_admin() && current_user_can( 'manage_network_plugins' ) && current_user_can( 'install_plugins' ) ) ) {
    1156         $feeds['plugins'] = array(
    1157             'link'         => '',
    1158             'url'          => array(
    1159                 'popular' => 'http://wordpress.org/plugins/rss/browse/popular/',
    1160             ),
    1161             'title'        => '',
    1162             'items'        => 1,
    1163             'show_summary' => 0,
    1164             'show_author'  => 0,
    1165             'show_date'    => 0,
    1166         );
    1167     }
    1168 
    11691358    wp_dashboard_cached_rss_widget( 'dashboard_primary', 'wp_dashboard_primary_output', $feeds );
    11701359}
     
    11741363 *
    11751364 * @since 3.8.0
     1365 * @since 4.8.0 Removed popular plugins feed.
    11761366 *
    11771367 * @param string $widget_id Widget ID.
     
    11821372        $args['type'] = $type;
    11831373        echo '<div class="rss-widget">';
    1184         if ( $type === 'plugins' ) {
    1185             wp_dashboard_plugins_output( $args['url'], $args );
    1186         } else {
    11871374            wp_widget_rss_output( $args['url'], $args );
    1188         }
    11891375        echo "</div>";
    11901376    }
    1191 }
    1192 
    1193 /**
    1194  * Display plugins text for the WordPress news widget.
    1195  *
    1196  * @since 2.5.0
    1197  *
    1198  * @param string $rss  The RSS feed URL.
    1199  * @param array  $args Array of arguments for this RSS feed.
    1200  */
    1201 function wp_dashboard_plugins_output( $rss, $args = array() ) {
    1202     // Plugin feeds plus link to install them
    1203     $popular = fetch_feed( $args['url']['popular'] );
    1204 
    1205     if ( false === $plugin_slugs = get_transient( 'plugin_slugs' ) ) {
    1206         $plugin_slugs = array_keys( get_plugins() );
    1207         set_transient( 'plugin_slugs', $plugin_slugs, DAY_IN_SECONDS );
    1208     }
    1209 
    1210     echo '<ul>';
    1211 
    1212     foreach ( array( $popular ) as $feed ) {
    1213         if ( is_wp_error( $feed ) || ! $feed->get_item_quantity() )
    1214             continue;
    1215 
    1216         $items = $feed->get_items(0, 5);
    1217 
    1218         // Pick a random, non-installed plugin
    1219         while ( true ) {
    1220             // Abort this foreach loop iteration if there's no plugins left of this type
    1221             if ( 0 == count($items) )
    1222                 continue 2;
    1223 
    1224             $item_key = array_rand($items);
    1225             $item = $items[$item_key];
    1226 
    1227             list($link, $frag) = explode( '#', $item->get_link() );
    1228 
    1229             $link = esc_url($link);
    1230             if ( preg_match( '|/([^/]+?)/?$|', $link, $matches ) )
    1231                 $slug = $matches[1];
    1232             else {
    1233                 unset( $items[$item_key] );
    1234                 continue;
    1235             }
    1236 
    1237             // Is this random plugin's slug already installed? If so, try again.
    1238             reset( $plugin_slugs );
    1239             foreach ( $plugin_slugs as $plugin_slug ) {
    1240                 if ( $slug == substr( $plugin_slug, 0, strlen( $slug ) ) ) {
    1241                     unset( $items[$item_key] );
    1242                     continue 2;
    1243                 }
    1244             }
    1245 
    1246             // If we get to this point, then the random plugin isn't installed and we can stop the while().
    1247             break;
    1248         }
    1249 
    1250         // Eliminate some common badly formed plugin descriptions
    1251         while ( ( null !== $item_key = array_rand($items) ) && false !== strpos( $items[$item_key]->get_description(), 'Plugin Name:' ) )
    1252             unset($items[$item_key]);
    1253 
    1254         if ( !isset($items[$item_key]) )
    1255             continue;
    1256 
    1257         $raw_title = $item->get_title();
    1258 
    1259         $ilink = wp_nonce_url('plugin-install.php?tab=plugin-information&plugin=' . $slug, 'install-plugin_' . $slug) . '&amp;TB_iframe=true&amp;width=600&amp;height=800';
    1260         echo '<li class="dashboard-news-plugin"><span>' . __( 'Popular Plugin' ) . ':</span> ' . esc_html( $raw_title ) .
    1261             '&nbsp;<a href="' . $ilink . '" class="thickbox open-plugin-details-modal" aria-label="' .
    1262             /* translators: %s: plugin name */
    1263             esc_attr( sprintf( __( 'Install %s' ), $raw_title ) ) . '">(' . __( 'Install' ) . ')</a></li>';
    1264 
    1265         $feed->__destruct();
    1266         unset( $feed );
    1267     }
    1268 
    1269     echo '</ul>';
    12701377}
    12711378
  • trunk/src/wp-admin/includes/deprecated.php

    r38470 r40607  
    12961296
    12971297/**
     1298 * Display plugins text for the WordPress news widget.
     1299 *
     1300 * @since 2.5.0
     1301 * @deprecated 4.8.0
     1302 *
     1303 * @param string $rss  The RSS feed URL.
     1304 * @param array  $args Array of arguments for this RSS feed.
     1305 */
     1306function wp_dashboard_plugins_output( $rss, $args = array() ) {
     1307    _deprecated_function( __FUNCTION__, '4.8.0' );
     1308
     1309    // Plugin feeds plus link to install them
     1310    $popular = fetch_feed( $args['url']['popular'] );
     1311
     1312    if ( false === $plugin_slugs = get_transient( 'plugin_slugs' ) ) {
     1313        $plugin_slugs = array_keys( get_plugins() );
     1314        set_transient( 'plugin_slugs', $plugin_slugs, DAY_IN_SECONDS );
     1315    }
     1316
     1317    echo '<ul>';
     1318
     1319    foreach ( array( $popular ) as $feed ) {
     1320        if ( is_wp_error( $feed ) || ! $feed->get_item_quantity() )
     1321            continue;
     1322
     1323        $items = $feed->get_items(0, 5);
     1324
     1325        // Pick a random, non-installed plugin
     1326        while ( true ) {
     1327            // Abort this foreach loop iteration if there's no plugins left of this type
     1328            if ( 0 == count($items) )
     1329                continue 2;
     1330
     1331            $item_key = array_rand($items);
     1332            $item = $items[$item_key];
     1333
     1334            list($link, $frag) = explode( '#', $item->get_link() );
     1335
     1336            $link = esc_url($link);
     1337            if ( preg_match( '|/([^/]+?)/?$|', $link, $matches ) )
     1338                $slug = $matches[1];
     1339            else {
     1340                unset( $items[$item_key] );
     1341                continue;
     1342            }
     1343
     1344            // Is this random plugin's slug already installed? If so, try again.
     1345            reset( $plugin_slugs );
     1346            foreach ( $plugin_slugs as $plugin_slug ) {
     1347                if ( $slug == substr( $plugin_slug, 0, strlen( $slug ) ) ) {
     1348                    unset( $items[$item_key] );
     1349                    continue 2;
     1350                }
     1351            }
     1352
     1353            // If we get to this point, then the random plugin isn't installed and we can stop the while().
     1354            break;
     1355        }
     1356
     1357        // Eliminate some common badly formed plugin descriptions
     1358        while ( ( null !== $item_key = array_rand($items) ) && false !== strpos( $items[$item_key]->get_description(), 'Plugin Name:' ) )
     1359            unset($items[$item_key]);
     1360
     1361        if ( !isset($items[$item_key]) )
     1362            continue;
     1363
     1364        $raw_title = $item->get_title();
     1365
     1366        $ilink = wp_nonce_url('plugin-install.php?tab=plugin-information&plugin=' . $slug, 'install-plugin_' . $slug) . '&amp;TB_iframe=true&amp;width=600&amp;height=800';
     1367        echo '<li class="dashboard-news-plugin"><span>' . __( 'Popular Plugin' ) . ':</span> ' . esc_html( $raw_title ) .
     1368            '&nbsp;<a href="' . $ilink . '" class="thickbox open-plugin-details-modal" aria-label="' .
     1369            /* translators: %s: plugin name */
     1370            esc_attr( sprintf( __( 'Install %s' ), $raw_title ) ) . '">(' . __( 'Install' ) . ')</a></li>';
     1371
     1372        $feed->__destruct();
     1373        unset( $feed );
     1374    }
     1375
     1376    echo '</ul>';
     1377}
     1378
     1379/**
    12981380 * This was once used to move child posts to a new parent.
    12991381 *
  • trunk/src/wp-admin/includes/upgrade.php

    r40296 r40607  
    565565    if ( $wp_current_db_version < 37965 )
    566566        upgrade_460();
     567
     568    if ( $wp_current_db_version < 40500 ) { //todo update to commit for #40702
     569        upgrade_480();
     570    }
    567571
    568572    maybe_disable_link_manager();
     
    17341738
    17351739/**
     1740 * Executes changes made in WordPress 4.8.0.
     1741 *
     1742 * @ignore
     1743 * @since 4.8.0
     1744 *
     1745 * @global int $wp_current_db_version Current database version.
     1746 */
     1747function upgrade_480() {
     1748    global $wp_current_db_version;
     1749
     1750    if ( $wp_current_db_version < 40500 ) { // todo update to commit for #40702
     1751        // This feature plugin was merged for #40702, so the plugin itself is no longer needed
     1752        deactivate_plugins( array( 'nearby-wp-events/nearby-wordpress-events.php' ), true );
     1753
     1754        // The markup stored in this transient changed for #40702
     1755        delete_transient( 'dash_' . md5( 'dashboard_primary' . '_' . get_locale() ) );
     1756    }
     1757}
     1758
     1759/**
    17361760 * Executes network-level upgrade routines.
    17371761 *
  • trunk/src/wp-admin/index.php

    r38899 r40607  
    1616
    1717wp_enqueue_script( 'dashboard' );
     18wp_localize_script( 'dashboard', 'communityEventsData', wp_get_community_events_script_data() );
     19
    1820if ( current_user_can( 'edit_theme_options' ) )
    1921    wp_enqueue_script( 'customize-loader' );
     
    139141
    140142<?php
     143wp_print_community_events_templates();
     144
    141145require( ABSPATH . 'wp-admin/admin-footer.php' );
  • trunk/src/wp-admin/js/dashboard.js

    r34977 r40607  
    11/* global pagenow, ajaxurl, postboxes, wpActiveEditor:true */
    22var ajaxWidgets, ajaxPopulateWidgets, quickPressLoad;
     3window.wp = window.wp || {};
    34
    45jQuery(document).ready( function($) {
     
    188189
    189190} );
     191
     192jQuery( function( $ ) {
     193    'use strict';
     194   
     195    var communityEventsData = window.communityEventsData || {};
     196
     197    var app = window.wp.communityEvents = {
     198        initialized: false,
     199        model: null,
     200
     201        /**
     202         * Initializes the wp.communityEvents object.
     203         *
     204         * @since 4.8.0
     205         */
     206        init: function() {
     207            if ( app.initialized ) {
     208                return;
     209            }
     210
     211            var $container = $( '#community-events' );
     212
     213            /*
     214             * When JavaScript is disabled, the errors container is shown, so
     215             * that "This widget requires Javascript" message can be seen.
     216             *
     217             * When JS is enabled, the container is hidden at first, and then
     218             * revealed during the template rendering, if there actually are
     219             * errors to show.
     220             *
     221             * The display indicator switches from `hide-if-js` to `aria-hidden`
     222             * here in order to maintain consistency with all the other fields
     223             * that key off of `aria-hidden` to determine their visibility.
     224             * `aria-hidden` can't be used initially, because there would be no
     225             * way to set it to false when JavaScript is disabled, which would
     226             * prevent people from seeing the "This widget requires JavaScript"
     227             * message.
     228             */
     229            $( '.community-events-errors' )
     230                .attr( 'aria-hidden', true )
     231                .removeClass( 'hide-if-js' );
     232
     233            $container.on( 'click', '.community-events-toggle-location, .community-events-cancel', app.toggleLocationForm );
     234
     235            $container.on( 'submit', '.community-events-form', function( event ) {
     236                event.preventDefault();
     237
     238                app.getEvents( {
     239                    location: $( '#community-events-location' ).val()
     240                });
     241            });
     242
     243            if ( communityEventsData && communityEventsData.cache && communityEventsData.cache.location && communityEventsData.cache.events ) {
     244                app.renderEventsTemplate( communityEventsData.cache, 'app' );
     245            } else {
     246                app.getEvents();
     247            }
     248
     249            app.initialized = true;
     250        },
     251
     252        /**
     253         * Toggles the visibility of the Edit Location form.
     254         *
     255         * @since 4.8.0
     256         *
     257         * @param {event|string} action 'show' or 'hide' to specify a state;
     258         *                              Or an event object to flip between states
     259         */
     260        toggleLocationForm: function( action ) {
     261            var $toggleButton = $( '.community-events-toggle-location' ),
     262                $cancelButton = $( '.community-events-cancel' ),
     263                $form         = $( '.community-events-form' );
     264
     265            if ( 'object' === typeof action ) {
     266                // Strict comparison doesn't work in this case.
     267                action = 'true' == $toggleButton.attr( 'aria-expanded' ) ? 'hide' : 'show';
     268            }
     269
     270            if ( 'hide' === action ) {
     271                $toggleButton.attr( 'aria-expanded', false );
     272                $cancelButton.attr( 'aria-expanded', false );
     273                $form.attr( 'aria-hidden', true );
     274            } else {
     275                $toggleButton.attr( 'aria-expanded', true );
     276                $cancelButton.attr( 'aria-expanded', true );
     277                $form.attr( 'aria-hidden', false );
     278            }
     279        },
     280
     281        /**
     282         * Sends REST API requests to fetch events for the widget.
     283         *
     284         * @since 4.8.0
     285         *
     286         * @param {object} requestParams
     287         */
     288        getEvents: function( requestParams ) {
     289            var initiatedBy,
     290                app = this,
     291                $spinner = $( '.community-events-form' ).children( '.spinner' );
     292
     293            requestParams          = requestParams || {};
     294            requestParams._wpnonce = communityEventsData.nonce;
     295            requestParams.timezone = window.Intl ? window.Intl.DateTimeFormat().resolvedOptions().timeZone : '';
     296
     297            initiatedBy = requestParams.location ? 'user' : 'app';
     298
     299            $spinner.addClass( 'is-active' );
     300
     301            wp.ajax.post( 'get-community-events', requestParams )
     302                .always( function() {
     303                    $spinner.removeClass( 'is-active' );
     304                })
     305
     306                .done( function( response ) {
     307                    if ( 'no_location_available' === response.error ) {
     308                        if ( requestParams.location ) {
     309                            response.unknownCity = requestParams.location;
     310                        } else {
     311                            /*
     312                             * No location was passed, which means that this was an automatic query
     313                             * based on IP, locale, and timezone. Since the user didn't initiate it,
     314                             * it should fail silently. Otherwise, the error could confuse and/or
     315                             * annoy them.
     316                             */
     317
     318                            delete response.error;
     319                        }
     320                    }
     321                    app.renderEventsTemplate( response, initiatedBy );
     322                })
     323
     324                .fail( function() {
     325                    app.renderEventsTemplate( {
     326                        'location' : false,
     327                        'error'    : true
     328                    }, initiatedBy );
     329                });
     330        },
     331
     332        /**
     333         * Renders the template for the Events section of the Events & News widget.
     334         *
     335         * @since 4.8.0
     336         *
     337         * @param {Object} templateParams The various parameters that will get passed to wp.template
     338         * @param {string} initiatedBy    'user' to indicate that this was triggered manually by the user;
     339         *                                'app' to indicate it was triggered automatically by the app itself.
     340         */
     341        renderEventsTemplate: function( templateParams, initiatedBy ) {
     342            var template,
     343                elementVisibility,
     344                l10nPlaceholder  = /%(?:\d\$)?s/g, // Match `%s`, `%1$s`, `%2$s`, etc.
     345                $locationMessage = $( '#community-events-location-message' ),
     346                $results         = $( '.community-events-results' );
     347
     348            /*
     349             * Hide all toggleable elements by default, to keep the logic simple.
     350             * Otherwise, each block below would have to turn hide everything that
     351             * could have been shown at an earlier point.
     352             *
     353             * The exception to that is that the .community-events container. It's hidden
     354             * when the page is first loaded, because the content isn't ready yet,
     355             * but once we've reached this point, it should always be shown.
     356             */
     357            elementVisibility = {
     358                '.community-events'                  : true,
     359                '.community-events-loading'          : false,
     360                '.community-events-errors'           : false,
     361                '.community-events-error-occurred'   : false,
     362                '.community-events-could-not-locate' : false,
     363                '#community-events-location-message' : false,
     364                '.community-events-toggle-location'  : false,
     365                '.community-events-results'          : false
     366            };
     367
     368            /*
     369             * Determine which templates should be rendered and which elements
     370             * should be displayed.
     371             */
     372            if ( templateParams.location ) {
     373                template = wp.template( 'community-events-attend-event-near' );
     374                $locationMessage.html( template( templateParams ) );
     375
     376                if ( templateParams.events.length ) {
     377                    template = wp.template( 'community-events-event-list' );
     378                    $results.html( template( templateParams ) );
     379                } else {
     380                    template = wp.template( 'community-events-no-upcoming-events' );
     381                    $results.html( template( templateParams ) );
     382                }
     383                wp.a11y.speak( communityEventsData.l10n.city_updated.replace( l10nPlaceholder, templateParams.location ) );
     384
     385                elementVisibility['#community-events-location-message'] = true;
     386                elementVisibility['.community-events-toggle-location']  = true;
     387                elementVisibility['.community-events-results']          = true;
     388
     389            } else if ( templateParams.unknownCity ) {
     390                template = wp.template( 'community-events-could-not-locate' );
     391                $( '.community-events-could-not-locate' ).html( template( templateParams ) );
     392                wp.a11y.speak( communityEventsData.l10n.could_not_locate_city.replace( l10nPlaceholder, templateParams.unknownCity ) );
     393
     394                elementVisibility['.community-events-errors']           = true;
     395                elementVisibility['.community-events-could-not-locate'] = true;
     396
     397            } else if ( templateParams.error && 'user' === initiatedBy ) {
     398                /*
     399                 * Errors messages are only shown for requests that were initiated
     400                 * by the user, not for ones that were initiated by the app itself.
     401                 * Showing error messages for an event that user isn't aware of
     402                 * could be confusing or unnecessarily distracting.
     403                 */
     404                wp.a11y.speak( communityEventsData.l10n.error_occurred_please_try_again );
     405
     406                elementVisibility['.community-events-errors']         = true;
     407                elementVisibility['.community-events-error-occurred'] = true;
     408
     409            } else {
     410                $locationMessage.text( communityEventsData.l10n.enter_closest_city );
     411
     412                elementVisibility['#community-events-location-message'] = true;
     413                elementVisibility['.community-events-toggle-location']  = true;
     414            }
     415
     416            // Set the visibility of toggleable elements.
     417            _.each( elementVisibility, function( isVisible, element ) {
     418                $( element ).attr( 'aria-hidden', ! isVisible );
     419            });
     420
     421            $( '.community-events-toggle-location' ).attr( 'aria-expanded', elementVisibility['.community-events-toggle-location'] );
     422
     423            /*
     424             * During the initial page load, the location form should be hidden
     425             * by default if the user has saved a valid location during a previous
     426             * session. It's safe to assume that they want to continue using that
     427             * location, and displaying the form would unnecessarily clutter the
     428             * widget.
     429             */
     430            if ( 'app' === initiatedBy && templateParams.location ) {
     431                app.toggleLocationForm( 'hide' );
     432            } else {
     433                app.toggleLocationForm( 'show' );
     434            }
     435        }
     436    };
     437
     438    if ( $( '#dashboard_primary' ).is( ':visible' ) ) {
     439        app.init();
     440    } else {
     441        $( document ).on( 'postbox-toggled', function( event, postbox ) {
     442            var $postbox = $( postbox );
     443
     444            if ( 'dashboard_primary' === $postbox.attr( 'id' ) && $postbox.is( ':visible' ) ) {
     445                app.init();
     446            }
     447        });
     448    }
     449});
  • trunk/src/wp-admin/network/index.php

    r38721 r40607  
    5555
    5656wp_enqueue_script( 'dashboard' );
     57wp_localize_script( 'dashboard', 'communityEventsData', wp_get_community_events_script_data() );
    5758wp_enqueue_script( 'plugin-install' );
    5859add_thickbox();
     
    7475</div><!-- wrap -->
    7576
    76 <?php include( ABSPATH . 'wp-admin/admin-footer.php' ); ?>
     77<?php
     78wp_print_community_events_templates();
     79include( ABSPATH . 'wp-admin/admin-footer.php' );
  • trunk/src/wp-includes/script-loader.php

    r40283 r40607  
    725725        ) );
    726726
    727         $scripts->add( 'dashboard', "/wp-admin/js/dashboard$suffix.js", array( 'jquery', 'admin-comments', 'postbox' ), false, 1 );
     727        $scripts->add( 'dashboard', "/wp-admin/js/dashboard$suffix.js", array( 'jquery', 'admin-comments', 'postbox', 'wp-util', 'wp-a11y' ), false, 1 );
    728728
    729729        $scripts->add( 'list-revisions', "/wp-includes/js/wp-list-revisions$suffix.js" );
Note: See TracChangeset for help on using the changeset viewer.