Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
| Total | |
50.00% |
313 / 626 |
|
25.81% |
8 / 31 |
CRAP | |
0.00% |
0 / 1 |
| Action_Bar | |
50.00% |
313 / 626 |
|
25.81% |
8 / 31 |
4702.50 | |
0.00% |
0 / 1 |
| init | |
0.00% |
0 / 8 |
|
0.00% |
0 / 1 |
20 | |||
| load | |
90.91% |
10 / 11 |
|
0.00% |
0 / 1 |
2.00 | |||
| enqueue_scripts | |
0.00% |
0 / 82 |
|
0.00% |
0 / 1 |
812 | |||
| footer | |
100.00% |
13 / 13 |
|
100.00% |
1 / 1 |
5 | |||
| get_post_stats_url | |
100.00% |
11 / 11 |
|
100.00% |
1 / 1 |
2 | |||
| is_vip | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
6 | |||
| localized_url | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
6 | |||
| is_folded | |
0.00% |
0 / 3 |
|
0.00% |
0 / 1 |
6 | |||
| set_folded | |
0.00% |
0 / 8 |
|
0.00% |
0 / 1 |
30 | |||
| logged_out_follow_disabled | |
0.00% |
0 / 3 |
|
0.00% |
0 / 1 |
12 | |||
| has_enough_posts | |
100.00% |
5 / 5 |
|
100.00% |
1 / 1 |
3 | |||
| flush_published_posts_count | |
100.00% |
6 / 6 |
|
100.00% |
1 / 1 |
6 | |||
| switch_to_user_locale | |
40.00% |
2 / 5 |
|
0.00% |
0 / 1 |
4.94 | |||
| restore_locale | |
40.00% |
2 / 5 |
|
0.00% |
0 / 1 |
4.94 | |||
| email_default | |
0.00% |
0 / 5 |
|
0.00% |
0 / 1 |
12 | |||
| icon_markup | |
96.43% |
27 / 28 |
|
0.00% |
0 / 1 |
3 | |||
| icon | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
| menu_item | |
100.00% |
22 / 22 |
|
100.00% |
1 / 1 |
4 | |||
| menu_group | |
75.00% |
3 / 4 |
|
0.00% |
0 / 1 |
2.06 | |||
| blavatar | |
33.33% |
2 / 6 |
|
0.00% |
0 / 1 |
5.67 | |||
| site_title | |
100.00% |
10 / 10 |
|
100.00% |
1 / 1 |
1 | |||
| follow_links | |
100.00% |
6 / 6 |
|
100.00% |
1 / 1 |
3 | |||
| html | |
58.39% |
188 / 322 |
|
0.00% |
0 / 1 |
516.47 | |||
| can_reblog | |
0.00% |
0 / 5 |
|
0.00% |
0 / 1 |
20 | |||
| gdpr_applies | |
71.43% |
5 / 7 |
|
0.00% |
0 / 1 |
6.84 | |||
| bump_stat | |
0.00% |
0 / 32 |
|
0.00% |
0 / 1 |
20 | |||
| fold | |
0.00% |
0 / 4 |
|
0.00% |
0 / 1 |
6 | |||
| unfold | |
0.00% |
0 / 4 |
|
0.00% |
0 / 1 |
6 | |||
| ajax_stats | |
0.00% |
0 / 3 |
|
0.00% |
0 / 1 |
6 | |||
| settings_field | |
0.00% |
0 / 2 |
|
0.00% |
0 / 1 |
2 | |||
| settings_field_display | |
0.00% |
0 / 3 |
|
0.00% |
0 / 1 |
2 | |||
| 1 | <?php |
| 2 | /** |
| 3 | * WordPress.com Action Bar. |
| 4 | * |
| 5 | * The floating bar in the bottom corner of a site's front end. Visitors can subscribe, comment, |
| 6 | * reblog and report from it; site members get Edit and Stats shortcuts. |
| 7 | * |
| 8 | * @package automattic/jetpack-newsletter |
| 9 | */ |
| 10 | |
| 11 | namespace Automattic\Jetpack\Newsletter; |
| 12 | |
| 13 | use Automattic\Jetpack\Assets; |
| 14 | use Automattic\Jetpack\Status; |
| 15 | use Automattic\Jetpack\Status\Host; |
| 16 | |
| 17 | /** |
| 18 | * The floating Action Bar on the front end of WordPress.com Simple sites. Ported from wpcom `wp-content/mu-plugins/actionbar.php`. |
| 19 | */ |
| 20 | class Action_Bar { |
| 21 | /** |
| 22 | * Transient caching whether the site has published enough posts to show Subscribe. |
| 23 | * |
| 24 | * @var string |
| 25 | */ |
| 26 | const ENOUGH_POSTS_TRANSIENT = 'jetpack_action_bar_has_enough_posts'; |
| 27 | |
| 28 | /** |
| 29 | * Whether the class has been initialized. |
| 30 | * |
| 31 | * @var bool |
| 32 | */ |
| 33 | private static $initialized = false; |
| 34 | |
| 35 | /** |
| 36 | * Queue the Action Bar to load once all plugins are available. |
| 37 | * |
| 38 | * Simple only for now. Yields to the copy wpcom still ships in mu-plugins, so the two never load together. |
| 39 | * |
| 40 | * @since 0.17.0 |
| 41 | */ |
| 42 | public static function init() { |
| 43 | if ( self::$initialized ) { |
| 44 | return; |
| 45 | } |
| 46 | self::$initialized = true; |
| 47 | |
| 48 | if ( ! ( new Host() )->is_wpcom_simple() ) { |
| 49 | return; |
| 50 | } |
| 51 | |
| 52 | // A callback added to the hook that is currently running never fires. |
| 53 | if ( did_action( 'plugins_loaded' ) ) { |
| 54 | self::load(); |
| 55 | } else { |
| 56 | add_action( 'plugins_loaded', array( __CLASS__, 'load' ) ); |
| 57 | } |
| 58 | } |
| 59 | |
| 60 | /** |
| 61 | * Register the bar's hooks unless wpcom's mu-plugin copy is loaded. |
| 62 | * |
| 63 | * @since 0.17.0 |
| 64 | */ |
| 65 | public static function load() { |
| 66 | if ( function_exists( 'wpcom_actionbar_enqueue_scripts' ) ) { |
| 67 | return; |
| 68 | } |
| 69 | |
| 70 | add_action( 'wp_enqueue_scripts', array( __CLASS__, 'enqueue_scripts' ), 101 ); |
| 71 | add_action( 'admin_init', array( __CLASS__, 'settings_field' ) ); |
| 72 | add_action( 'transition_post_status', array( __CLASS__, 'flush_published_posts_count' ), 10, 3 ); |
| 73 | |
| 74 | add_action( 'wp_ajax_fold_actionbar', array( __CLASS__, 'fold' ) ); |
| 75 | add_action( 'wp_ajax_nopriv_fold_actionbar', array( __CLASS__, 'fold' ) ); |
| 76 | add_action( 'wp_ajax_unfold_actionbar', array( __CLASS__, 'unfold' ) ); |
| 77 | add_action( 'wp_ajax_nopriv_unfold_actionbar', array( __CLASS__, 'unfold' ) ); |
| 78 | add_action( 'wp_ajax_actionbar_stats', array( __CLASS__, 'ajax_stats' ) ); |
| 79 | add_action( 'wp_ajax_nopriv_actionbar_stats', array( __CLASS__, 'ajax_stats' ) ); |
| 80 | } |
| 81 | |
| 82 | /** |
| 83 | * Decide whether the bar renders on this request and, if so, queue its data and footer output. |
| 84 | */ |
| 85 | public static function enqueue_scripts() { |
| 86 | if ( get_option( 'wpcom_hide_action_bar' ) ) { |
| 87 | return; |
| 88 | } |
| 89 | |
| 90 | $current_user = wp_get_current_user(); |
| 91 | |
| 92 | /** |
| 93 | * Filters whether the Action Bar loads on this request. |
| 94 | * |
| 95 | * WordPress.com hooks this to keep the bar off its internal sites and off sites marked deleted, spam, archived, or parked. |
| 96 | * |
| 97 | * @since 0.17.0 |
| 98 | * |
| 99 | * @param bool $enabled Whether to load the bar. Default true. |
| 100 | */ |
| 101 | if ( ! apply_filters( 'jetpack_action_bar_enabled', true ) ) { |
| 102 | return; |
| 103 | } |
| 104 | |
| 105 | $site_id = get_current_blog_id(); |
| 106 | |
| 107 | // phpcs:disable WordPress.Security.NonceVerification.Recommended -- Read-only checks on preview query args. |
| 108 | // Don't show on theme previews and block patterns source sites. |
| 109 | $is_theme_demo = function_exists( 'wpcom_is_theme_demo_site' ) && wpcom_is_theme_demo_site(); |
| 110 | // @phan-suppress-next-line PhanUndeclaredFunction -- Defined by jetpack-mu-wpcom, which is not a dependency; guarded by function_exists(). |
| 111 | $is_pattern_source = function_exists( 'wpcom_has_blog_sticker' ) && wpcom_has_blog_sticker( 'block-patterns-source-site', $site_id ); |
| 112 | if ( isset( $_GET['theme'] ) || $is_theme_demo || $is_pattern_source ) { |
| 113 | return; |
| 114 | } |
| 115 | |
| 116 | // Don't show on Customizer previews. |
| 117 | if ( isset( $_GET['customize_theme'] ) || isset( $_GET['customize_changeset_uuid'] ) ) { |
| 118 | return; |
| 119 | } |
| 120 | // phpcs:enable |
| 121 | |
| 122 | // Don't show on WordPress mobile apps. |
| 123 | $user_agent = isset( $_SERVER['HTTP_USER_AGENT'] ) ? sanitize_text_field( wp_unslash( $_SERVER['HTTP_USER_AGENT'] ) ) : ''; |
| 124 | if ( $user_agent && preg_match( '/wp-(android|iphone)/', $user_agent ) ) { |
| 125 | return; |
| 126 | } |
| 127 | |
| 128 | // Don't show on Landpack blogs, unless user is a member of the blog. |
| 129 | if ( defined( 'WPCOM_LANDPACK_BLOG_IDS' ) && in_array( $site_id, (array) WPCOM_LANDPACK_BLOG_IDS, true ) && ! is_user_member_of_blog( $current_user->ID, $site_id ) ) { |
| 130 | return; |
| 131 | } |
| 132 | |
| 133 | // Render this in the user's language. |
| 134 | self::switch_to_user_locale(); |
| 135 | |
| 136 | $status_message = false; |
| 137 | // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Status flag set by the subscribe.wordpress.com redirect. |
| 138 | $blogsub = isset( $_GET['blogsub'] ) ? sanitize_key( wp_unslash( $_GET['blogsub'] ) ) : ''; |
| 139 | switch ( $blogsub ) { |
| 140 | case 'confirming': |
| 141 | $status_message = '<h3>' . __( 'Thanks', 'jetpack-newsletter' ) . '</h3>'; |
| 142 | $status_message .= '<div>' . |
| 143 | wp_kses( |
| 144 | sprintf( |
| 145 | /* translators: %s is the URL of the support contact page. */ |
| 146 | __( 'You’ll get an email with a link to confirm your subscription. If it doesn’t arrive, please <a href="%s">contact us</a>.', 'jetpack-newsletter' ), |
| 147 | esc_url( self::localized_url( 'https://wordpress.com/support/contact/' ) ) |
| 148 | ), |
| 149 | array( |
| 150 | 'a' => array( |
| 151 | 'href' => array(), |
| 152 | ), |
| 153 | ) |
| 154 | ) . |
| 155 | '</div>'; |
| 156 | break; |
| 157 | case 'subscribed': |
| 158 | $status_message = '<div>' . __( 'You’re already subscribed to this site!', 'jetpack-newsletter' ) . '</div>'; |
| 159 | break; |
| 160 | case 'flooded': |
| 161 | $status_message = |
| 162 | '<div>' . |
| 163 | sprintf( |
| 164 | /* translators: %s is a link with its text (Subscription Manager) translated separately */ |
| 165 | __( 'You already have several pending email subscriptions. Approve or delete a few through your %s before attempting to subscribe to more blogs.', 'jetpack-newsletter' ), |
| 166 | '<a href="https://subscribe.wordpress.com/">' . __( 'Subscription Manager', 'jetpack-newsletter' ) . '</a>' |
| 167 | ) . |
| 168 | '</div>'; |
| 169 | break; |
| 170 | case 'pending': |
| 171 | $status_message = '<div>' . __( 'You already have a pending subscription, we just sent you another email, click the link or <a href="https://en.support.wordpress.com/contact/">contact us</a> if you don’t get it', 'jetpack-newsletter' ) . '</div>'; |
| 172 | break; |
| 173 | case 'confirmed': |
| 174 | $status_message = '<div>' . __( 'Congrats, you’re subscribed! You’ll get an email with the details of your subscription and an unsubscribe link', 'jetpack-newsletter' ) . '</div>'; |
| 175 | break; |
| 176 | } |
| 177 | |
| 178 | // VIP: Disable functionality on sites that have logged_out follow set to false. |
| 179 | if ( ! is_user_logged_in() && self::logged_out_follow_disabled() ) { |
| 180 | self::restore_locale(); |
| 181 | return; |
| 182 | } |
| 183 | |
| 184 | $http_host = isset( $_SERVER['HTTP_HOST'] ) ? sanitize_text_field( wp_unslash( $_SERVER['HTTP_HOST'] ) ) : ''; |
| 185 | |
| 186 | // Data to pass to the JS. |
| 187 | $actionbar_info = array( |
| 188 | 'siteID' => $site_id, |
| 189 | 'postID' => is_singular() ? get_the_ID() : 0, |
| 190 | 'siteURL' => get_option( 'home' ), |
| 191 | 'xhrURL' => esc_url_raw( ( is_ssl() ? 'https://' : 'http://' ) . $http_host . '/wp-admin/admin-ajax.php' ), |
| 192 | 'nonce' => wp_create_nonce( 'manage_subscription' ), |
| 193 | 'isLoggedIn' => is_user_logged_in(), |
| 194 | 'statusMessage' => $status_message, |
| 195 | 'subsEmailDefault' => self::email_default( $current_user ), |
| 196 | 'proxyScriptUrl' => 'https://s0.wp.com/wp-content/js/wpcom-proxy-request.js?ver=20211021', |
| 197 | ); |
| 198 | |
| 199 | if ( is_singular() ) { |
| 200 | $actionbar_info['shortlink'] = wp_get_shortlink( get_the_ID() ); |
| 201 | } |
| 202 | |
| 203 | $actionbar_info['i18n'] = array( |
| 204 | 'followedText' => __( 'New posts from this site will now appear in your <a href="https://wordpress.com/reader">Reader</a>', 'jetpack-newsletter' ), |
| 205 | 'foldBar' => __( 'Collapse this bar', 'jetpack-newsletter' ), |
| 206 | 'unfoldBar' => __( 'Expand this bar', 'jetpack-newsletter' ), |
| 207 | 'shortLinkCopied' => __( 'Copied to clipboard.', 'jetpack-newsletter' ), |
| 208 | ); |
| 209 | |
| 210 | // Switch back to site language. |
| 211 | self::restore_locale(); |
| 212 | |
| 213 | // An alias handle with no src, so wp_localize_script() prints the data without a script tag. |
| 214 | // phpcs:disable WordPress.WP.EnqueuedResourceParameters.MissingVersion |
| 215 | wp_register_script( 'wpcom-actionbar-placeholder', '', array(), null, false ); |
| 216 | wp_localize_script( 'wpcom-actionbar-placeholder', 'actionbardata', $actionbar_info ); |
| 217 | wp_enqueue_script( 'wpcom-actionbar-placeholder' ); |
| 218 | // phpcs:enable |
| 219 | |
| 220 | // Defer loading the actionbar resources. |
| 221 | add_action( 'wp_footer', array( __CLASS__, 'footer' ) ); |
| 222 | } |
| 223 | |
| 224 | /** |
| 225 | * Print the bar's markup, then a loader that appends its CSS and JS after DOMContentLoaded. |
| 226 | */ |
| 227 | public static function footer() { |
| 228 | $is_rtl = function_exists( 'wpcom_is_locale_rtl' ) ? wpcom_is_locale_rtl( get_user_locale() ) : is_rtl(); |
| 229 | self::html( $is_rtl ); |
| 230 | |
| 231 | $asset_path = dirname( __DIR__ ) . '/build/action-bar.asset.php'; |
| 232 | $asset_file = file_exists( $asset_path ) ? include $asset_path : array(); |
| 233 | $version = is_array( $asset_file ) && ! empty( $asset_file['version'] ) ? $asset_file['version'] : Settings::PACKAGE_VERSION; |
| 234 | |
| 235 | // One stylesheet for both directions: it uses logical properties and the bar carries its own dir attribute. |
| 236 | $css_url = wp_json_encode( add_query_arg( 'ver', $version, plugins_url( '../build/action-bar.css', __FILE__ ) ), JSON_HEX_TAG | JSON_HEX_AMP | JSON_HEX_APOS | JSON_HEX_QUOT ); |
| 237 | $js_url = wp_json_encode( add_query_arg( 'ver', $version, plugins_url( '../build/action-bar.js', __FILE__ ) ), JSON_HEX_TAG | JSON_HEX_AMP | JSON_HEX_APOS | JSON_HEX_QUOT ); |
| 238 | |
| 239 | wp_print_inline_script_tag( |
| 240 | 'window.addEventListener( "DOMContentLoaded", function () { |
| 241 | var link = document.createElement( "link" ); |
| 242 | link.href = ' . $css_url . '; |
| 243 | link.rel = "stylesheet"; |
| 244 | document.head.appendChild( link ); |
| 245 | |
| 246 | var script = document.createElement( "script" ); |
| 247 | script.src = ' . $js_url . '; |
| 248 | document.body.appendChild( script ); |
| 249 | } );' |
| 250 | ); |
| 251 | } |
| 252 | |
| 253 | /** |
| 254 | * Where the bar's post stats link goes. |
| 255 | * |
| 256 | * @since $$next-version$$ |
| 257 | * |
| 258 | * @param int $post_id The post. |
| 259 | * @param int $site_id The site's blog ID. |
| 260 | * @param string $site_slug The site slug in Calypso URLs. |
| 261 | * @param bool $use_calypso_links Whether the site links to Calypso instead of wp-admin. |
| 262 | * @return string |
| 263 | */ |
| 264 | public static function get_post_stats_url( $post_id, $site_id, $site_slug, $use_calypso_links ) { |
| 265 | $url = $use_calypso_links |
| 266 | ? sprintf( 'https://wordpress.com/stats/post/%d/%s', $post_id, $site_slug ) |
| 267 | : admin_url( sprintf( 'admin.php?page=stats#!/stats/post/%d/%d', $post_id, $site_id ) ); |
| 268 | |
| 269 | /** This filter is documented in projects/packages/stats-admin/src/class-admin-bar.php */ |
| 270 | return apply_filters( |
| 271 | 'jetpack_stats_url', |
| 272 | $url, |
| 273 | array( |
| 274 | 'view' => 'post', |
| 275 | 'id' => $post_id, |
| 276 | ) |
| 277 | ); |
| 278 | } |
| 279 | |
| 280 | /** |
| 281 | * Whether this is a WordPress.com VIP site. False anywhere the wpcom helper is missing. |
| 282 | * |
| 283 | * @return bool |
| 284 | */ |
| 285 | private static function is_vip() { |
| 286 | return function_exists( 'wpcom_is_vip' ) && wpcom_is_vip(); |
| 287 | } |
| 288 | |
| 289 | /** |
| 290 | * Localize a WordPress.com URL for the current user where wpcom can; otherwise return it as is. |
| 291 | * |
| 292 | * @param string $url A wordpress.com URL. |
| 293 | * @return string |
| 294 | */ |
| 295 | private static function localized_url( $url ) { |
| 296 | return function_exists( 'localized_wpcom_url' ) ? localized_wpcom_url( $url ) : $url; |
| 297 | } |
| 298 | |
| 299 | /** |
| 300 | * Whether the user collapsed the bar. A wpcom user attribute on Simple, user meta elsewhere. |
| 301 | * |
| 302 | * @param int $user_id User ID. |
| 303 | * @return bool |
| 304 | */ |
| 305 | private static function is_folded( $user_id ) { |
| 306 | if ( function_exists( 'get_user_attribute' ) ) { |
| 307 | return (bool) get_user_attribute( $user_id, 'is_actionbar_folded' ); |
| 308 | } |
| 309 | return (bool) get_user_meta( $user_id, 'is_actionbar_folded', true ); |
| 310 | } |
| 311 | |
| 312 | /** |
| 313 | * Remember or forget that the user collapsed the bar. |
| 314 | * |
| 315 | * @param int $user_id User ID. |
| 316 | * @param bool $folded Whether the bar is collapsed. |
| 317 | */ |
| 318 | private static function set_folded( $user_id, $folded ) { |
| 319 | if ( function_exists( 'update_user_attribute' ) && function_exists( 'delete_user_attribute' ) ) { |
| 320 | if ( $folded ) { |
| 321 | update_user_attribute( $user_id, 'is_actionbar_folded', 1 ); |
| 322 | } else { |
| 323 | delete_user_attribute( $user_id, 'is_actionbar_folded' ); |
| 324 | } |
| 325 | return; |
| 326 | } |
| 327 | if ( $folded ) { |
| 328 | update_user_meta( $user_id, 'is_actionbar_folded', 1 ); |
| 329 | } else { |
| 330 | delete_user_meta( $user_id, 'is_actionbar_folded' ); |
| 331 | } |
| 332 | } |
| 333 | |
| 334 | /** |
| 335 | * Whether logged-out visitors should get no bar and no follow actions. |
| 336 | * |
| 337 | * @return bool |
| 338 | */ |
| 339 | private static function logged_out_follow_disabled() { |
| 340 | $settings = get_option( 'subscription_options' ); |
| 341 | $disabled = self::is_vip() && ( ! isset( $settings['loggedoutfollow'] ) || 'off' === $settings['loggedoutfollow'] ); |
| 342 | |
| 343 | /** |
| 344 | * Filters whether logged-out visitors get the bar and its follow actions. |
| 345 | * |
| 346 | * @since 0.17.0 |
| 347 | * |
| 348 | * @param bool $disabled Whether to disable. Defaults to true on VIP sites with logged-out follow off. |
| 349 | */ |
| 350 | return (bool) apply_filters( 'wpcom_disable_logged_out_follow', $disabled ); |
| 351 | } |
| 352 | |
| 353 | /** |
| 354 | * Whether the site has published enough posts for a Subscribe button to make sense. |
| 355 | * |
| 356 | * @return bool |
| 357 | */ |
| 358 | private static function has_enough_posts() { |
| 359 | $has_enough_posts = get_transient( self::ENOUGH_POSTS_TRANSIENT ); |
| 360 | if ( false === $has_enough_posts ) { |
| 361 | // Stored as 1/0: a cached false would read as a cache miss. |
| 362 | $has_enough_posts = (int) wp_count_posts( 'post' )->publish >= 2 ? 1 : 0; |
| 363 | set_transient( self::ENOUGH_POSTS_TRANSIENT, $has_enough_posts, DAY_IN_SECONDS ); |
| 364 | } |
| 365 | |
| 366 | return (bool) $has_enough_posts; |
| 367 | } |
| 368 | |
| 369 | /** |
| 370 | * Clear the cached Subscribe answer when a post enters or leaves the published state. |
| 371 | * |
| 372 | * @since $$next-version$$ |
| 373 | * |
| 374 | * @param string $new_status New post status. |
| 375 | * @param string $old_status Old post status. |
| 376 | * @param \WP_Post|mixed $post Post object. |
| 377 | */ |
| 378 | public static function flush_published_posts_count( $new_status, $old_status, $post ) { |
| 379 | if ( ! $post instanceof \WP_Post ) { |
| 380 | // Some callers fire the action without a populated post object (e.g. failed get_post lookups). |
| 381 | return; |
| 382 | } |
| 383 | |
| 384 | if ( |
| 385 | 'post' === $post->post_type |
| 386 | && $new_status !== $old_status |
| 387 | && ( 'publish' === $new_status || 'publish' === $old_status ) |
| 388 | ) { |
| 389 | delete_transient( self::ENOUGH_POSTS_TRANSIENT ); |
| 390 | } |
| 391 | } |
| 392 | |
| 393 | /** |
| 394 | * Switch to the current user's locale, if logged in. |
| 395 | */ |
| 396 | private static function switch_to_user_locale() { |
| 397 | if ( ! is_user_logged_in() ) { |
| 398 | return; |
| 399 | } |
| 400 | if ( function_exists( 'wpcom_switch_to_user_locale' ) ) { |
| 401 | wpcom_switch_to_user_locale( get_current_user_id() ); |
| 402 | } else { |
| 403 | switch_to_user_locale( get_current_user_id() ); |
| 404 | } |
| 405 | } |
| 406 | |
| 407 | /** |
| 408 | * Undo switch_to_user_locale(). |
| 409 | */ |
| 410 | private static function restore_locale() { |
| 411 | if ( ! is_user_logged_in() ) { |
| 412 | return; |
| 413 | } |
| 414 | if ( function_exists( 'wpcom_restore_current_locale' ) ) { |
| 415 | wpcom_restore_current_locale(); |
| 416 | } else { |
| 417 | restore_previous_locale(); |
| 418 | } |
| 419 | } |
| 420 | |
| 421 | /** |
| 422 | * The email delivery frequency a new subscription defaults to for this user. |
| 423 | * |
| 424 | * @param \WP_User $current_user The current user. |
| 425 | * @return string One of instantly, daily, weekly, never. |
| 426 | */ |
| 427 | private static function email_default( $current_user ) { |
| 428 | if ( ! empty( $current_user->subs_email_default ) ) { |
| 429 | return $current_user->subs_email_default; |
| 430 | } |
| 431 | if ( function_exists( 'wpcom_subs_get_subscription_delivery_email_default' ) ) { |
| 432 | return wpcom_subs_get_subscription_delivery_email_default(); |
| 433 | } |
| 434 | return 'instantly'; |
| 435 | } |
| 436 | |
| 437 | /** |
| 438 | * Markup for one of the WordPress icons the bar uses, inlined from @wordpress/icons. |
| 439 | * |
| 440 | * Icons inherit their color from CSS `color`, like the library's own Icon component. |
| 441 | * |
| 442 | * @param string $name Icon name from the WordPress icon library. |
| 443 | * @param int $size Rendered width and height in pixels. |
| 444 | * @return string Empty for an unknown name. |
| 445 | */ |
| 446 | private static function icon_markup( $name, $size = 24 ) { |
| 447 | $fill_icons = array( |
| 448 | 'bell' => '<path fill-rule="evenodd" clip-rule="evenodd" d="M17 11.5c0 1.353.17 2.368.976 3 .266.209.602.376 1.024.5v1H5v-1c.422-.124.757-.291 1.024-.5.806-.632.976-1.647.976-3V9c0-2.8 2.2-5 5-5s5 2.2 5 5v2.5ZM15.5 9v2.5c0 .93.066 1.98.515 2.897l.053.103H7.932a4.018 4.018 0 0 0 .053-.103c.449-.917.515-1.967.515-2.897V9c0-1.972 1.528-3.5 3.5-3.5s3.5 1.528 3.5 3.5Zm-5.492 9.008c0-.176.023-.346.065-.508h3.854A1.996 1.996 0 0 1 12 20c-1.1 0-1.992-.892-1.992-1.992Z"/>', |
| 449 | 'copy' => '<path fill-rule="evenodd" clip-rule="evenodd" d="M5 4.5h11a.5.5 0 0 1 .5.5v11a.5.5 0 0 1-.5.5H5a.5.5 0 0 1-.5-.5V5a.5.5 0 0 1 .5-.5ZM3 5a2 2 0 0 1 2-2h11a2 2 0 0 1 2 2v11a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V5Zm17 3v10.75c0 .69-.56 1.25-1.25 1.25H6v1.5h12.75a2.75 2.75 0 0 0 2.75-2.75V8H20Z"/>', |
| 450 | 'external' => '<path d="M19.5 4.5h-7V6h4.44l-5.97 5.97 1.06 1.06L18 7.06v4.44h1.5v-7Zm-13 1a2 2 0 0 0-2 2v10a2 2 0 0 0 2 2h10a2 2 0 0 0 2-2v-3H17v3a.5.5 0 0 1-.5.5h-10a.5.5 0 0 1-.5-.5v-10a.5.5 0 0 1 .5-.5h3V5.5h-3Z"/>', |
| 451 | 'comment' => '<path d="M18 4H6c-1.1 0-2 .9-2 2v12.9c0 .6.5 1.1 1.1 1.1.3 0 .5-.1.8-.3L8.5 17H18c1.1 0 2-.9 2-2V6c0-1.1-.9-2-2-2zm.5 11c0 .3-.2.5-.5.5H7.9l-2.4 2.4V6c0-.3.2-.5.5-.5h12c.3 0 .5.2.5.5v9z"/>', |
| 452 | 'more-horizontal' => '<path d="M11 13h2v-2h-2v2zm-6 0h2v-2H5v2zm12-2v2h2v-2h-2z"/>', |
| 453 | 'pencil' => '<path d="m19 7-3-3-8.5 8.5-1 4 4-1L19 7Zm-7 11.5H5V20h7v-1.5Z"/>', |
| 454 | 'reusable-block' => '<path d="M7 7.2h8.2L13.5 9l1.1 1.1 3.6-3.6-3.5-4-1.1 1 1.9 2.3H7c-.9 0-1.7.3-2.3.9-1.4 1.5-1.4 4.2-1.4 5.6v.2h1.5v-.3c0-1.1 0-3.5 1-4.5.3-.3.7-.5 1.2-.5zm13.8 4V11h-1.5v.3c0 1.1 0 3.5-1 4.5-.3.3-.7.5-1.3.5H8.8l1.7-1.7-1.1-1.1L5.9 17l3.5 4 1.1-1-1.9-2.3H17c.9 0 1.7-.3 2.3-.9 1.5-1.4 1.5-4.2 1.5-5.6z"/>', |
| 455 | 'shield' => '<path fill-rule="evenodd" clip-rule="evenodd" d="M12 3.176l6.75 3.068v4.574c0 3.9-2.504 7.59-6.035 8.755a2.283 2.283 0 01-1.43 0c-3.53-1.164-6.035-4.856-6.035-8.755V6.244L12 3.176zM6.75 7.21v3.608c0 3.313 2.145 6.388 5.005 7.33.159.053.331.053.49 0 2.86-.942 5.005-4.017 5.005-7.33V7.21L12 4.824 6.75 7.21z"/>', |
| 456 | ); |
| 457 | // These are drawn with strokes, not fills, in the library. |
| 458 | $stroke_icons = array( |
| 459 | 'chart-bar' => '<path d="M6.75 20V10M12 20V5M17.25 20V14" vector-effect="non-scaling-stroke"/>', |
| 460 | 'check' => '<path d="M7 12L10 15L17 8" vector-effect="non-scaling-stroke"/>', |
| 461 | ); |
| 462 | |
| 463 | if ( isset( $fill_icons[ $name ] ) ) { |
| 464 | $attributes = 'fill="currentColor"'; |
| 465 | $markup = $fill_icons[ $name ]; |
| 466 | } elseif ( isset( $stroke_icons[ $name ] ) ) { |
| 467 | $attributes = 'style="fill: none" stroke="currentColor" stroke-width="1.5"'; |
| 468 | $markup = $stroke_icons[ $name ]; |
| 469 | } else { |
| 470 | return ''; |
| 471 | } |
| 472 | |
| 473 | return sprintf( |
| 474 | '<svg class="actnbr-icon actnbr-icon-%1$s" width="%2$d" height="%2$d" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false" %3$s>%4$s</svg>', |
| 475 | esc_attr( $name ), |
| 476 | (int) $size, |
| 477 | $attributes, |
| 478 | $markup |
| 479 | ); |
| 480 | } |
| 481 | |
| 482 | /** |
| 483 | * Print one of the bar's icons. |
| 484 | * |
| 485 | * @param string $name Icon name from the WordPress icon library. |
| 486 | * @param int $size Rendered width and height in pixels. |
| 487 | */ |
| 488 | private static function icon( $name, $size = 24 ) { |
| 489 | echo self::icon_markup( $name, $size ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Static SVG markup, name escaped in the builder. |
| 490 | } |
| 491 | |
| 492 | /** |
| 493 | * Markup for one item of the ⋯ menu. |
| 494 | * |
| 495 | * @param array $args Item arguments: href, label, class (stats hook), icon (right-edge icon name), blank (open in a new tab), before (trusted markup before the label), role (defaults to menuitem). |
| 496 | * @return string |
| 497 | */ |
| 498 | private static function menu_item( $args ) { |
| 499 | $args = wp_parse_args( |
| 500 | $args, |
| 501 | array( |
| 502 | 'href' => '', |
| 503 | 'label' => '', |
| 504 | 'class' => '', |
| 505 | 'icon' => '', |
| 506 | 'blank' => false, |
| 507 | 'before' => '', |
| 508 | 'role' => 'menuitem', |
| 509 | ) |
| 510 | ); |
| 511 | |
| 512 | return sprintf( |
| 513 | '<a%1$s class="actnbr-menu__item %2$s" href="%3$s"%4$s>%5$s<span class="actnbr-menu__label">%6$s</span>%7$s</a>', |
| 514 | $args['role'] ? ' role="' . esc_attr( $args['role'] ) . '"' : '', |
| 515 | esc_attr( $args['class'] ), |
| 516 | esc_url( $args['href'] ), |
| 517 | $args['blank'] ? ' target="_blank" rel="noopener noreferrer"' : '', |
| 518 | $args['before'], // Caller-built markup, escaped at the source. |
| 519 | esc_html( $args['label'] ), |
| 520 | $args['icon'] ? '<span class="actnbr-menu__icon">' . self::icon_markup( $args['icon'], 18 ) . '</span>' : '' |
| 521 | ); |
| 522 | } |
| 523 | |
| 524 | /** |
| 525 | * Print a menu group if it has any items. |
| 526 | * |
| 527 | * @param string[] $items Rendered items; empty strings are skipped. |
| 528 | */ |
| 529 | private static function menu_group( array $items ) { |
| 530 | $items = array_filter( $items ); |
| 531 | if ( ! $items ) { |
| 532 | return; |
| 533 | } |
| 534 | echo '<div class="actnbr-menu__group" role="group">' . implode( '', $items ) . '</div>'; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Items are escaped where built. |
| 535 | } |
| 536 | |
| 537 | /** |
| 538 | * The site's blavatar image markup, or an empty string. |
| 539 | * |
| 540 | * @return string |
| 541 | */ |
| 542 | private static function blavatar() { |
| 543 | if ( ! function_exists( 'get_blavatar' ) ) { |
| 544 | return ''; |
| 545 | } |
| 546 | $blavatar_img = get_blavatar( get_option( 'siteurl' ), 50, Assets::staticize_subdomain( 'https://en.wordpress.com/i/logo/wpcom-gray-white.png' ) ); // phpcs:ignore WPCOM.I18nRules.LocalizedUrl.UnlocalizedUrl |
| 547 | if ( str_starts_with( $blavatar_img, '<img alt' ) ) { |
| 548 | $blavatar_img = "<img loading='lazy' alt" . substr( $blavatar_img, 8 ); |
| 549 | } |
| 550 | return $blavatar_img; |
| 551 | } |
| 552 | |
| 553 | /** |
| 554 | * Print the site title row at the top of the Subscribe popover. |
| 555 | * |
| 556 | * @param string $site_url Site home URL. |
| 557 | * @param string $site_name Site title. |
| 558 | * @param string $blavatar Blavatar image markup, or empty. |
| 559 | */ |
| 560 | private static function site_title( $site_url, $site_name, $blavatar ) { |
| 561 | $item = self::menu_item( |
| 562 | array( |
| 563 | 'href' => $site_url, |
| 564 | 'label' => $site_name, |
| 565 | 'class' => 'actnbr-sitename', |
| 566 | 'before' => $blavatar, |
| 567 | 'role' => '', |
| 568 | ) |
| 569 | ); |
| 570 | echo '<div class="actnbr-panel__group">' . $item . '</div>'; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Escaped where built. |
| 571 | } |
| 572 | |
| 573 | /** |
| 574 | * Print the Subscribe and Subscribed links; only one is visible at a time. |
| 575 | * |
| 576 | * @param bool $is_following Whether the current user already subscribes to this site. |
| 577 | */ |
| 578 | private static function follow_links( $is_following ) { |
| 579 | ?> |
| 580 | <a class="actnbr-action actnbr-actn-follow <?php echo $is_following ? ' no-display' : ''; ?>" href="" role="button" aria-haspopup="dialog" aria-expanded="false"> |
| 581 | <?php self::icon( 'bell', 20 ); ?> |
| 582 | <span><?php esc_html_e( 'Subscribe', 'jetpack-newsletter' ); ?></span> |
| 583 | </a> |
| 584 | <a class="actnbr-action actnbr-actn-following <?php echo $is_following ? '' : ' no-display'; ?>" href="" role="button"> |
| 585 | <?php self::icon( 'check', 20 ); ?> |
| 586 | <span><?php esc_html_e( 'Subscribed', 'jetpack-newsletter' ); ?></span> |
| 587 | </a> |
| 588 | <?php |
| 589 | } |
| 590 | |
| 591 | /** |
| 592 | * Print the bar's markup. Hidden inline until the JS reveals it. |
| 593 | * |
| 594 | * @param bool $is_rtl Whether to render right-to-left. |
| 595 | */ |
| 596 | private static function html( $is_rtl ) { |
| 597 | $current_user = wp_get_current_user(); |
| 598 | $site_id = get_current_blog_id(); |
| 599 | |
| 600 | global $current_blog; |
| 601 | |
| 602 | $is_logged_in = is_user_logged_in(); |
| 603 | $is_member = $is_logged_in && is_user_member_of_blog( $current_user->ID, $site_id ); |
| 604 | |
| 605 | // Render this in the user's language. |
| 606 | self::switch_to_user_locale(); |
| 607 | |
| 608 | $is_suspended = function_exists( 'is_suspended' ) && is_suspended( $site_id ); |
| 609 | |
| 610 | /* |
| 611 | * Follow actions are dropped only on a "static" site (front page set, under two posts) viewed by |
| 612 | * someone who is not a member. Deleted, spam and archived sites never reach this function. |
| 613 | */ |
| 614 | $can_follow = true; |
| 615 | if ( |
| 616 | ! $is_suspended && |
| 617 | ! in_array( $site_id, (array) apply_filters( 'loggedout_follow_disabled_blog_id', array( 1 ) ), true ) && |
| 618 | ! apply_filters( 'loggedout_follow_disabled', false ) && |
| 619 | ! self::logged_out_follow_disabled() && |
| 620 | ( ! $is_member || function_exists( 'is_automattician' ) && is_automattician( $current_user->ID ) ) && |
| 621 | ( 'page' === get_option( 'show_on_front' ) && get_option( 'post_count' ) < 2 ) |
| 622 | ) { |
| 623 | $can_follow = false; |
| 624 | } |
| 625 | |
| 626 | $login_url = add_query_arg( 'redirect_to', get_permalink(), 'https://wordpress.com/log-in' ); |
| 627 | $login_url = add_query_arg( 'signup_flow', 'account', $login_url ); |
| 628 | if ( |
| 629 | ! empty( $current_blog->primary_redirect ) |
| 630 | && ! str_contains( $current_blog->primary_redirect, '.wordpress.com' ) |
| 631 | ) { |
| 632 | // phpcs:ignore WPCOM.I18nRules.LocalizedUrl.UnlocalizedUrl |
| 633 | $redirect_to = add_query_arg( 'back', rawurlencode( get_permalink() ? get_permalink() : home_url() ), 'https://r-login.wordpress.com/remote-login.php?action=link' ); |
| 634 | $login_url = add_query_arg( 'redirect_to', rawurlencode( $redirect_to ), 'https://wordpress.com/log-in' ); |
| 635 | } |
| 636 | |
| 637 | $site_name = get_option( 'blogname' ); |
| 638 | $site_url = get_option( 'home' ); |
| 639 | $site_host = wp_parse_url( $site_url, PHP_URL_HOST ); |
| 640 | $site_slug = ( new Status() )->get_site_suffix(); |
| 641 | $can_customize_site = $is_member && current_user_can( 'edit_theme_options' ); |
| 642 | $subscription_id = function_exists( 'wpcom_subs_is_subscribed' ) ? wpcom_subs_is_subscribed( |
| 643 | array( |
| 644 | 'user_id' => get_current_user_id(), |
| 645 | 'blog_id' => $site_id, |
| 646 | ) |
| 647 | ) : false; |
| 648 | $is_following = $subscription_id ? true : false; |
| 649 | $signup_url = 'https://wordpress.com/start/'; |
| 650 | $theme_slug = get_stylesheet(); |
| 651 | $theme_url = function_exists( 'wpcom_get_theme_showcase_url' ) ? wpcom_get_theme_showcase_url( $theme_slug ) : 'https://wordpress.com/theme/' . $theme_slug; |
| 652 | $is_singular = false; |
| 653 | $is_folded = $is_logged_in && self::is_folded( $current_user->ID ); |
| 654 | $feed_id = false; |
| 655 | if ( class_exists( 'FeedBag' ) ) { |
| 656 | $feed_id = \FeedBag::get_feed_id_for_blog_id( $site_id ); |
| 657 | } |
| 658 | $gdpr_applies = self::gdpr_applies(); |
| 659 | |
| 660 | // Fall back to the site's domain if the title is empty. |
| 661 | if ( empty( $site_name ) ) { |
| 662 | $site_name = function_exists( 'get_primary_redirect' ) ? get_primary_redirect() : $site_host; |
| 663 | } |
| 664 | |
| 665 | $post_id = 0; |
| 666 | $shortlink = ''; |
| 667 | $edit_link = ''; |
| 668 | $stats_link = ''; |
| 669 | $can_edit_post = false; |
| 670 | if ( is_singular() ) { |
| 671 | $is_singular = true; |
| 672 | $post_id = get_the_ID(); |
| 673 | $shortlink = wp_get_shortlink( $post_id ); |
| 674 | $can_edit_post = $is_member && current_user_can( 'edit_post', $post_id ); |
| 675 | |
| 676 | /* |
| 677 | * Use the wp admin editor for VIPs (since they have custom editor |
| 678 | * plugins), or for super admins who are not members of the blog (until |
| 679 | * user-switching is implemented in Calypso). |
| 680 | */ |
| 681 | $edit_link = add_query_arg( |
| 682 | array( |
| 683 | 'post' => $post_id, |
| 684 | 'action' => 'edit', |
| 685 | ), |
| 686 | admin_url( 'post.php' ) |
| 687 | ); |
| 688 | |
| 689 | $post_type = get_post_type(); |
| 690 | |
| 691 | // @phan-suppress-next-line PhanUndeclaredFunction -- Defined by jetpack-mu-wpcom, which is not a dependency; guarded by function_exists(). |
| 692 | $should_use_calypso_links = empty( $post_type ) || function_exists( 'wpcom_should_disable_calypso_links' ) && ! wpcom_should_disable_calypso_links( 'edit.php?post_type=' . $post_type ); |
| 693 | |
| 694 | if ( $should_use_calypso_links && ! self::is_vip() && ( ! is_super_admin() || $is_member ) ) { |
| 695 | $path_prefix = null; |
| 696 | if ( in_array( $post_type, array( 'post', 'page' ), true ) ) { |
| 697 | $path_prefix = $post_type; |
| 698 | } elseif ( in_array( $post_type, apply_filters( 'rest_api_allowed_post_types', array( 'post', 'page', 'revision' ) ), true ) ) { |
| 699 | $path_prefix = sprintf( 'edit/%s', $post_type ); |
| 700 | } |
| 701 | |
| 702 | if ( $path_prefix ) { |
| 703 | $edit_link = sprintf( 'https://wordpress.com/%s/%s/%d', $path_prefix, $site_slug, $post_id ); |
| 704 | } |
| 705 | } |
| 706 | |
| 707 | $stats_link = self::get_post_stats_url( $post_id, $site_id, $site_slug, $should_use_calypso_links ); |
| 708 | } |
| 709 | |
| 710 | $referer = ''; |
| 711 | if ( isset( $_SERVER['HTTP_HOST'] ) && isset( $_SERVER['REQUEST_URI'] ) ) { |
| 712 | $referer = ( is_ssl() ? 'https' : 'http' ) . '://' . sanitize_text_field( wp_unslash( $_SERVER['HTTP_HOST'] ) ) . sanitize_text_field( wp_unslash( $_SERVER['REQUEST_URI'] ) ); |
| 713 | } |
| 714 | |
| 715 | $can_comment = is_single() && ! post_password_required( $post_id ) && comments_open( $post_id ); |
| 716 | $can_reblog = is_single() && self::can_reblog( $site_id, $post_id ); |
| 717 | $can_edit_current_view = $can_edit_post || $can_customize_site; |
| 718 | $show_follow = $can_follow && ! $can_edit_current_view && self::has_enough_posts(); |
| 719 | |
| 720 | $followers = ''; |
| 721 | if ( $show_follow && ! $is_logged_in ) { |
| 722 | $subscribers_total = function_exists( 'wpcom_subs_total_for_blog' ) ? wpcom_subs_total_for_blog() : 0; |
| 723 | if ( ! empty( $subscribers_total ) && $subscribers_total > 24 ) { |
| 724 | /* translators: %s: number of subscribers */ |
| 725 | $followers = sprintf( _n( 'Join %s other subscriber', 'Join %s other subscribers', $subscribers_total, 'jetpack-newsletter' ), number_format_i18n( $subscribers_total ) ); |
| 726 | } |
| 727 | } |
| 728 | |
| 729 | $blavatar = self::blavatar(); |
| 730 | |
| 731 | $classes = 'actnbr-' . str_replace( '/', '-', $theme_slug ); |
| 732 | if ( $is_folded ) { |
| 733 | $classes .= ' actnbr-folded'; |
| 734 | } |
| 735 | |
| 736 | $dir = $is_rtl ? 'rtl' : 'ltr'; |
| 737 | |
| 738 | ?> |
| 739 | <div id="actionbar" dir="<?php echo esc_attr( $dir ); ?>" style="display: none;" |
| 740 | class="<?php echo esc_attr( $classes ); ?>" role="region" aria-label="<?php esc_attr_e( 'Site actions', 'jetpack-newsletter' ); ?>"> |
| 741 | <span class="actnbr-live" aria-live="polite"></span> |
| 742 | <ul> |
| 743 | <?php |
| 744 | if ( $can_edit_post ) { |
| 745 | ?> |
| 746 | <li class="actnbr-btn actnbr-edit"> |
| 747 | <a href="<?php echo esc_url( $edit_link ); ?>"> |
| 748 | <?php self::icon( 'pencil', 20 ); ?> |
| 749 | <span><?php esc_html_e( 'Edit', 'jetpack-newsletter' ); ?></span> |
| 750 | </a> |
| 751 | </li> |
| 752 | <li class="actnbr-btn actnbr-stats"> |
| 753 | <a href="<?php echo esc_url( $stats_link ); ?>"> |
| 754 | <?php self::icon( 'chart-bar', 20 ); ?> |
| 755 | <span><?php esc_html_e( 'Stats', 'jetpack-newsletter' ); ?></span> |
| 756 | </a> |
| 757 | </li> |
| 758 | <?php |
| 759 | } |
| 760 | |
| 761 | if ( $can_comment && ! $can_edit_current_view ) { |
| 762 | ?> |
| 763 | <li class="actnbr-btn"> |
| 764 | <a class="actnbr-action actnbr-actn-comment" href="<?php echo esc_url( get_comments_link( $post_id ) ); ?>"> |
| 765 | <?php self::icon( 'comment', 20 ); ?> |
| 766 | <span><?php esc_html_e( 'Comment', 'jetpack-newsletter' ); ?> |
| 767 | </span> |
| 768 | </a> |
| 769 | </li> |
| 770 | <?php |
| 771 | } |
| 772 | |
| 773 | if ( $can_reblog && ! $can_edit_current_view ) { |
| 774 | ?> |
| 775 | <li class="actnbr-btn"> |
| 776 | <a class="actnbr-action actnbr-actn-reblog" href="" role="button"> |
| 777 | <?php self::icon( 'reusable-block', 20 ); ?><span><?php esc_html_e( 'Reblog', 'jetpack-newsletter' ); ?></span> |
| 778 | </a> |
| 779 | </li> |
| 780 | <?php |
| 781 | } |
| 782 | |
| 783 | if ( $show_follow ) { |
| 784 | ?> |
| 785 | <li class="actnbr-btn actnbr-hidden"> |
| 786 | <?php self::follow_links( $is_following ); ?> |
| 787 | <div class="actnbr-popover actnbr-panel actnbr-notice" id="follow-bubble" role="dialog" aria-label="<?php echo esc_attr( sprintf( /* translators: %s: site name */ __( 'Subscribe to %s', 'jetpack-newsletter' ), $site_name ) ); ?>"> |
| 788 | <div class="actnbr-follow-bubble"> |
| 789 | <?php self::site_title( $site_url, $site_name, $blavatar ); ?> |
| 790 | <?php |
| 791 | if ( $is_logged_in ) { |
| 792 | ?> |
| 793 | <div class="actnbr-panel__group actnbr-site-settings"> |
| 794 | <div class="actnbr-message no-display" aria-live="polite"></div> |
| 795 | <div class="actnbr-site-settings__setting"> |
| 796 | <span class="actnbr-site-settings__toggle"> |
| 797 | <input class="actnbr-site-settings__toggle__input" id="toggle-input-notify-posts" type="checkbox" role="switch" /> |
| 798 | <span class="actnbr-site-settings__toggle__track"></span> |
| 799 | <span class="actnbr-site-settings__toggle__thumb"></span> |
| 800 | </span> |
| 801 | <label for="toggle-input-notify-posts" class="components-toggle-control__label"> |
| 802 | <?php esc_html_e( 'Notify me of new posts', 'jetpack-newsletter' ); ?> |
| 803 | </label> |
| 804 | </div> |
| 805 | <p class="actnbr-site-settings__details"> |
| 806 | <?php esc_html_e( 'Receive web and mobile notifications for new posts from this site.', 'jetpack-newsletter' ); ?> |
| 807 | </p> |
| 808 | <div class="actnbr-site-settings__setting"> |
| 809 | <span class="actnbr-site-settings__toggle"> |
| 810 | <input class="actnbr-site-settings__toggle__input" id="toggle-input-email-posts" type="checkbox" role="switch" /> |
| 811 | <span class="actnbr-site-settings__toggle__track"></span> |
| 812 | <span class="actnbr-site-settings__toggle__thumb"></span> |
| 813 | </span> |
| 814 | <label for="toggle-input-email-posts" class="components-toggle-control__label"> |
| 815 | <?php esc_html_e( 'Email me new posts', 'jetpack-newsletter' ); ?> |
| 816 | </label> |
| 817 | </div> |
| 818 | <div class="actnbr-site-settings__details" id="email-new-posts-details"> |
| 819 | <ul class="segmented-control" role="radiogroup" aria-label="<?php esc_attr_e( 'Email frequency', 'jetpack-newsletter' ); ?>"> |
| 820 | <li class="segmented-control__item"> |
| 821 | <a class="segmented-control__link frequency-instantly" data-frequency="instantly" role="radio" aria-checked="false" tabindex="0"><?php esc_html_e( 'Instantly', 'jetpack-newsletter' ); ?></a> |
| 822 | </li> |
| 823 | <li class="segmented-control__item"> |
| 824 | <a class="segmented-control__link frequency-daily" data-frequency="daily" role="radio" aria-checked="false" tabindex="0"><?php esc_html_e( 'Daily', 'jetpack-newsletter' ); ?></a> |
| 825 | </li> |
| 826 | <li class="segmented-control__item"> |
| 827 | <a class="segmented-control__link frequency-weekly" data-frequency="weekly" role="radio" aria-checked="false" tabindex="0"><?php esc_html_e( 'Weekly', 'jetpack-newsletter' ); ?></a> |
| 828 | </li> |
| 829 | </ul> |
| 830 | </div> |
| 831 | <div class="actnbr-site-settings__setting"> |
| 832 | <span class="actnbr-site-settings__toggle"> |
| 833 | <input class="actnbr-site-settings__toggle__input" id="toggle-input-email-comments" type="checkbox" role="switch" /> |
| 834 | <span class="actnbr-site-settings__toggle__track"></span> |
| 835 | <span class="actnbr-site-settings__toggle__thumb"></span> |
| 836 | </span> |
| 837 | <label for="toggle-input-email-comments" class="components-toggle-control__label"> |
| 838 | <?php esc_html_e( 'Email me new comments', 'jetpack-newsletter' ); ?> |
| 839 | </label> |
| 840 | </div> |
| 841 | </div> |
| 842 | <?php |
| 843 | } else { |
| 844 | ?> |
| 845 | <div class="actnbr-panel__group"> |
| 846 | <div class="actnbr-message no-display" aria-live="polite"></div> |
| 847 | <form method="post" action="https://subscribe.wordpress.com" accept-charset="utf-8" class="no-display"> |
| 848 | <?php |
| 849 | if ( $followers ) { |
| 850 | ?> |
| 851 | <div class="actnbr-follow-count"><?php echo esc_html( $followers ); ?></div> |
| 852 | <?php |
| 853 | } |
| 854 | ?> |
| 855 | <input type="email" name="email" placeholder="<?php esc_attr_e( 'Want updates? Enter your email', 'jetpack-newsletter' ); ?>" class="actnbr-email-field" aria-label="<?php esc_attr_e( 'Email address', 'jetpack-newsletter' ); ?>" /> |
| 856 | <input type="hidden" name="action" value="subscribe" /> |
| 857 | <input type="hidden" name="blog_id" value="<?php echo esc_attr( (string) $site_id ); ?>" /> |
| 858 | <input type="hidden" name="source" value="<?php echo esc_url( $referer ); ?>" /> |
| 859 | <input type="hidden" name="sub-type" value="actionbar-follow" /> |
| 860 | <?php wp_nonce_field( 'blogsub_subscribe_' . $site_id, '_wpnonce', false, true ); ?> |
| 861 | <button type="submit" class="actnbr-button"><?php esc_html_e( 'Sign me up', 'jetpack-newsletter' ); ?></button> |
| 862 | </form> |
| 863 | <p class="actnbr-login-nudge"> |
| 864 | <?php |
| 865 | echo wp_kses( |
| 866 | /* translators: %s is a URL */ |
| 867 | sprintf( __( 'Have a WordPress.com account? <a href="%s">Log in now.</a>', 'jetpack-newsletter' ), esc_url( $login_url ) ), |
| 868 | array( |
| 869 | 'a' => array( |
| 870 | 'href' => array(), |
| 871 | ), |
| 872 | ) |
| 873 | ); |
| 874 | ?> |
| 875 | </p> |
| 876 | </div> |
| 877 | <?php |
| 878 | } |
| 879 | ?> |
| 880 | </div> |
| 881 | </div> |
| 882 | </li> |
| 883 | <?php |
| 884 | } |
| 885 | |
| 886 | /* |
| 887 | * The privacy/GDPR button is special as it relies on window.__tcfapi being in the browser window object as well. |
| 888 | * no-display is removed by JS if window.__tcfapi is present. |
| 889 | */ |
| 890 | if ( $gdpr_applies ) { |
| 891 | ?> |
| 892 | <li class="actnbr-btn no-display" onclick="javascript:__tcfapi( 'showUi' );"> |
| 893 | <a class="actnbr-action actnbr-actn-privacy" href="#" role="button"> |
| 894 | <?php self::icon( 'shield', 20 ); ?> |
| 895 | <span><?php esc_html_e( 'Privacy', 'jetpack-newsletter' ); ?> |
| 896 | </span> |
| 897 | </a> |
| 898 | </li> |
| 899 | <?php |
| 900 | } |
| 901 | ?> |
| 902 | <li class="actnbr-ellipsis actnbr-hidden"> |
| 903 | <button type="button" class="actnbr-more-toggle" aria-haspopup="true" aria-expanded="false" aria-label="<?php esc_attr_e( 'More options', 'jetpack-newsletter' ); ?>"> |
| 904 | <?php self::icon( 'more-horizontal', 24 ); ?> |
| 905 | </button> |
| 906 | <div class="actnbr-popover actnbr-menu" role="menu" aria-label="<?php esc_attr_e( 'Site options', 'jetpack-newsletter' ); ?>"> |
| 907 | <?php |
| 908 | // Site. |
| 909 | self::menu_group( |
| 910 | array( |
| 911 | self::menu_item( |
| 912 | array( |
| 913 | 'href' => $site_url, |
| 914 | 'label' => $site_name, |
| 915 | 'class' => 'actnbr-sitename', |
| 916 | 'before' => $blavatar, |
| 917 | ) |
| 918 | ), |
| 919 | ) |
| 920 | ); |
| 921 | |
| 922 | // This post or site. |
| 923 | $items = array(); |
| 924 | if ( $is_singular ) { |
| 925 | $items[] = sprintf( |
| 926 | '<a role="menuitem" class="actnbr-menu__item actnbr-shortlink" href="%1$s"><span class="actnbr-menu__label actnbr-shortlink__text">%2$s</span><span class="actnbr-menu__icon actnbr-shortlink__icon">%3$s</span><span class="actnbr-menu__icon actnbr-shortlink__icon-copied">%4$s</span></a>', |
| 927 | esc_url( $shortlink ), |
| 928 | esc_html__( 'Copy shortlink', 'jetpack-newsletter' ), |
| 929 | self::icon_markup( 'copy', 18 ), |
| 930 | self::icon_markup( 'check', 18 ) |
| 931 | ); |
| 932 | } |
| 933 | if ( $can_follow && $is_singular ) { |
| 934 | $items[] = self::menu_item( |
| 935 | array( |
| 936 | 'href' => 'https://wordpress.com/reader/blogs/' . (int) $site_id . '/posts/' . (int) $post_id, |
| 937 | 'label' => __( 'View post in Reader', 'jetpack-newsletter' ), |
| 938 | 'class' => 'actnbr-reader', |
| 939 | ) |
| 940 | ); |
| 941 | } |
| 942 | if ( $can_follow && ! $is_singular ) { |
| 943 | $items[] = self::menu_item( |
| 944 | array( |
| 945 | 'href' => 'https://wordpress.com/reader/' . ( $feed_id ? 'feeds/' . (int) $feed_id : 'blogs/' . (int) $site_id ), |
| 946 | 'label' => __( 'View site in Reader', 'jetpack-newsletter' ), |
| 947 | 'class' => 'actnbr-reader', |
| 948 | ) |
| 949 | ); |
| 950 | } |
| 951 | if ( $is_logged_in && ! $can_customize_site ) { |
| 952 | $items[] = self::menu_item( |
| 953 | array( |
| 954 | 'href' => $theme_url, |
| 955 | 'label' => __( 'Get theme', 'jetpack-newsletter' ) . ': ' . wp_get_theme()->get( 'Name' ), |
| 956 | 'class' => 'actnbr-theme', |
| 957 | ) |
| 958 | ); |
| 959 | } |
| 960 | self::menu_group( $items ); |
| 961 | |
| 962 | // Account. |
| 963 | $items = array(); |
| 964 | if ( $is_logged_in && $is_following ) { |
| 965 | $items[] = self::menu_item( |
| 966 | array( |
| 967 | 'href' => 'https://wordpress.com/read/subscriptions/' . (int) $subscription_id, |
| 968 | 'label' => __( 'Manage subscription', 'jetpack-newsletter' ), |
| 969 | 'class' => 'actnbr-follows', |
| 970 | ) |
| 971 | ); |
| 972 | } |
| 973 | if ( $is_logged_in && ! $is_following ) { |
| 974 | $items[] = self::menu_item( |
| 975 | array( |
| 976 | 'href' => 'https://wordpress.com/read/subscriptions?s=' . rawurlencode( (string) $site_host ), |
| 977 | 'label' => __( 'Manage subscriptions', 'jetpack-newsletter' ), |
| 978 | 'class' => 'actnbr-follows', |
| 979 | ) |
| 980 | ); |
| 981 | } |
| 982 | if ( ! $is_logged_in ) { |
| 983 | $items[] = self::menu_item( |
| 984 | array( |
| 985 | 'href' => 'https://subscribe.wordpress.com/', |
| 986 | 'label' => __( 'Manage subscriptions', 'jetpack-newsletter' ), |
| 987 | 'class' => 'actnbr-subs', |
| 988 | ) |
| 989 | ); |
| 990 | $items[] = self::menu_item( |
| 991 | array( |
| 992 | 'href' => $signup_url, |
| 993 | 'label' => __( 'Sign up', 'jetpack-newsletter' ), |
| 994 | 'class' => 'actnbr-signup', |
| 995 | ) |
| 996 | ); |
| 997 | $items[] = self::menu_item( |
| 998 | array( |
| 999 | 'href' => $login_url, |
| 1000 | 'label' => __( 'Log in', 'jetpack-newsletter' ), |
| 1001 | 'class' => 'actnbr-login', |
| 1002 | ) |
| 1003 | ); |
| 1004 | } |
| 1005 | self::menu_group( $items ); |
| 1006 | |
| 1007 | // Report. |
| 1008 | $items = array(); |
| 1009 | if ( ! $can_customize_site ) { |
| 1010 | $report_url = add_query_arg( |
| 1011 | 'report_url', |
| 1012 | $is_singular ? get_permalink( $post_id ) : $site_url, |
| 1013 | // phpcs:ignore WPCOM.I18nRules.LocalizedUrl.UnlocalizedUrl |
| 1014 | 'https://wordpress.com/abuse/' |
| 1015 | ); |
| 1016 | $items[] = self::menu_item( |
| 1017 | array( |
| 1018 | 'href' => $report_url, |
| 1019 | 'label' => __( 'Report this content', 'jetpack-newsletter' ), |
| 1020 | 'class' => 'flb-report', |
| 1021 | 'icon' => 'external', |
| 1022 | 'blank' => true, |
| 1023 | ) |
| 1024 | ); |
| 1025 | } |
| 1026 | self::menu_group( $items ); |
| 1027 | |
| 1028 | // Bar. |
| 1029 | $items = array(); |
| 1030 | if ( $is_logged_in || $can_follow ) { |
| 1031 | $items[] = self::menu_item( |
| 1032 | array( |
| 1033 | 'href' => '', |
| 1034 | 'label' => $is_folded ? __( 'Expand this bar', 'jetpack-newsletter' ) : __( 'Collapse this bar', 'jetpack-newsletter' ), |
| 1035 | 'class' => 'actnbr-fold', |
| 1036 | ) |
| 1037 | ); |
| 1038 | } |
| 1039 | if ( current_user_can( 'manage_options' ) ) { |
| 1040 | $items[] = self::menu_item( |
| 1041 | array( |
| 1042 | 'href' => self::localized_url( 'https://wordpress.com/support/action-bar/#show-or-hide-the-action-bar' ), |
| 1043 | 'label' => __( 'Turn off this bar', 'jetpack-newsletter' ), |
| 1044 | 'class' => 'actnbr-turn-off', |
| 1045 | 'icon' => 'external', |
| 1046 | 'blank' => true, |
| 1047 | ) |
| 1048 | ); |
| 1049 | } |
| 1050 | self::menu_group( $items ); |
| 1051 | ?> |
| 1052 | </div> |
| 1053 | </li> |
| 1054 | </ul> |
| 1055 | </div> |
| 1056 | <?php |
| 1057 | |
| 1058 | // Switch back to site language. |
| 1059 | self::restore_locale(); |
| 1060 | } |
| 1061 | |
| 1062 | /** |
| 1063 | * Whether the post can be reblogged and the viewer is allowed to reblog. |
| 1064 | * |
| 1065 | * @param int $site_id Blog ID. |
| 1066 | * @param int $post_id Post ID. |
| 1067 | * @return bool |
| 1068 | */ |
| 1069 | private static function can_reblog( $site_id, $post_id ) { |
| 1070 | if ( ! function_exists( 'wpr_can_reblog_post' ) || ! function_exists( 'wpcom_can_user_make_a_reblog' ) ) { |
| 1071 | return false; |
| 1072 | } |
| 1073 | $post_ok = (bool) wpr_can_reblog_post( $site_id, $post_id ); |
| 1074 | $user_ok = (bool) wpcom_can_user_make_a_reblog(); |
| 1075 | return $post_ok && $user_ok; |
| 1076 | } |
| 1077 | |
| 1078 | /** |
| 1079 | * Whether the WordAds consent manager is on and the visitor is in a GDPR region. |
| 1080 | * |
| 1081 | * @return bool |
| 1082 | */ |
| 1083 | private static function gdpr_applies() { |
| 1084 | if ( ! class_exists( 'WordAds_Consent_Management_Provider' ) && defined( 'WP_CONTENT_DIR' ) ) { |
| 1085 | $provider_file = WP_CONTENT_DIR . '/blog-plugins/wordads-classes/class-wordads-consent-management-provider.php'; |
| 1086 | if ( file_exists( $provider_file ) ) { |
| 1087 | require_once $provider_file; |
| 1088 | } |
| 1089 | } |
| 1090 | if ( ! class_exists( 'WordAds_Consent_Management_Provider' ) ) { |
| 1091 | return false; |
| 1092 | } |
| 1093 | // @phan-suppress-next-line PhanUndeclaredClassMethod -- wpcom-only class, guarded by class_exists above. |
| 1094 | return \WordAds_Consent_Management_Provider::is_feature_enabled() && \WordAds_Consent_Management_Provider::does_gdpr_apply(); |
| 1095 | } |
| 1096 | |
| 1097 | /** |
| 1098 | * Bump one of the whitelisted action bar stats. |
| 1099 | * |
| 1100 | * Logged-in clicks land in the `actionbar` MC stat, logged-out clicks in `actionbar_logged_out`. |
| 1101 | * |
| 1102 | * @param string $stat_value The stat to bump. |
| 1103 | */ |
| 1104 | private static function bump_stat( $stat_value ) { |
| 1105 | $whitelist = array( |
| 1106 | 'clicked_login_link', |
| 1107 | 'clicked_login_nudge', |
| 1108 | 'clicked_manage_subs_link', |
| 1109 | 'clicked_signup_link', |
| 1110 | 'clicked_site_title', |
| 1111 | 'clicked_stats', |
| 1112 | 'copied_shortlink', |
| 1113 | 'customized', |
| 1114 | 'edited', |
| 1115 | 'expanded', |
| 1116 | 'explored_theme', |
| 1117 | 'folded', |
| 1118 | 'followed', |
| 1119 | 'managed_following', |
| 1120 | 'privacy_clicked', |
| 1121 | 'reported_content', |
| 1122 | 'show_follow_form', |
| 1123 | 'show_more_menu', |
| 1124 | 'submit_follow_form', |
| 1125 | 'unfollowed', |
| 1126 | 'view_reader', |
| 1127 | 'comment_clicked', |
| 1128 | ); |
| 1129 | |
| 1130 | if ( ! in_array( $stat_value, $whitelist, true ) ) { |
| 1131 | return; |
| 1132 | } |
| 1133 | |
| 1134 | if ( ! function_exists( 'bump_stats_extras' ) ) { |
| 1135 | return; |
| 1136 | } |
| 1137 | |
| 1138 | $stat_name = 'actionbar'; |
| 1139 | |
| 1140 | if ( ! is_user_logged_in() ) { |
| 1141 | $stat_name .= '_logged_out'; |
| 1142 | } |
| 1143 | |
| 1144 | bump_stats_extras( $stat_name, $stat_value ); |
| 1145 | } |
| 1146 | |
| 1147 | /* |
| 1148 | * The three ajax handlers below deliberately skip nonce verification, as the wpcom original did. |
| 1149 | * Batcache can serve a logged-in visitor a page whose localized nonce was minted for user 0, so a |
| 1150 | * check would reject the request and the folded state would never save. The writes are limited to |
| 1151 | * the caller's own fold flag and anonymous counters. |
| 1152 | */ |
| 1153 | |
| 1154 | /** |
| 1155 | * Ajax: remember that the user collapsed the bar. |
| 1156 | * |
| 1157 | * @return never |
| 1158 | */ |
| 1159 | public static function fold() { |
| 1160 | self::bump_stat( 'folded' ); |
| 1161 | |
| 1162 | if ( is_user_logged_in() ) { |
| 1163 | self::set_folded( get_current_user_id(), true ); |
| 1164 | } |
| 1165 | |
| 1166 | die; |
| 1167 | } |
| 1168 | |
| 1169 | /** |
| 1170 | * Ajax: forget that the user collapsed the bar. |
| 1171 | * |
| 1172 | * @return never |
| 1173 | */ |
| 1174 | public static function unfold() { |
| 1175 | self::bump_stat( 'expanded' ); |
| 1176 | |
| 1177 | if ( is_user_logged_in() ) { |
| 1178 | self::set_folded( get_current_user_id(), false ); |
| 1179 | } |
| 1180 | |
| 1181 | die; |
| 1182 | } |
| 1183 | |
| 1184 | /** |
| 1185 | * Ajax: bump a click stat sent by the JS. |
| 1186 | * |
| 1187 | * @return never |
| 1188 | */ |
| 1189 | public static function ajax_stats() { |
| 1190 | // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Whitelisted counter bump; see the note above. |
| 1191 | $stat_value = isset( $_REQUEST['stat'] ) ? sanitize_key( wp_unslash( $_REQUEST['stat'] ) ) : ''; |
| 1192 | |
| 1193 | self::bump_stat( $stat_value ); |
| 1194 | |
| 1195 | die; |
| 1196 | } |
| 1197 | |
| 1198 | /** |
| 1199 | * Add the Action Bar visibility setting to the General settings page. |
| 1200 | * |
| 1201 | * Stored in the `wpcom_hide_action_bar` option. |
| 1202 | */ |
| 1203 | public static function settings_field() { |
| 1204 | add_settings_field( 'wpcom_hide_action_bar', __( 'Action Bar visibility', 'jetpack-newsletter' ), array( __CLASS__, 'settings_field_display' ), 'general', 'default', array( 'label_for' => 'wpcom_hide_action_bar' ) ); |
| 1205 | |
| 1206 | register_setting( 'general', 'wpcom_hide_action_bar' ); |
| 1207 | } |
| 1208 | |
| 1209 | /** |
| 1210 | * Render the `wpcom_hide_action_bar` checkbox. |
| 1211 | */ |
| 1212 | public static function settings_field_display() { |
| 1213 | ?> |
| 1214 | <input type="checkbox" id="wpcom_hide_action_bar" name="wpcom_hide_action_bar" value="1" <?php checked( 1, get_option( 'wpcom_hide_action_bar' ) ); ?> /> |
| 1215 | |
| 1216 | <?php esc_html_e( 'Hide the Action Bar on the front end of the site.', 'jetpack-newsletter' ); ?> |
| 1217 | |
| 1218 | <p class="description"><a href="<?php echo esc_url( self::localized_url( 'https://wordpress.com/support/action-bar/' ) ); ?>" data-target="wpcom-help-center"><?php esc_html_e( 'Learn more about the Action Bar', 'jetpack-newsletter' ); ?></a>.</p> |
| 1219 | <?php |
| 1220 | } |
| 1221 | } |