diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 7fa0a15268415..de5373e764dc7 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -2197,7 +2197,63 @@ importers: specifier: 4.9.1 version: 4.9.1(webpack@5.76.0) - projects/packages/masterbar: {} + projects/packages/masterbar: + dependencies: + '@automattic/calypso-color-schemes': + specifier: 3.1.3 + version: 3.1.3 + '@automattic/color-studio': + specifier: 2.6.0 + version: 2.6.0 + postcss-custom-properties: + specifier: 12.1.7 + version: 12.1.7(postcss@8.4.31) + devDependencies: + '@automattic/jetpack-webpack-config': + specifier: workspace:* + version: link:../../js-packages/webpack-config + '@automattic/remove-asset-webpack-plugin': + specifier: workspace:* + version: link:../../js-packages/remove-asset-webpack-plugin + '@babel/core': + specifier: 7.24.0 + version: 7.24.0 + '@csstools/postcss-global-data': + specifier: 2.1.1 + version: 2.1.1(postcss@8.4.31) + '@wordpress/browserslist-config': + specifier: 5.39.0 + version: 5.39.0 + '@wordpress/dependency-extraction-webpack-plugin': + specifier: 5.7.0 + version: 5.7.0(webpack@5.76.0) + autoprefixer: + specifier: 10.4.14 + version: 10.4.14(postcss@8.4.31) + core-js: + specifier: 3.23.5 + version: 3.23.5 + glob: + specifier: 10.3.15 + version: 10.3.15 + postcss: + specifier: 8.4.31 + version: 8.4.31 + postcss-loader: + specifier: 6.2.0 + version: 6.2.0(postcss@8.4.31)(webpack@5.76.0) + sass: + specifier: 1.64.1 + version: 1.64.1 + sass-loader: + specifier: 12.4.0 + version: 12.4.0(sass@1.64.1)(webpack@5.76.0) + webpack: + specifier: 5.76.0 + version: 5.76.0(webpack-cli@4.9.1) + webpack-cli: + specifier: 4.9.1 + version: 4.9.1(webpack@5.76.0) projects/packages/my-jetpack: dependencies: diff --git a/projects/packages/masterbar/.gitattributes b/projects/packages/masterbar/.gitattributes index b0b228d4ad6ad..8d0f7898051a3 100644 --- a/projects/packages/masterbar/.gitattributes +++ b/projects/packages/masterbar/.gitattributes @@ -5,13 +5,17 @@ package.json export-ignore # Files to include in the mirror repo, but excluded via gitignore # Remember to end all directories with `/**` to properly tag every file. -# /src/js/example.min.js production-include +dist/** production-include # Files to exclude from the mirror repo, but included in the monorepo. # Remember to end all directories with `/**` to properly tag every file. .gitignore production-exclude +.phpcs.dir.xml production-exclude +.phpcsignore production-exclude changelog/** production-exclude phpunit.xml.dist production-exclude -.phpcs.dir.xml production-exclude +src/**/*.css production-exclude +src/**/*.js production-exclude +src/**/*.scss production-exclude tests/** production-exclude -.phpcsignore production-exclude +tools/** production-exclude diff --git a/projects/packages/masterbar/.gitignore b/projects/packages/masterbar/.gitignore index 688e2469bdf27..71e37fe99f319 100644 --- a/projects/packages/masterbar/.gitignore +++ b/projects/packages/masterbar/.gitignore @@ -1,3 +1,6 @@ vendor/ node_modules/ wordpress/ + +dist/ +.cache/ diff --git a/projects/packages/masterbar/.phan/baseline.php b/projects/packages/masterbar/.phan/baseline.php new file mode 100644 index 0000000000000..ff5a327951554 --- /dev/null +++ b/projects/packages/masterbar/.phan/baseline.php @@ -0,0 +1,28 @@ + [ + 'src/admin-menu/class-wpcom-admin-menu.php' => ['PhanTypeMismatchArgument'], + 'src/class-main.php' => ['PhanNoopNew'], + 'src/profile-edit/bootstrap.php' => ['PhanNoopNew'], + 'tests/php/test-class-admin-color-schemes.php' => ['PhanNoopNew'], + ], + // 'directory_suppressions' => ['src/directory_name' => ['PhanIssueName1', 'PhanIssueName2']] can be manually added if needed. + // (directory_suppressions will currently be ignored by subsequent calls to --save-baseline, but may be preserved in future Phan releases) +]; diff --git a/projects/packages/masterbar/.phan/config.php b/projects/packages/masterbar/.phan/config.php index 4a26d3fa39e9d..61c163439819f 100644 --- a/projects/packages/masterbar/.phan/config.php +++ b/projects/packages/masterbar/.phan/config.php @@ -10,4 +10,26 @@ // Require base config. require __DIR__ . '/../../../../.phan/config.base.php'; -return make_phan_config( dirname( __DIR__ ) ); +// We need to load the wpcom and amp stubs too. +return make_phan_config( + dirname( __DIR__ ), + array( + '+stubs' => array( 'wpcom', 'amp' ), + 'parse_file_list' => array( + + /* + Reference files to handle code checking for stuff from Jetpack-the-plugin or other in-monorepo plugins. + * Wherever feasible we should really clean up this sort of thing instead of adding stuff here. + * + * DO NOT add references to files in other packages like this! Generally packages should be listed in composer.json 'require'. + * If there are truly optional dependencies or circular dependencies that can't be cleaned up, one package may list the + * other in 'require-dev' and `extra.dependencies.test-only' instead. See packages/config for an example. + */ + __DIR__ . '/../../../plugins/jetpack/3rd-party/class.jetpack-amp-support.php', + __DIR__ . '/../../../plugins/jetpack/modules/custom-css/custom-css.php', + __DIR__ . '/../../../plugins/jetpack/modules/notes.php', + __DIR__ . '/../../../plugins/jetpack/modules/scan/class-admin-bar-notice.php', + __DIR__ . '/../../../plugins/jetpack/modules/stats.php', + ), + ) +); diff --git a/projects/packages/masterbar/README.md b/projects/packages/masterbar/README.md index 36d240713f69c..3272a019f2465 100644 --- a/projects/packages/masterbar/README.md +++ b/projects/packages/masterbar/README.md @@ -1,14 +1,17 @@ -# masterbar +# Masterbar The WordPress.com Toolbar feature replaces the default admin bar and offers quick links to the Reader, all your sites, your WordPress.com profile, and notifications. -## How to install masterbar +## Get Started -### Installation From Git Repo +Package is published in [Packagist](https://packagist.org/packages/automattic/jetpack-masterbar). We recommend using the latest version there, or you can also test with the latest development versions like below: -## Contribute - -## Get Help +``` +"require": { + "automattic/jetpack-autoloader": "dev-trunk", + "automattic/jetpack-masterbar": "dev-trunk" +} +``` ## Using this package in your WordPress plugin @@ -18,7 +21,19 @@ If you plan on using this package in your WordPress plugin, we would recommend t Need to report a security vulnerability? Go to [https://automattic.com/security/](https://automattic.com/security/) or directly to our security bug bounty site [https://hackerone.com/automattic](https://hackerone.com/automattic). +## Build System + +_Note: `cd` to `projects/packages/masterbar` before running these commands_ + +- `npm run build`
+ Compiles the plugins for development - the files are not minified and we produce a source map. + +- `npm run build-production`
+ Compiles the plugins for production - we produce minified files without source maps. + +- `npm run clean`
+ Removes all build files. + ## License masterbar is licensed under [GNU General Public License v2 (or later)](./LICENSE.txt) - diff --git a/projects/packages/masterbar/changelog/update-copy-masterbar-code-to-pkg b/projects/packages/masterbar/changelog/update-copy-masterbar-code-to-pkg new file mode 100644 index 0000000000000..424c0f36d5ff6 --- /dev/null +++ b/projects/packages/masterbar/changelog/update-copy-masterbar-code-to-pkg @@ -0,0 +1,4 @@ +Significance: minor +Type: changed + +Masterbar: Copy module code to package diff --git a/projects/packages/masterbar/composer.json b/projects/packages/masterbar/composer.json index 9ab20e336f972..15a62ca4cf259 100644 --- a/projects/packages/masterbar/composer.json +++ b/projects/packages/masterbar/composer.json @@ -4,11 +4,22 @@ "type": "jetpack-library", "license": "GPL-2.0-or-later", "require": { - "php": ">=7.0" + "php": ">=7.0", + "automattic/jetpack-assets": "@dev", + "automattic/jetpack-blaze": "@dev", + "automattic/jetpack-compat": "@dev", + "automattic/jetpack-device-detection": "@dev", + "automattic/jetpack-connection": "@dev", + "automattic/jetpack-jitm": "@dev", + "automattic/jetpack-logo": "@dev", + "automattic/jetpack-plans": "@dev", + "automattic/jetpack-status": "@dev" }, "require-dev": { + "brain/monkey": "2.6.1", "yoast/phpunit-polyfills": "1.1.0", "automattic/jetpack-changelogger": "@dev", + "automattic/patchwork-redefine-exit": "@dev", "automattic/wordbless": "dev-master" }, "autoload": { @@ -17,14 +28,19 @@ ] }, "scripts": { - "build-development": "echo 'Add your build step to composer.json, please!'", - "build-production": "echo 'Add your build step to composer.json, please!'", + "build-production": [ + "pnpm run build-production" + ], + "build-development": [ + "pnpm run build" + ], "phpunit": [ "./vendor/phpunit/phpunit/phpunit --colors=always" ], "post-install-cmd": "WorDBless\\Composer\\InstallDropin::copy", "post-update-cmd": "WorDBless\\Composer\\InstallDropin::copy", "test-php": [ + "pnpm run build-production", "@composer phpunit" ] }, @@ -55,7 +71,7 @@ "mirror-repo": "Automattic/jetpack-masterbar", "textdomain": "jetpack-masterbar", "version-constants": { - "::PACKAGE_VERSION": "src/class-masterbar.php" + "::PACKAGE_VERSION": "src/class-main.php" } }, "suggest": { diff --git a/projects/packages/masterbar/package.json b/projects/packages/masterbar/package.json index e452010e35ccc..384dbc69f3d32 100644 --- a/projects/packages/masterbar/package.json +++ b/projects/packages/masterbar/package.json @@ -15,11 +15,34 @@ "license": "GPL-2.0-or-later", "author": "Automattic", "scripts": { - "build": "echo 'Not implemented.'", - "build-js": "echo 'Not implemented.'", - "build-production": "echo 'Not implemented.'", - "build-production-js": "echo 'Not implemented.'", - "clean": "true" + "build": "pnpm run clean && pnpm run build-js", + "build-js": "webpack --config tools/webpack.config.js", + "build-production": "pnpm run clean && pnpm run build-production-js && pnpm run validate", + "build-production-js": "NODE_ENV=production BABEL_ENV=production pnpm run build-js", + "clean": "rm -rf dist/ .cache/", + "validate": "pnpm exec validate-es --no-error-on-unmatched-pattern dist/", + "watch": "pnpm run build && pnpm webpack watch" }, - "devDependencies": {} + "dependencies": { + "@automattic/calypso-color-schemes": "3.1.3", + "@automattic/color-studio": "2.6.0", + "postcss-custom-properties": "12.1.7" + }, + "devDependencies": { + "@automattic/jetpack-webpack-config": "workspace:*", + "@automattic/remove-asset-webpack-plugin": "workspace:*", + "autoprefixer": "10.4.14", + "@csstools/postcss-global-data": "2.1.1", + "@babel/core": "7.24.0", + "@wordpress/browserslist-config": "5.39.0", + "@wordpress/dependency-extraction-webpack-plugin": "5.7.0", + "core-js": "3.23.5", + "glob": "10.3.15", + "postcss": "8.4.31", + "postcss-loader": "6.2.0", + "sass": "1.64.1", + "sass-loader": "12.4.0", + "webpack": "5.76.0", + "webpack-cli": "4.9.1" + } } diff --git a/projects/packages/masterbar/src/admin-color-schemes/class-admin-color-schemes.php b/projects/packages/masterbar/src/admin-color-schemes/class-admin-color-schemes.php new file mode 100644 index 0000000000000..ab72282ca5b01 --- /dev/null +++ b/projects/packages/masterbar/src/admin-color-schemes/class-admin-color-schemes.php @@ -0,0 +1,194 @@ + array( $this, 'update_admin_color_permissions_check' ), + 'description' => __( 'Slug of the admin color scheme.', 'jetpack-masterbar' ), + 'single' => true, + 'show_in_rest' => array( + 'schema' => array( 'default' => 'fresh' ), + ), + 'type' => 'string', + ) + ); + } + + /** + * Permission callback to edit the `admin_color` user meta. + * + * @param bool $allowed Whether the given user is allowed to edit this meta value. + * @param string $meta_key Meta key. In this case `admin_color`. + * @param int $object_id Queried user ID. + * @return bool + */ + public function update_admin_color_permissions_check( $allowed, $meta_key, $object_id ) { // phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter, VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable + return current_user_can( 'edit_user', $object_id ); + } + + /** + * Get the admin color scheme URL based on the environment + * + * @param string $color_scheme The color scheme to get the URL for. + * @return string + */ + public function get_admin_color_scheme_url( $color_scheme ) { + return plugins_url( '../../dist/admin-color-schemes/colors/' . $color_scheme . '/colors.css', __FILE__ ); + } + + /** + * Registers new admin color schemes + */ + public function register_admin_color_schemes() { + + wp_admin_css_color( + 'aquatic', + __( 'Aquatic', 'jetpack-masterbar' ), + $this->get_admin_color_scheme_url( 'aquatic' ), + array( '#135e96', '#007e65', '#043959', '#c5d9ed' ), + array( + 'base' => '#c5d9ed', + 'focus' => '#fff', + 'current' => '#01263a', + ) + ); + + wp_admin_css_color( + 'classic-blue', + __( 'Classic Blue', 'jetpack-masterbar' ), + $this->get_admin_color_scheme_url( 'classic-blue' ), + array( '#135e96', '#b26200', '#dcdcde', '#646970' ), + array( + 'base' => '#646970', + 'focus' => '#2271b1', + 'current' => '#fff', + ) + ); + + wp_admin_css_color( + 'classic-bright', + __( 'Classic Bright', 'jetpack-masterbar' ), + $this->get_admin_color_scheme_url( 'classic-bright' ), + array( '#135e96', '#c9256e', '#ffffff', '#e9eff5' ), + array( + 'base' => '#646970', + 'focus' => '#1d2327', + 'current' => '#0a4b78', + ) + ); + + wp_admin_css_color( + 'classic-dark', + __( 'Classic Dark', 'jetpack-masterbar' ), + $this->get_admin_color_scheme_url( 'classic-dark' ), + array( '#101517', '#c9356e', '#32373c', '#0073aa' ), + array( + 'base' => '#a2aab2', + 'focus' => '#00b9eb', + 'current' => '#fff', + ) + ); + + wp_admin_css_color( + 'contrast', + __( 'Contrast', 'jetpack-masterbar' ), + $this->get_admin_color_scheme_url( 'contrast' ), + array( '#101517', '#ffffff', '#32373c', '#b4b9be' ), + array( + 'base' => '#1d2327', + 'focus' => '#fff', + 'current' => '#fff', + ) + ); + + wp_admin_css_color( + 'nightfall', + __( 'Nightfall', 'jetpack-masterbar' ), + $this->get_admin_color_scheme_url( 'nightfall' ), + array( '#00131c', '#043959', '#2271b1', '#9ec2e6' ), + array( + 'base' => '#9ec2e6', + 'focus' => '#fff', + 'current' => '#fff', + ) + ); + + wp_admin_css_color( + 'powder-snow', + __( 'Powder Snow', 'jetpack-masterbar' ), + $this->get_admin_color_scheme_url( 'powder-snow' ), + array( '#101517', '#2271b1', '#dcdcde', '#646970' ), + array( + 'base' => '#646970', + 'focus' => '#135e96', + 'current' => '#fff', + ) + ); + + wp_admin_css_color( + 'sakura', + __( 'Sakura', 'jetpack-masterbar' ), + $this->get_admin_color_scheme_url( 'sakura' ), + array( '#005042', '#f2ceda', '#2271b1', '#8c1749' ), + array( + 'base' => '#8c1749', + 'focus' => '#4f092a', + 'current' => '#fff', + ) + ); + + wp_admin_css_color( + 'sunset', + __( 'Sunset', 'jetpack-masterbar' ), + $this->get_admin_color_scheme_url( 'sunset' ), + array( '#691c1c', '#b26200', '#f0c930', '#facfd2' ), + array( + 'base' => '#facfd2', + 'focus' => '#fff', + 'current' => '#4f3500', + ) + ); + } + + /** + * Enqueues current color-scheme overrides for core color schemes + */ + public function enqueue_core_color_schemes_overrides() { + $core_color_schemes = array( 'blue', 'coffee', 'ectoplasm', 'fresh', 'light', 'midnight', 'modern', 'ocean', 'sunrise' ); + $color_scheme = get_user_option( 'admin_color' ); + if ( in_array( $color_scheme, $core_color_schemes, true ) ) { + wp_enqueue_style( + 'jetpack-core-color-schemes-overrides', + $this->get_admin_color_scheme_url( $color_scheme ), + array(), + Main::PACKAGE_VERSION + ); + } + } +} diff --git a/projects/packages/masterbar/src/admin-color-schemes/colors/_admin.scss b/projects/packages/masterbar/src/admin-color-schemes/colors/_admin.scss new file mode 100644 index 0000000000000..4c28825f6b528 --- /dev/null +++ b/projects/packages/masterbar/src/admin-color-schemes/colors/_admin.scss @@ -0,0 +1,776 @@ +/* + * This file is the basis for generating all admin color scheme stylesheets. + * It consists of two parts: + * 1. All styles from core _admin.scss (keep in sync with changes in core!). + * 2. An import of the overrides necessary to match Calypso color schemes. + */ + +@import 'variables'; +@import 'mixins'; + +@function url-friendly-colour( $color ) { + @return '%23' + str-slice( '#{ $color }', 2, -1 ); +} + +body { + background: $body-background; +} + + +/* Links */ + +a { + color: $link; + + &:hover, + &:active, + &:focus { + color: $link-focus; + } +} + +#post-body .misc-pub-post-status:before, +#post-body #visibility:before, +.curtime #timestamp:before, +#post-body .misc-pub-revisions:before, +span.wp-media-buttons-icon:before { + color: currentColor; +} + +.wp-core-ui .button-link { + color: $link; + + &:hover, + &:active, + &:focus { + color: $link-focus; + } +} + +.media-modal .delete-attachment, +.media-modal .trash-attachment, +.media-modal .untrash-attachment, +.wp-core-ui .button-link-delete { + color: #d63638; +} + +.media-modal .delete-attachment:hover, +.media-modal .trash-attachment:hover, +.media-modal .untrash-attachment:hover, +.media-modal .delete-attachment:focus, +.media-modal .trash-attachment:focus, +.media-modal .untrash-attachment:focus, +.wp-core-ui .button-link-delete:hover, +.wp-core-ui .button-link-delete:focus { + color: #d63638; +} + +/* Forms */ + +input[type=checkbox]:checked::before { + content: url("data:image/svg+xml;utf8,%3Csvg%20xmlns%3D%27http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%27%20viewBox%3D%270%200%2020%2020%27%3E%3Cpath%20d%3D%27M14.83%204.89l1.34.94-5.81%208.38H9.02L5.78%209.67l1.34-1.25%202.57%202.4z%27%20fill%3D%27#{url-friendly-colour($form-checked)}%27%2F%3E%3C%2Fsvg%3E"); +} + +input[type=radio]:checked::before { + background: $form-checked; +} + +.wp-core-ui input[type="reset"]:hover, +.wp-core-ui input[type="reset"]:active { + color: $link-focus; +} + +input[type="text"]:focus, +input[type="password"]:focus, +input[type="color"]:focus, +input[type="date"]:focus, +input[type="datetime"]:focus, +input[type="datetime-local"]:focus, +input[type="email"]:focus, +input[type="month"]:focus, +input[type="number"]:focus, +input[type="search"]:focus, +input[type="tel"]:focus, +input[type="text"]:focus, +input[type="time"]:focus, +input[type="url"]:focus, +input[type="week"]:focus, +input[type="checkbox"]:focus, +input[type="radio"]:focus, +select:focus, +textarea:focus { + border-color: $highlight-color; + box-shadow: 0 0 0 1px $highlight-color; +} + + +/* Core UI */ + +.wp-core-ui { + + .button { + border-color: #7e8993; + color: #32373c; + } + + .button.hover, + .button:hover, + .button.focus, + .button:focus { + border-color: darken( #7e8993, 5% ); + color: darken( #32373c, 5% ); + } + + .button.focus, + .button:focus { + border-color: #7e8993; + color: darken( #32373c, 5% ); + box-shadow: 0 0 0 1px #32373c; + } + + .button:active { + border-color: #7e8993; + color: darken( #32373c, 5% ); + box-shadow: none; + } + + .button.active, + .button.active:focus, + .button.active:hover { + border-color: $button-color; + color: darken( #32373c, 5% ); + box-shadow: inset 0 2px 5px -3px $button-color; + } + + .button.active:focus { + box-shadow: 0 0 0 1px #32373c; + } + + @if ( $low-contrast-theme != "true" ) { + .button, + .button-secondary { + color: $highlight-color; + border-color: $highlight-color; + } + + .button.hover, + .button:hover, + .button-secondary:hover{ + border-color: darken($highlight-color, 10); + color: darken($highlight-color, 10); + } + + .button.focus, + .button:focus, + .button-secondary:focus { + border-color: lighten($highlight-color, 10); + color: darken($highlight-color, 20);; + box-shadow: 0 0 0 1px lighten($highlight-color, 10); + } + + .button-primary { + &:hover { + color: #fff; + } + } + } + + .button-primary { + @include button( $button-color ); + } + + .button-group > .button.active { + border-color: $button-color; + } + + .wp-ui-primary { + color: $text-color; + background-color: $base-color; + } + .wp-ui-text-primary { + color: $base-color; + } + + .wp-ui-highlight { + color: $menu-highlight-text; + background-color: $menu-highlight-background; + } + .wp-ui-text-highlight { + color: $menu-highlight-background; + } + + .wp-ui-notification { + color: $menu-bubble-text; + background-color: $menu-bubble-background; + } + .wp-ui-text-notification { + color: $menu-bubble-background; + } + + .wp-ui-text-icon { + color: $menu-icon; + } +} + + +/* List tables */ +@if $low-contrast-theme == "true" { + .wrap .page-title-action:hover { + color: $menu-text; + background-color: $menu-background; + } +} @else { + .wrap .page-title-action, + .wrap .page-title-action:active { + border: 1px solid $highlight-color; + color: $highlight-color; + } + + .wrap .page-title-action:hover { + color: darken($highlight-color, 10); + border-color: darken($highlight-color, 10); + } + + .wrap .page-title-action:focus { + border-color: lighten($highlight-color, 10); + color: darken($highlight-color, 20);; + box-shadow: 0 0 0 1px lighten($highlight-color, 10); + } +} + +.view-switch a.current:before { + color: $menu-background; +} + +.view-switch a:hover:before { + color: $menu-bubble-background; +} + + +/* Admin Menu */ + +#adminmenuback, +#adminmenuwrap, +#adminmenu { + background: $menu-background; +} + +#adminmenu a { + color: $menu-text; +} + +#adminmenu div.wp-menu-image:before { + color: $menu-icon; +} + +#adminmenu a:hover, +#adminmenu li.menu-top:hover, +#adminmenu li.opensub > a.menu-top, +#adminmenu li > a.menu-top:focus { + color: $menu-highlight-text; + background-color: $menu-highlight-background; +} + +#adminmenu li.menu-top:hover div.wp-menu-image:before, +#adminmenu li.opensub > a.menu-top div.wp-menu-image:before { + color: $menu-highlight-icon; +} + + +/* Active tabs use a bottom border color that matches the page background color. */ + +.about-wrap .nav-tab-active, +.nav-tab-active, +.nav-tab-active:hover { + background-color: $body-background; + border-bottom-color: $body-background; +} + + +/* Admin Menu: submenu */ + +#adminmenu .wp-submenu, +#adminmenu .wp-has-current-submenu .wp-submenu, +#adminmenu .wp-has-current-submenu.opensub .wp-submenu, +.folded #adminmenu .wp-has-current-submenu .wp-submenu, +#adminmenu a.wp-has-current-submenu:focus + .wp-submenu { + background: $menu-submenu-background; +} + +#adminmenu li.wp-has-submenu.wp-not-current-submenu.opensub:hover:after { + border-right-color: $menu-submenu-background; +} + +#adminmenu .wp-submenu .wp-submenu-head { + color: $menu-submenu-text; +} + +#adminmenu .wp-submenu a, +#adminmenu .wp-has-current-submenu .wp-submenu a, +.folded #adminmenu .wp-has-current-submenu .wp-submenu a, +#adminmenu a.wp-has-current-submenu:focus + .wp-submenu a, +#adminmenu .wp-has-current-submenu.opensub .wp-submenu a { + color: $menu-submenu-text; + + &:focus, &:hover { + color: $menu-submenu-focus-text; + } +} + + +/* Admin Menu: current */ + +#adminmenu .wp-submenu li.current a, +#adminmenu a.wp-has-current-submenu:focus + .wp-submenu li.current a, +#adminmenu .wp-has-current-submenu.opensub .wp-submenu li.current a { + color: $menu-submenu-current-text; + + &:hover, &:focus { + color: $menu-submenu-focus-text; + } +} + +ul#adminmenu a.wp-has-current-submenu:after, +ul#adminmenu > li.current > a.current:after { + border-inline-end-color: $body-background; +} + +#adminmenu li.current a.menu-top, +#adminmenu li.wp-has-current-submenu a.wp-has-current-submenu, +#adminmenu li.wp-has-current-submenu .wp-submenu .wp-submenu-head, +.folded #adminmenu li.current.menu-top { + color: $menu-current-text; + background: $menu-current-background; +} + +#adminmenu li.wp-has-current-submenu div.wp-menu-image:before, +#adminmenu a.current:hover div.wp-menu-image:before, +#adminmenu li.wp-has-current-submenu a:focus div.wp-menu-image:before, +#adminmenu li.wp-has-current-submenu.opensub div.wp-menu-image:before, +#adminmenu li:hover div.wp-menu-image:before, +#adminmenu li a:focus div.wp-menu-image:before, +#adminmenu li.opensub div.wp-menu-image:before { + color: $menu-current-icon; +} + + +/* Admin Menu: bubble */ + +#adminmenu .awaiting-mod, +#adminmenu .update-plugins { + color: $menu-bubble-text; + background: $menu-bubble-background; +} + +#adminmenu li.current a .awaiting-mod, +#adminmenu li a.wp-has-current-submenu .update-plugins, +#adminmenu li:hover a .awaiting-mod, +#adminmenu li.menu-top:hover > a .update-plugins { + color: $menu-bubble-current-text; + background: $menu-bubble-current-background; +} + + +/* Admin Menu: collapse button */ + +#collapse-button { + color: $menu-collapse-text; +} + +#collapse-button:hover, +#collapse-button:focus { + color: $menu-submenu-focus-text; +} + +/* Admin Bar */ + +#wpadminbar { + color: $menu-text; + background: $menu-background; +} + +#wpadminbar .ab-item, +#wpadminbar a.ab-item, +#wpadminbar > #wp-toolbar span.ab-label, +#wpadminbar > #wp-toolbar span.noticon { + color: #f0f0f1; +} + +#wpadminbar .ab-icon, +#wpadminbar .ab-icon:before, +#wpadminbar .ab-item:before, +#wpadminbar .ab-item:after { + color: #a7aaad; +} + +#wpadminbar:not(.mobile) .ab-top-menu > li:hover > .ab-item, +#wpadminbar:not(.mobile) .ab-top-menu > li > .ab-item:focus, +#wpadminbar.nojq .quicklinks .ab-top-menu > li > .ab-item:focus, +#wpadminbar.nojs .ab-top-menu > li.menupop:hover > .ab-item, +#wpadminbar .ab-top-menu > li.menupop.hover > .ab-item { + color: $menu-submenu-focus-text; + background: $menu-submenu-background; +} + +#wpadminbar:not(.mobile) > #wp-toolbar li:hover span.ab-label, +#wpadminbar:not(.mobile) > #wp-toolbar li.hover span.ab-label, +#wpadminbar:not(.mobile) > #wp-toolbar a:focus span.ab-label { + color: $menu-submenu-focus-text; +} + +#wpadminbar:not(.mobile) li:hover .ab-icon:before, +#wpadminbar:not(.mobile) li:hover .ab-item:before, +#wpadminbar:not(.mobile) li:hover .ab-item:after, +#wpadminbar:not(.mobile) li:hover #adminbarsearch:before { + color: $menu-highlight-icon; +} + + +/* Admin Bar: submenu */ + +#wpadminbar .menupop .ab-sub-wrapper { + background: $menu-submenu-background; +} + +#wpadminbar .quicklinks .menupop ul.ab-sub-secondary, +#wpadminbar .quicklinks .menupop ul.ab-sub-secondary .ab-submenu { + background: $menu-submenu-background-alt; +} + +#wpadminbar .ab-submenu .ab-item, +#wpadminbar .quicklinks .menupop ul li a, +#wpadminbar .quicklinks .menupop.hover ul li a, +#wpadminbar.nojs .quicklinks .menupop:hover ul li a { + color: $menu-submenu-text; +} + +#wpadminbar .quicklinks li .blavatar, +#wpadminbar .menupop .menupop > .ab-item:before { + color: $menu-icon; +} + +#wpadminbar .quicklinks .menupop ul li a:hover, +#wpadminbar .quicklinks .menupop ul li a:focus, +#wpadminbar .quicklinks .menupop ul li a:hover strong, +#wpadminbar .quicklinks .menupop ul li a:focus strong, +#wpadminbar .quicklinks .ab-sub-wrapper .menupop.hover > a, +#wpadminbar .quicklinks .menupop.hover ul li a:hover, +#wpadminbar .quicklinks .menupop.hover ul li a:focus, +#wpadminbar.nojs .quicklinks .menupop:hover ul li a:hover, +#wpadminbar.nojs .quicklinks .menupop:hover ul li a:focus, +#wpadminbar li:hover .ab-icon:before, +#wpadminbar li:hover .ab-item:before, +#wpadminbar li a:focus .ab-icon:before, +#wpadminbar li .ab-item:focus:before, +#wpadminbar li .ab-item:focus .ab-icon:before, +#wpadminbar li.hover .ab-icon:before, +#wpadminbar li.hover .ab-item:before, +#wpadminbar li:hover #adminbarsearch:before, +#wpadminbar li #adminbarsearch.adminbar-focused:before { + color: $menu-submenu-focus-text; +} + +#wpadminbar .quicklinks li a:hover .blavatar, +#wpadminbar .quicklinks li a:focus .blavatar, +#wpadminbar .quicklinks .ab-sub-wrapper .menupop.hover > a .blavatar, +#wpadminbar .menupop .menupop > .ab-item:hover:before, +#wpadminbar.mobile .quicklinks .ab-icon:before, +#wpadminbar.mobile .quicklinks .ab-item:before { + color: $menu-submenu-focus-text; +} + +#wpadminbar.mobile .quicklinks .hover .ab-icon:before, +#wpadminbar.mobile .quicklinks .hover .ab-item:before { + color: $menu-icon; +} + + +/* Admin Bar: search */ + +#wpadminbar #adminbarsearch:before { + color: $menu-icon; +} + +#wpadminbar > #wp-toolbar > #wp-admin-bar-top-secondary > #wp-admin-bar-search #adminbarsearch input.adminbar-input:focus { + color: $menu-text; + background: $adminbar-input-background; +} + +/* Admin Bar: recovery mode */ + +#wpadminbar #wp-admin-bar-recovery-mode { + color: $adminbar-recovery-exit-text; + background-color: $adminbar-recovery-exit-background; +} + +#wpadminbar #wp-admin-bar-recovery-mode .ab-item, +#wpadminbar #wp-admin-bar-recovery-mode a.ab-item { + color: $adminbar-recovery-exit-text; +} + +#wpadminbar .ab-top-menu > #wp-admin-bar-recovery-mode.hover >.ab-item, +#wpadminbar.nojq .quicklinks .ab-top-menu > #wp-admin-bar-recovery-mode > .ab-item:focus, +#wpadminbar:not(.mobile) .ab-top-menu > #wp-admin-bar-recovery-mode:hover > .ab-item, +#wpadminbar:not(.mobile) .ab-top-menu > #wp-admin-bar-recovery-mode > .ab-item:focus { + color: $adminbar-recovery-exit-text; + background-color: $adminbar-recovery-exit-background-alt; +} + +/* Admin Bar: my account */ + +#wpadminbar .quicklinks li#wp-admin-bar-my-account.with-avatar > a img { + border-color: $adminbar-avatar-frame; + background-color: $adminbar-avatar-frame; +} + +#wpadminbar #wp-admin-bar-user-info .display-name { + color: $menu-text; +} + +#wpadminbar #wp-admin-bar-user-info a:hover .display-name { + color: $menu-submenu-focus-text; +} + +#wpadminbar #wp-admin-bar-user-info .username { + color: $menu-submenu-text; +} + + +/* Pointers */ + +.wp-pointer .wp-pointer-content h3 { + background-color: $highlight-color; + border-color: darken( $highlight-color, 5% ); +} + +.wp-pointer .wp-pointer-content h3:before { + color: $highlight-color; +} + +.wp-pointer.wp-pointer-top .wp-pointer-arrow, +.wp-pointer.wp-pointer-top .wp-pointer-arrow-inner, +.wp-pointer.wp-pointer-undefined .wp-pointer-arrow, +.wp-pointer.wp-pointer-undefined .wp-pointer-arrow-inner { + border-bottom-color: $highlight-color; +} + + +/* Media */ + +.media-item .bar, +.media-progress-bar div { + background-color: $highlight-color; +} + +.details.attachment { + box-shadow: + inset 0 0 0 3px #fff, + inset 0 0 0 7px $highlight-color; +} + +.attachment.details .check { + background-color: $highlight-color; + box-shadow: 0 0 0 1px #fff, 0 0 0 2px $highlight-color; +} + +.media-selection .attachment.selection.details .thumbnail { + box-shadow: 0 0 0 1px #fff, 0 0 0 3px $highlight-color; +} + + +/* Themes */ + +.theme-browser .theme.active .theme-name, +.theme-browser .theme.add-new-theme a:hover:after, +.theme-browser .theme.add-new-theme a:focus:after { + background: $highlight-color; +} + +.theme-browser .theme.add-new-theme a:hover span:after, +.theme-browser .theme.add-new-theme a:focus span:after { + color: $highlight-color; +} + +.theme-section.current, +.theme-filter.current { + border-bottom-color: $menu-background; +} + +body.more-filters-opened .more-filters { + color: $menu-text; + background-color: $menu-background; +} + +body.more-filters-opened .more-filters:before { + color: $menu-text; +} + +body.more-filters-opened .more-filters:hover, +body.more-filters-opened .more-filters:focus { + background-color: $menu-highlight-background; + color: $menu-highlight-text; +} + +body.more-filters-opened .more-filters:hover:before, +body.more-filters-opened .more-filters:focus:before { + color: $menu-highlight-text; +} + +/* Widgets */ + +.widgets-chooser li.widgets-chooser-selected { + background-color: $menu-highlight-background; + color: $menu-highlight-text; +} + +.widgets-chooser li.widgets-chooser-selected:before, +.widgets-chooser li.widgets-chooser-selected:focus:before { + color: $menu-highlight-text; +} + +/* Responsive Component */ + +div#wp-responsive-toggle a:before { + color: $menu-icon; +} + +.wp-responsive-open div#wp-responsive-toggle a { + // ToDo: make inset border + border-color: transparent; + background: $menu-highlight-background; +} + +.wp-responsive-open #wpadminbar #wp-admin-bar-menu-toggle a { + background: $menu-submenu-background; +} + +.wp-responsive-open #wpadminbar #wp-admin-bar-menu-toggle .ab-icon:before { + color: $menu-icon; +} + +/* TinyMCE */ + +.mce-container.mce-menu .mce-menu-item:hover, +.mce-container.mce-menu .mce-menu-item.mce-selected, +.mce-container.mce-menu .mce-menu-item:focus, +.mce-container.mce-menu .mce-menu-item-normal.mce-active, +.mce-container.mce-menu .mce-menu-item-preview.mce-active { + background: $highlight-color; +} + +/* Customizer */ +#customize-controls .control-section:hover > .accordion-section-title, +#customize-controls .control-section .accordion-section-title:hover, +#customize-controls .control-section.open .accordion-section-title, +#customize-controls .control-section .accordion-section-title:focus { + color: $highlight-color; + border-left-color: $highlight-color; +} + +.customize-controls-close:focus, +.customize-controls-close:hover, +.customize-controls-preview-toggle:focus, +.customize-controls-preview-toggle:hover { + color: $highlight-color; + border-top-color: $highlight-color; +} + +.customize-panel-back:hover, +.customize-panel-back:focus, +.customize-section-back:hover, +.customize-section-back:focus { + color: $highlight-color; + border-left-color: $highlight-color; +} + +.customize-screen-options-toggle:hover, +.customize-screen-options-toggle:active, +.customize-screen-options-toggle:focus, +.active-menu-screen-options .customize-screen-options-toggle, +#customize-controls .customize-info.open.active-menu-screen-options .customize-help-toggle:hover, +#customize-controls .customize-info.open.active-menu-screen-options .customize-help-toggle:active, +#customize-controls .customize-info.open.active-menu-screen-options .customize-help-toggle:focus { + color: $highlight-color; +} + +.wp-customizer .menu-item .submitbox .submitdelete:focus, +.customize-screen-options-toggle:focus:before, +#customize-controls .customize-info .customize-help-toggle:focus:before, +.wp-customizer button:focus .toggle-indicator:before, +.menu-delete:focus, +.menu-item-bar .item-delete:focus:before, +#available-menu-items .item-add:focus:before { + box-shadow: + 0 0 0 1px lighten($highlight-color, 10), + 0 0 2px 1px $highlight-color; +} + +#customize-controls .customize-info.open .customize-help-toggle, +#customize-controls .customize-info .customize-help-toggle:focus, +#customize-controls .customize-info .customize-help-toggle:hover { + color: $highlight-color; +} + +.control-panel-themes .customize-themes-section-title:focus, +.control-panel-themes .customize-themes-section-title:hover { + border-left-color: $highlight-color; + color: $highlight-color; +} + +.control-panel-themes .theme-section .customize-themes-section-title.selected:after { + background: $highlight-color; +} + +.control-panel-themes .customize-themes-section-title.selected { + color: $highlight-color; +} + +#customize-theme-controls .control-section:hover > .accordion-section-title:after, +#customize-theme-controls .control-section .accordion-section-title:hover:after, +#customize-theme-controls .control-section.open .accordion-section-title:after, +#customize-theme-controls .control-section .accordion-section-title:focus:after, +#customize-outer-theme-controls .control-section:hover > .accordion-section-title:after, +#customize-outer-theme-controls .control-section .accordion-section-title:hover:after, +#customize-outer-theme-controls .control-section.open .accordion-section-title:after, +#customize-outer-theme-controls .control-section .accordion-section-title:focus:after { + color: $highlight-color; +} + +.customize-control .attachment-media-view .button-add-media:focus { + background-color: #fbfbfc; + border-color: $highlight-color; + border-style: solid; + box-shadow: 0 0 0 1px $highlight-color; + outline: 2px solid transparent; +} + +.wp-full-overlay-footer .devices button:focus, +.wp-full-overlay-footer .devices button.active:hover { + border-bottom-color: $highlight-color; +} + +.wp-core-ui .wp-full-overlay .collapse-sidebar:hover, +.wp-core-ui .wp-full-overlay .collapse-sidebar:focus { + color: $highlight-color; +} + +.wp-full-overlay .collapse-sidebar:hover .collapse-sidebar-arrow, +.wp-full-overlay .collapse-sidebar:focus .collapse-sidebar-arrow { + box-shadow: + 0 0 0 1px lighten($highlight-color, 10), + 0 0 2px 1px $highlight-color; +} + +.wp-full-overlay-footer .devices button:hover:before, +.wp-full-overlay-footer .devices button:focus:before { + color: $highlight-color; +} + +/* Overrides */ +@import 'overrides'; + +/* Gutenberg Support */ +@import 'gutenberg'; + +/* Inline Help Support */ +@import 'inline-help'; diff --git a/projects/packages/masterbar/src/admin-color-schemes/colors/_core-overrides.scss b/projects/packages/masterbar/src/admin-color-schemes/colors/_core-overrides.scss new file mode 100644 index 0000000000000..94cdbdb7fb135 --- /dev/null +++ b/projects/packages/masterbar/src/admin-color-schemes/colors/_core-overrides.scss @@ -0,0 +1,79 @@ +.admin-color-blue, +.admin-color-coffee, +.admin-color-ectoplasm, +.admin-color-fresh, +.admin-color-light, +.admin-color-midnight, +.admin-color-modern, +.admin-color-ocean, +.admin-color-sunrise { + /* Masterbar */ + #wpadminbar { + background: $masterbar-background; + -webkit-box-shadow: inset 0 -1px 0 $menu-submenu-background; + -moz-box-shadow: inset 0 -1px 0 $menu-submenu-background; + box-shadow: inset 0 -1px 0 $menu-submenu-background; + } + + #wpadminbar .ab-top-menu > #wp-admin-bar-blog > .ab-item { + background: $menu-submenu-background; + color: $text-color !important; + } + + #wpadminbar .ab-top-menu > #wp-admin-bar-newdash > .ab-item { + color: $text-color !important; + } + + #wpadminbar:not(.mobile) .ab-top-menu > li:not(#wp-admin-bar-ab-new-post):hover > .ab-item, + #wpadminbar #wp-admin-bar-notes.hover > .ab-item, + #wpadminbar #wp-admin-bar-notes.wpnt-show > .ab-item { + background: $menu-submenu-background !important; + } + + /** + * Site Switcher - Browse Sites button text & arrow + */ + #adminmenuwrap > #adminmenu .site-switcher { + color: $nav-unification-sidebar-text-alternative; + + div.wp-menu-image:before { + color: $nav-unification-sidebar-text-alternative; + } + + &:hover, + &:hover div.wp-menu-image:before { + color: $menu-highlight-text; + } + } + + /** + * Site Card + */ + #adminmenu li.toplevel_page_site-card { + border-bottom: 1px solid $menu-submenu-background; + border-top: 1px solid $menu-submenu-background; + } + + #adminmenu .toplevel_page_site-card:hover div.wp-menu-image, + #adminmenu .toplevel_page_site-card a:focus div.wp-menu-image { + background-color: $highlight-color; + } + + .site__info .site__title { + color: $text-color; + } + + .site__info .site__domain { + color: $nav-unification-sidebar-text-alternative; + } + + .site__info .site__title::after, + .site__info .site__domain::after { + background: linear-gradient(90deg,rgba( $base-color-rgb, 0 ),rgba( $base-color-rgb, 1 ) 90%); + } + + .site__info > .site__badge { + background: $highlight-color; + color: $text-color; + } +} diff --git a/projects/packages/masterbar/src/admin-color-schemes/colors/_gutenberg.scss b/projects/packages/masterbar/src/admin-color-schemes/colors/_gutenberg.scss new file mode 100644 index 0000000000000..385cbbb7d3df8 --- /dev/null +++ b/projects/packages/masterbar/src/admin-color-schemes/colors/_gutenberg.scss @@ -0,0 +1,14 @@ +/* + * This file ensures scheme colors propagating into the block editor. The editor expects colors + * as CSS variables. We generate them the same way it does for core color schemes. + * + * @see https://github.com/WordPress/gutenberg/blob/HEAD/packages/base-styles/_mixins.scss + */ + +body { + --wp-admin-theme-color: #{$highlight-color}; + + // Darker shades. + --wp-admin-theme-color-darker-10: #{darken($highlight-color, 5%)}; + --wp-admin-theme-color-darker-20: #{darken($highlight-color, 10%)}; +} diff --git a/projects/packages/masterbar/src/admin-color-schemes/colors/_inline-help.scss b/projects/packages/masterbar/src/admin-color-schemes/colors/_inline-help.scss new file mode 100644 index 0000000000000..48a21f5c0f355 --- /dev/null +++ b/projects/packages/masterbar/src/admin-color-schemes/colors/_inline-help.scss @@ -0,0 +1,8 @@ +// These vars are consumed within `modules/masterbar/inline-help/inline-help.css` +.a8c-faux-inline-help { + --color-primary: #{$color-primary}; + --color-primary-dark: #{$color-primary-dark}; + --color-primary-light: #{$color-primary-light}; + --color-surface: #{$color-surface}; + --color-neutral-100-rgb: #{$color-neutral-100-rgb}; +} diff --git a/projects/packages/masterbar/src/admin-color-schemes/colors/_mixins.scss b/projects/packages/masterbar/src/admin-color-schemes/colors/_mixins.scss new file mode 100644 index 0000000000000..1e9aee43f5e1f --- /dev/null +++ b/projects/packages/masterbar/src/admin-color-schemes/colors/_mixins.scss @@ -0,0 +1,37 @@ +/* + * Button mixin- creates a button effect with correct + * highlights/shadows, based on a base color. + */ + @mixin button( $button-color, $button-text-color: #fff ) { + background: $button-color; + border-color: $button-color; + color: $button-text-color; + + &:hover, + &:focus { + background: lighten( $button-color, 3% ); + border-color: darken( $button-color, 3% ); + color: $button-text-color; + } + + &:focus { + box-shadow: + 0 0 0 1px #fff, + 0 0 0 3px $button-color; + } + + &:active { + background: darken( $button-color, 5% ); + border-color: darken( $button-color, 5% ); + color: $button-text-color; + } + + &.active, + &.active:focus, + &.active:hover { + background: $button-color; + color: $button-text-color; + border-color: darken( $button-color, 15% ); + box-shadow: inset 0 2px 5px -3px darken( $button-color, 50% ); + } +} diff --git a/projects/packages/masterbar/src/admin-color-schemes/colors/_overrides.scss b/projects/packages/masterbar/src/admin-color-schemes/colors/_overrides.scss new file mode 100644 index 0000000000000..f9f2a8e858bc1 --- /dev/null +++ b/projects/packages/masterbar/src/admin-color-schemes/colors/_overrides.scss @@ -0,0 +1,141 @@ + +// This file cotains overrides to _admin.scss necessary to match Calypso color schemes + +// Make wpadminbar colors differ from submenu colors + +.admin-color-aquatic, +.admin-color-classic-blue, +.admin-color-classic-bright, +.admin-color-classic-dark, +.admin-color-contrast, +.admin-color-nightfall, +.admin-color-powder-snow, +.admin-color-sakura, +.admin-color-sunset { + + #wpadminbar{ + background: $masterbar-background !important; + } + + #wpadminbar:not(.mobile) .ab-top-menu > li:hover > .ab-item, + #wpadminbar:not(.mobile) .ab-top-menu > li > .ab-item:focus, + #wpadminbar.nojq .quicklinks .ab-top-menu > li > .ab-item:focus, + #wpadminbar.nojs .ab-top-menu > li.menupop:hover > .ab-item, + #wpadminbar .ab-top-menu > li.menupop.hover > .ab-item { + background: $masterbar-highlight-background; + } + +} + +// Make current submenu icon color stay the same on hover +#adminmenu li.wp-has-current-submenu:hover div.wp-menu-image:before, +#adminmenu .wp-has-current-submenu div.wp-menu-image:before, +#adminmenu .current div.wp-menu-image:before, +#adminmenu a.wp-has-current-submenu:hover div.wp-menu-image:before, +#adminmenu a.current:hover div.wp-menu-image:before, +#adminmenu li.wp-has-current-submenu a:focus div.wp-menu-image:before, +#adminmenu li.wp-has-current-submenu.opensub div.wp-menu-image:before { + color: $menu-current-text; +} + +// Make current submenu item color stay the same on hover/focus +#adminmenu .wp-submenu li.current a, +#adminmenu a.wp-has-current-submenu:focus + .wp-submenu li.current a, +#adminmenu .wp-has-current-submenu.opensub .wp-submenu li.current a { + + &:hover, &:focus { + color: $menu-submenu-current-text; + } +} + +// Nav unification +.admin-color-aquatic, +.admin-color-classic-blue, +.admin-color-classic-bright, +.admin-color-classic-dark, +.admin-color-contrast, +.admin-color-nightfall, +.admin-color-powder-snow, +.admin-color-sakura, +.admin-color-sunset { + + // Masterbar - border below the Masterbar + #wpadminbar { + box-shadow: inset 0 -1px 0 $masterbar-background; // Calypso --color-masterbar-background + } + + // Masterbar - My Sites active state + #wpadminbar .ab-top-menu > li.my-sites > .ab-item, + #wpadminbar .ab-top-menu > li.my-sites > .ab-item:hover { + background: $masterbar-active-background; + } + + // Masterbar - Notification hover background + #wpadminbar #wp-admin-bar-notes.hover > .ab-item { + background: $masterbar-highlight-background; + } + + // Masterbar - Notification active background + #wpadminbar .ab-top-menu > li.wpnt-show > .ab-item { + background: $masterbar-active-background !important; // important used in masterbar.css + } + + // Site Card - Border around Site Card + #adminmenu li.toplevel_page_site-card { + border-bottom: 1px solid $nav-unification-sidebar-border; + border-top: 1px solid $nav-unification-sidebar-border; + } + + // Site Card - Keep background the same color on hover + #adminmenu li.toplevel_page_site-card:hover, + #adminmenu li.toplevel_page_site-card a:hover { + background: $menu-background; + } + + // Site Card - Site title + .site__info .site__title { + color: $menu-text; + } + + // Site Card - Site domain + .site__info .site__domain { + color: $nav-unification-sidebar-text-alternative; + } + + // Site Card - Site icon placeholder image background on hover/focus + #adminmenu .toplevel_page_site-card:hover div.wp-menu-image, + #adminmenu .toplevel_page_site-card a:focus div.wp-menu-image { + background-color: $menu-current-background; + } + + // Site Card - Fade out for content overflowing the Site Card + .site__info .site__title::after, + .site__info .site__domain::after { + background: linear-gradient( 90deg, rgba( $color: $menu-background, $alpha: 0 ), rgba( $color: $menu-background, $alpha: 1 ) 90% ); + } + + // Site Card - Browse Sites button text & arrow + #adminmenuwrap > #adminmenu .site-switcher { + color: $nav-unification-sidebar-text-alternative; + + div.wp-menu-image:before { + color: $nav-unification-sidebar-text-alternative; + } + + &:hover, + &:hover div.wp-menu-image:before { + color: $menu-highlight-text; + } + } + +} + +// Ensure sidebar is visually separate from the content in the Contrast color scheme +.admin-color-contrast #adminmenuback { + outline: 1px solid $nav-unification-sidebar-border; +} + +.admin-color-contrast.folded #adminmenu .toplevel_page_site-notices .wp-menu-image, +.admin-color-classic-bright.folded #adminmenu .toplevel_page_site-notices .wp-menu-image { + background-color: $studio-gray-80 !important; +} diff --git a/projects/packages/masterbar/src/admin-color-schemes/colors/_upsell-banner.scss b/projects/packages/masterbar/src/admin-color-schemes/colors/_upsell-banner.scss new file mode 100644 index 0000000000000..bf7862fd70401 --- /dev/null +++ b/projects/packages/masterbar/src/admin-color-schemes/colors/_upsell-banner.scss @@ -0,0 +1,14 @@ +// Calypso Upsell Banner +#adminmenu .toplevel_page_site-notices .upsell_banner { + background-color: $menu-nudge-background; + color: $menu-nudge-text-color; +} + +#adminmenu .toplevel_page_site-notices .upsell_banner .button { + background-color: $menu-nudge-cta-background; + color: $menu-nudge-cta-color; +} + +#adminmenu .toplevel_page_site-notices .upsell_banner .button:hover { + background-color: $menu-nudge-cta-background-hover +} diff --git a/projects/packages/masterbar/src/admin-color-schemes/colors/_variables.scss b/projects/packages/masterbar/src/admin-color-schemes/colors/_variables.scss new file mode 100644 index 0000000000000..8287a2320282c --- /dev/null +++ b/projects/packages/masterbar/src/admin-color-schemes/colors/_variables.scss @@ -0,0 +1,65 @@ +// assign default value to all undefined variables + + +// core variables + +$text-color: #fff !default; +$base-color: #23282d !default; +$icon-color: hsl( hue( $base-color ), 7%, 95% ) !default; +$highlight-color: #0073aa !default; +$notification-color: #d54e21 !default; + + +// global + +$body-background: #f1f1f1 !default; + +$link: #0073aa !default; +$link-focus: lighten( $link, 10% ) !default; + +$button-color: $highlight-color !default; +$button-text-color: $text-color !default; + +$form-checked: #7e8993 !default; + +// admin menu & admin-bar + +$menu-text: $text-color !default; +$menu-icon: $icon-color !default; +$menu-background: $base-color !default; + +$menu-highlight-text: $text-color !default; +$menu-highlight-icon: $text-color !default; +$menu-highlight-background: $highlight-color !default; + +$menu-current-text: $menu-highlight-text !default; +$menu-current-icon: $menu-highlight-icon !default; +$menu-current-background: $menu-highlight-background !default; + +$menu-submenu-text: mix( $base-color, $text-color, 30% ) !default; +$menu-submenu-background: darken( $base-color, 7% ) !default; +$menu-submenu-background-alt: desaturate( lighten( $menu-background, 7% ), 7% ) !default; + +$menu-submenu-focus-text: $highlight-color !default; +$menu-submenu-current-text: $text-color !default; + +$menu-bubble-text: $text-color !default; +$menu-bubble-background: $notification-color !default; +$menu-bubble-current-text: $text-color !default; +$menu-bubble-current-background: $menu-submenu-background !default; + +$menu-collapse-text: $menu-icon !default; +$menu-collapse-icon: $menu-icon !default; +$menu-collapse-focus-text: $text-color !default; +$menu-collapse-focus-icon: $menu-highlight-icon !default; + +$adminbar-avatar-frame: lighten( $menu-background, 7% ) !default; +$adminbar-input-background: lighten( $menu-background, 7% ) !default; + +$adminbar-recovery-exit-text: $menu-bubble-text !default; +$adminbar-recovery-exit-background: $menu-bubble-background !default; +$adminbar-recovery-exit-background-alt: mix(black, $adminbar-recovery-exit-background, 10%) !default; + +$menu-customizer-text: mix( $base-color, $text-color, 40% ) !default; + +$low-contrast-theme: "false" !default; diff --git a/projects/packages/masterbar/src/admin-color-schemes/colors/aquatic/_variables.scss b/projects/packages/masterbar/src/admin-color-schemes/colors/aquatic/_variables.scss new file mode 100644 index 0000000000000..f528eb52248ca --- /dev/null +++ b/projects/packages/masterbar/src/admin-color-schemes/colors/aquatic/_variables.scss @@ -0,0 +1,49 @@ +// make use of color studio variables for consistency across products +@import 'node_modules/@automattic/color-studio/dist/color-variables.scss'; +@import 'node_modules/@automattic/color-studio/dist/color-variables-rgb.scss'; + +// core variables (core _variables.scss) +$base-color: $studio-blue-80; // Calypso --color-masterbar-background +$icon-color: $studio-blue-5; // Calypso --color-sidebar-gridicon-fill +$highlight-color: $studio-celadon-50; // Calypso --color-accent +$notification-color: $studio-celadon-30; // Calypso --color-masterbar-unread-dot-background + +// global (core _variables.scss) +$body-background: $studio-gray-0; // Calypso --color-surface-backdrop + +// admin menu & admin-bar (core _variables.scss) +$menu-text: $studio-white; // Calypso --color-sidebar-text +$menu-background: $studio-blue-60; // Calypso --color-sidebar-background +$menu-highlight-text: $studio-white; // Calypso --color-sidebar-menu-hover-text +$menu-highlight-icon: $menu-highlight-text; +$menu-highlight-background: $studio-blue-50; // Calypso --color-sidebar-menu-hover-background +$menu-current-text: $studio-blue-90; // Calypso --color-sidebar-menu-selected-text +$menu-current-icon: $menu-current-text; +$menu-current-background: $studio-yellow-20; // Calypso --color-sidebar-menu-selected-background +$menu-submenu-text: $studio-white; // Calypso --color-sidebar-submenu-text +$menu-submenu-background: $studio-blue-80; // Calypso --color-sidebar-submenu-background +$menu-submenu-focus-text: $studio-yellow-20; // Calypso --color-sidebar-submenu-hover-text +$menu-submenu-current-text: $studio-white; // Calypso --color-sidebar-submenu-text + +// masterbar overrides +$masterbar-background: $studio-blue-80; // Calypso --color-masterbar-background +$masterbar-highlight-background: $studio-blue-90; // Calypso --color-masterbar-item-hover-background +$masterbar-active-background: $studio-blue-100; // Calypso --color-masterbar-item-active-background + +// nav unification overrides +$nav-unification-sidebar-border: $studio-blue-70; // Calypso --color-sidebar-border +$nav-unification-sidebar-text-alternative: $studio-blue-5; // Calypso --color-sidebar-text-alternative + +// Calypso color variables used in e.g. inline help +$color-primary: $studio-blue-50; // Calypso --color-primary +$color-primary-dark: $studio-blue-70; // Calypso --color-primary-dark +$color-primary-light: $studio-blue-30; // Calypso --color-primary-light +$color-surface: $studio-white; // Calypso --color-surface +$color-neutral-100-rgb: $studio-gray-100-rgb; // Calypso --color-neutral-100-rgb + +// Calypso Sidebar Nudges +$menu-nudge-background: $studio-white; +$menu-nudge-text-color: $studio-black; +$menu-nudge-cta-background: $highlight-color; +$menu-nudge-cta-color: $studio-white; +$menu-nudge-cta-background-hover: $studio-celadon-60; diff --git a/projects/packages/masterbar/src/admin-color-schemes/colors/aquatic/colors.scss b/projects/packages/masterbar/src/admin-color-schemes/colors/aquatic/colors.scss new file mode 100644 index 0000000000000..07136f2553c45 --- /dev/null +++ b/projects/packages/masterbar/src/admin-color-schemes/colors/aquatic/colors.scss @@ -0,0 +1,3 @@ +@import "variables"; +@import "../admin"; +@import "sidebar-notice"; diff --git a/projects/packages/masterbar/src/admin-color-schemes/colors/aquatic/sidebar-notice.scss b/projects/packages/masterbar/src/admin-color-schemes/colors/aquatic/sidebar-notice.scss new file mode 100644 index 0000000000000..3e1b6380cd81e --- /dev/null +++ b/projects/packages/masterbar/src/admin-color-schemes/colors/aquatic/sidebar-notice.scss @@ -0,0 +1,5 @@ +/* + * Styles for sidebar notices are maintained in a separate file for each color scheme so we can load them independently when needed. +*/ +@import "variables"; +@import "../upsell-banner"; diff --git a/projects/packages/masterbar/src/admin-color-schemes/colors/blue/_variables.scss b/projects/packages/masterbar/src/admin-color-schemes/colors/blue/_variables.scss new file mode 100644 index 0000000000000..a35d489fae115 --- /dev/null +++ b/projects/packages/masterbar/src/admin-color-schemes/colors/blue/_variables.scss @@ -0,0 +1,26 @@ +// make use of color studio variables for consistency across products +@import 'node_modules/@automattic/color-studio/dist/color-variables.scss'; +@import 'node_modules/@automattic/color-studio/dist/color-variables-rgb.scss'; + +// Core overrides +$masterbar-background: #52accc; +$menu-submenu-background: #4796b3; +$text-color: #fff; +$highlight-color: #096484; +$base-color-rgb: rgb(82, 172, 204); +$nav-unification-sidebar-text-alternative: #e2ecf1; +$menu-highlight-text: #fff; + +// Calypso color variables used in e.g. inline help +$color-primary: #096484; // Calypso --color-primary +$color-primary-dark: $studio-blue-70; // Calypso --color-primary-dark +$color-primary-light: $studio-blue-30; // Calypso --color-primary-light +$color-surface: $studio-white; // Calypso --color-surface +$color-neutral-100-rgb: $studio-gray-100-rgb; // Calypso --color-neutral-100-rgb + +// Sidebar Nudges +$menu-nudge-background: $studio-white; +$menu-nudge-text-color: $studio-black; +$menu-nudge-cta-background: $highlight-color; +$menu-nudge-cta-color: $studio-white; +$menu-nudge-cta-background-hover: $studio-blue-60; diff --git a/projects/packages/masterbar/src/admin-color-schemes/colors/blue/colors.scss b/projects/packages/masterbar/src/admin-color-schemes/colors/blue/colors.scss new file mode 100644 index 0000000000000..5090216474c9a --- /dev/null +++ b/projects/packages/masterbar/src/admin-color-schemes/colors/blue/colors.scss @@ -0,0 +1,5 @@ +@import "variables"; +@import "../_core-overrides"; +@import "../_gutenberg"; +@import "../_inline-help"; +@import "sidebar-notice"; diff --git a/projects/packages/masterbar/src/admin-color-schemes/colors/blue/sidebar-notice.scss b/projects/packages/masterbar/src/admin-color-schemes/colors/blue/sidebar-notice.scss new file mode 100644 index 0000000000000..3e1b6380cd81e --- /dev/null +++ b/projects/packages/masterbar/src/admin-color-schemes/colors/blue/sidebar-notice.scss @@ -0,0 +1,5 @@ +/* + * Styles for sidebar notices are maintained in a separate file for each color scheme so we can load them independently when needed. +*/ +@import "variables"; +@import "../upsell-banner"; diff --git a/projects/packages/masterbar/src/admin-color-schemes/colors/classic-blue/_variables.scss b/projects/packages/masterbar/src/admin-color-schemes/colors/classic-blue/_variables.scss new file mode 100644 index 0000000000000..7d9a7a55b8926 --- /dev/null +++ b/projects/packages/masterbar/src/admin-color-schemes/colors/classic-blue/_variables.scss @@ -0,0 +1,49 @@ +// make use of color studio variables for consistency across products +@import 'node_modules/@automattic/color-studio/dist/color-variables.scss'; +@import 'node_modules/@automattic/color-studio/dist/color-variables-rgb.scss'; + +// core variables (core _variables.scss) +$base-color: $studio-blue-60; // Calypso --color-masterbar-background +$icon-color: $studio-gray-50; // Calypso --color-sidebar-gridicon-fill +$highlight-color: $studio-orange-50; // Calypso --color-accent +$notification-color: $studio-orange-30; // Calypso --color-masterbar-unread-dot-background + +// global (core _variables.scss) +$body-background: $studio-gray-0; // Calypso --color-surface-backdrop + +// admin menu & admin-bar (core _variables.scss) +$menu-text: $studio-gray-80; // Calypso --color-sidebar-text +$menu-background: $studio-gray-5; // Calypso --color-sidebar-background +$menu-highlight-text: $studio-blue-50; // Calypso --color-sidebar-menu-hover-text +$menu-highlight-icon: $menu-highlight-text; +$menu-highlight-background: $studio-white; // Calypso --color-sidebar-menu-hover-background +$menu-current-text: $studio-white; // Calypso --color-sidebar-menu-selected-text +$menu-current-icon: $menu-current-text; +$menu-current-background: $studio-gray-60; // Calypso --color-sidebar-menu-selected-background +$menu-submenu-text: $studio-white; // Calypso --color-sidebar-submenu-text +$menu-submenu-background: $studio-blue-60; // Calypso --color-sidebar-submenu-background +$menu-submenu-focus-text: $studio-orange-30; // Calypso --color-sidebar-submenu-hover-text +$menu-submenu-current-text: $studio-white; // Calypso --color-sidebar-submenu-text + +// masterbar overrides +$masterbar-background: $studio-blue-60; // Calypso --color-masterbar-background +$masterbar-highlight-background: $studio-blue-70; // Calypso --color-masterbar-item-hover-background +$masterbar-active-background: $studio-blue-90; // Calypso --color-masterbar-item-active-background + +// nav unification overrides +$nav-unification-sidebar-border: $studio-gray-10; // Calypso --color-sidebar-border +$nav-unification-sidebar-text-alternative: $studio-gray-50; // Calypso --color-sidebar-text-alternative + +// Calypso color variables used in e.g. inline help +$color-primary: $studio-blue-50; // Calypso --color-primary +$color-primary-dark: $studio-blue-70; // Calypso --color-primary-dark +$color-primary-light: $studio-blue-30; // Calypso --color-primary-light +$color-surface: $studio-white; // Calypso --color-surface +$color-neutral-100-rgb: $studio-gray-100-rgb; // Calypso --color-neutral-100-rgb + +// Sidebar Nudges +$menu-nudge-background: $studio-white; +$menu-nudge-text-color: $studio-black; +$menu-nudge-cta-background: $highlight-color; +$menu-nudge-cta-color: $studio-white; +$menu-nudge-cta-background-hover: $studio-orange-60; diff --git a/projects/packages/masterbar/src/admin-color-schemes/colors/classic-blue/colors.scss b/projects/packages/masterbar/src/admin-color-schemes/colors/classic-blue/colors.scss new file mode 100644 index 0000000000000..07136f2553c45 --- /dev/null +++ b/projects/packages/masterbar/src/admin-color-schemes/colors/classic-blue/colors.scss @@ -0,0 +1,3 @@ +@import "variables"; +@import "../admin"; +@import "sidebar-notice"; diff --git a/projects/packages/masterbar/src/admin-color-schemes/colors/classic-blue/sidebar-notice.scss b/projects/packages/masterbar/src/admin-color-schemes/colors/classic-blue/sidebar-notice.scss new file mode 100644 index 0000000000000..3e1b6380cd81e --- /dev/null +++ b/projects/packages/masterbar/src/admin-color-schemes/colors/classic-blue/sidebar-notice.scss @@ -0,0 +1,5 @@ +/* + * Styles for sidebar notices are maintained in a separate file for each color scheme so we can load them independently when needed. +*/ +@import "variables"; +@import "../upsell-banner"; diff --git a/projects/packages/masterbar/src/admin-color-schemes/colors/classic-bright/_variables.scss b/projects/packages/masterbar/src/admin-color-schemes/colors/classic-bright/_variables.scss new file mode 100644 index 0000000000000..62f993e070734 --- /dev/null +++ b/projects/packages/masterbar/src/admin-color-schemes/colors/classic-bright/_variables.scss @@ -0,0 +1,49 @@ +// make use of color studio variables for consistency across products +@import 'node_modules/@automattic/color-studio/dist/color-variables.scss'; +@import 'node_modules/@automattic/color-studio/dist/color-variables-rgb.scss'; + +// core variables (core _variables.scss) +$base-color: $studio-blue-60; // Calypso --color-masterbar-background +$icon-color: $studio-gray-50; // Calypso --color-sidebar-gridicon-fill +$highlight-color: $studio-pink-50; // Calypso --color-accent +$notification-color: $studio-pink-20; // Calypso --color-masterbar-unread-dot-background + +// global (core _variables.scss) +$body-background: $studio-gray-0; // Calypso --color-surface-backdrop + +// admin menu & admin-bar (core _variables.scss) +$menu-text: $studio-gray-80; // Calypso --color-sidebar-text +$menu-background: $studio-white; // Calypso --color-sidebar-background +$menu-highlight-text: $studio-gray-90; // Calypso --color-sidebar-menu-hover-text +$menu-highlight-icon: $menu-highlight-text; +$menu-highlight-background: $studio-gray-5; // Calypso --color-sidebar-menu-hover-background +$menu-current-text: $studio-blue-70; // Calypso --color-sidebar-menu-selected-text +$menu-current-icon: $menu-current-text; +$menu-current-background: $studio-blue-5; // Calypso --color-sidebar-menu-selected-background +$menu-submenu-text: $studio-blue-70; // Calypso --color-sidebar-submenu-text +$menu-submenu-background: $studio-blue-0; // Calypso --color-sidebar-submenu-background +$menu-submenu-focus-text: $studio-pink-50; // Calypso --color-sidebar-submenu-hover-text +$menu-submenu-current-text: $studio-pink-50; // Calypso --color-sidebar-submenu-selected-text + +// masterbar overrides +$masterbar-background: $studio-blue-60; // Calypso --color-masterbar-background +$masterbar-highlight-background: $studio-blue-70; // Calypso --color-masterbar-item-hover-background +$masterbar-active-background: $studio-blue-90; // Calypso --color-masterbar-item-active-background + +// nav unification overrides +$nav-unification-sidebar-border: $studio-gray-5; // Calypso --color-sidebar-border +$nav-unification-sidebar-text-alternative: $studio-gray-50; // Calypso --color-sidebar-text-alternative + +// Calypso color variables used in e.g. inline help +$color-primary: $studio-blue-50; // Calypso --color-primary +$color-primary-dark: $studio-blue-70; // Calypso --color-primary-dark +$color-primary-light: $studio-blue-30; // Calypso --color-primary-light +$color-surface: $studio-white; // Calypso --color-surface +$color-neutral-100-rgb: $studio-gray-100-rgb; // Calypso --color-neutral-100-rgb + +// Sidebar Nudges +$menu-nudge-background: $studio-gray-80; +$menu-nudge-text-color: $studio-white; +$menu-nudge-cta-background: $highlight-color; +$menu-nudge-cta-color: $studio-white; +$menu-nudge-cta-background-hover: $studio-pink-60; diff --git a/projects/packages/masterbar/src/admin-color-schemes/colors/classic-bright/colors.scss b/projects/packages/masterbar/src/admin-color-schemes/colors/classic-bright/colors.scss new file mode 100644 index 0000000000000..07136f2553c45 --- /dev/null +++ b/projects/packages/masterbar/src/admin-color-schemes/colors/classic-bright/colors.scss @@ -0,0 +1,3 @@ +@import "variables"; +@import "../admin"; +@import "sidebar-notice"; diff --git a/projects/packages/masterbar/src/admin-color-schemes/colors/classic-bright/sidebar-notice.scss b/projects/packages/masterbar/src/admin-color-schemes/colors/classic-bright/sidebar-notice.scss new file mode 100644 index 0000000000000..3e1b6380cd81e --- /dev/null +++ b/projects/packages/masterbar/src/admin-color-schemes/colors/classic-bright/sidebar-notice.scss @@ -0,0 +1,5 @@ +/* + * Styles for sidebar notices are maintained in a separate file for each color scheme so we can load them independently when needed. +*/ +@import "variables"; +@import "../upsell-banner"; diff --git a/projects/packages/masterbar/src/admin-color-schemes/colors/classic-dark/_variables.scss b/projects/packages/masterbar/src/admin-color-schemes/colors/classic-dark/_variables.scss new file mode 100644 index 0000000000000..e63ca79f99393 --- /dev/null +++ b/projects/packages/masterbar/src/admin-color-schemes/colors/classic-dark/_variables.scss @@ -0,0 +1,49 @@ +// make use of color studio variables for consistency across products +@import 'node_modules/@automattic/color-studio/dist/color-variables.scss'; +@import 'node_modules/@automattic/color-studio/dist/color-variables-rgb.scss'; + +// core variables (core _variables.scss) +$base-color: #101517; // Calypso --color-masterbar-background +$icon-color: #a2aab2; // Calypso --color-sidebar-gridicon-fill +$highlight-color: $studio-pink-50;// Calypso --color-accent +$notification-color: $highlight-color; + +// global (core _variables.scss) +$body-background: $studio-gray-0; // Calypso --color-surface-backdrop + +// admin menu & admin-bar (core _variables.scss) +$menu-text: #eeeeee; // Calypso --color-sidebar-text +$menu-background: #23282d; // Calypso --color-sidebar-background +$menu-highlight-text: #00b9eb; // Calypso --color-sidebar-menu-hover-text +$menu-highlight-icon: $menu-highlight-text; +$menu-highlight-background: #1a1e23; // Calypso --color-sidebar-menu-hover-background +$menu-current-text: #ffffff; // Calypso --color-sidebar-menu-selected-text +$menu-current-icon: $menu-current-text; +$menu-current-background: #0073aa; // Calypso --color-sidebar-menu-selected-background +$menu-submenu-text: #b4b9be; // Calypso --color-sidebar-submenu-text +$menu-submenu-background: #32373c; // Calypso --color-sidebar-submenu-background +$menu-submenu-focus-text: #00b9eb; // Calypso --color-sidebar-submenu-hover-text +$menu-submenu-current-text: $studio-white; // Calypso --color-sidebar-submenu-selected-text + +// masterbar overrides +$masterbar-background: $base-color; // Calypso --color-masterbar-background +$masterbar-highlight-background: #333; // Calypso --color-masterbar-item-hover-background +$masterbar-active-background: #23282d; // Calypso --color-masterbar-item-active-background + +// nav unification overrides +$nav-unification-sidebar-border: #333333; // Calypso --color-sidebar-border +$nav-unification-sidebar-text-alternative: #a2aab2; // Calypso --color-sidebar-text-alternative + +// Calypso color variables used in e.g. inline help +$color-primary: $studio-gray-90; // Calypso --color-primary +$color-primary-dark: $studio-gray-70; // Calypso --color-primary-dark +$color-primary-light: $studio-gray-30; // Calypso --color-primary-light +$color-surface: $studio-white; // Calypso --color-surface +$color-neutral-100-rgb: $studio-gray-100-rgb; // Calypso --color-neutral-100-rgb + +// Sidebar Nudges +$menu-nudge-background: $studio-white; +$menu-nudge-text-color: $studio-black; +$menu-nudge-cta-background: $highlight-color; +$menu-nudge-cta-color: $studio-white; +$menu-nudge-cta-background-hover: $studio-pink-60; diff --git a/projects/packages/masterbar/src/admin-color-schemes/colors/classic-dark/colors.scss b/projects/packages/masterbar/src/admin-color-schemes/colors/classic-dark/colors.scss new file mode 100644 index 0000000000000..07136f2553c45 --- /dev/null +++ b/projects/packages/masterbar/src/admin-color-schemes/colors/classic-dark/colors.scss @@ -0,0 +1,3 @@ +@import "variables"; +@import "../admin"; +@import "sidebar-notice"; diff --git a/projects/packages/masterbar/src/admin-color-schemes/colors/classic-dark/sidebar-notice.scss b/projects/packages/masterbar/src/admin-color-schemes/colors/classic-dark/sidebar-notice.scss new file mode 100644 index 0000000000000..3e1b6380cd81e --- /dev/null +++ b/projects/packages/masterbar/src/admin-color-schemes/colors/classic-dark/sidebar-notice.scss @@ -0,0 +1,5 @@ +/* + * Styles for sidebar notices are maintained in a separate file for each color scheme so we can load them independently when needed. +*/ +@import "variables"; +@import "../upsell-banner"; diff --git a/projects/packages/masterbar/src/admin-color-schemes/colors/coffee/_variables.scss b/projects/packages/masterbar/src/admin-color-schemes/colors/coffee/_variables.scss new file mode 100644 index 0000000000000..46c2c50e74e76 --- /dev/null +++ b/projects/packages/masterbar/src/admin-color-schemes/colors/coffee/_variables.scss @@ -0,0 +1,25 @@ +// make use of color studio variables for consistency across products +@import 'node_modules/@automattic/color-studio/dist/color-variables.scss'; +@import 'node_modules/@automattic/color-studio/dist/color-variables-rgb.scss'; + +$masterbar-background: #59524c; +$menu-submenu-background: #46403c; +$text-color: #fff; +$highlight-color: #c7a589; +$base-color-rgb: rgb(89, 82, 76); +$nav-unification-sidebar-text-alternative: #f6f7f7; +$menu-highlight-text: #fff; + +// Calypso color variables used in e.g. inline help +$color-primary: #c7a589; // Calypso --color-primary +$color-primary-dark: $studio-orange-70; // Calypso --color-primary-dark +$color-primary-light: $studio-orange-30; // Calypso --color-primary-light +$color-surface: $studio-white; // Calypso --color-surface +$color-neutral-100-rgb: $studio-gray-100-rgb; // Calypso --color-neutral-100-rgb + +// Sidebar Nudges +$menu-nudge-background: $studio-white; +$menu-nudge-text-color: $studio-black; +$menu-nudge-cta-background: $highlight-color; +$menu-nudge-cta-color: $studio-white; +$menu-nudge-cta-background-hover: $studio-orange-60; diff --git a/projects/packages/masterbar/src/admin-color-schemes/colors/coffee/colors.scss b/projects/packages/masterbar/src/admin-color-schemes/colors/coffee/colors.scss new file mode 100644 index 0000000000000..5090216474c9a --- /dev/null +++ b/projects/packages/masterbar/src/admin-color-schemes/colors/coffee/colors.scss @@ -0,0 +1,5 @@ +@import "variables"; +@import "../_core-overrides"; +@import "../_gutenberg"; +@import "../_inline-help"; +@import "sidebar-notice"; diff --git a/projects/packages/masterbar/src/admin-color-schemes/colors/coffee/sidebar-notice.scss b/projects/packages/masterbar/src/admin-color-schemes/colors/coffee/sidebar-notice.scss new file mode 100644 index 0000000000000..3e1b6380cd81e --- /dev/null +++ b/projects/packages/masterbar/src/admin-color-schemes/colors/coffee/sidebar-notice.scss @@ -0,0 +1,5 @@ +/* + * Styles for sidebar notices are maintained in a separate file for each color scheme so we can load them independently when needed. +*/ +@import "variables"; +@import "../upsell-banner"; diff --git a/projects/packages/masterbar/src/admin-color-schemes/colors/contrast/_variables.scss b/projects/packages/masterbar/src/admin-color-schemes/colors/contrast/_variables.scss new file mode 100644 index 0000000000000..c132a3c394cae --- /dev/null +++ b/projects/packages/masterbar/src/admin-color-schemes/colors/contrast/_variables.scss @@ -0,0 +1,49 @@ +// make use of color studio variables for consistency across products +@import 'node_modules/@automattic/color-studio/dist/color-variables.scss'; +@import 'node_modules/@automattic/color-studio/dist/color-variables-rgb.scss'; + +// core variables (core _variables.scss) +$base-color: $studio-gray-100; // Calypso --color-masterbar-background +$icon-color: $studio-gray-90; // Calypso --color-sidebar-gridicon-fill +$highlight-color: $studio-blue-70;// Calypso --color-accent +$notification-color: $studio-yellow-20; // Calypso --color-masterbar-unread-dot-background + +// global (core _variables.scss) +$body-background: $studio-white; // Calypso --color-surface-backdrop + +// admin menu & admin-bar (core _variables.scss) +$menu-text: $studio-gray-90; // Calypso --color-sidebar-text +$menu-background: $studio-white; // Calypso --color-sidebar-background +$menu-highlight-text: $studio-white; // Calypso --color-sidebar-menu-hover-text +$menu-highlight-icon: $menu-highlight-text; +$menu-highlight-background: $studio-gray-60; // Calypso --color-sidebar-menu-hover-background +$menu-current-text: $studio-white; // Calypso --color-sidebar-menu-selected-text +$menu-current-icon: $menu-current-text; +$menu-current-background: $studio-gray-100; // Calypso --color-sidebar-menu-selected-background +$menu-submenu-text: $studio-gray-10; // Calypso --color-sidebar-submenu-text +$menu-submenu-background: $studio-gray-90; // Calypso --color-sidebar-submenu-background +$menu-submenu-focus-text: $studio-yellow-20; // Calypso --color-sidebar-submenu-hover-text +$menu-submenu-current-text: $studio-white; // Calypso --color-sidebar-submenu-selected-text + +// masterbar overrides +$masterbar-background: $base-color; // Calypso --color-masterbar-background +$masterbar-highlight-background: $studio-gray-80; // Calypso --color-masterbar-item-hover-background +$masterbar-active-background: $studio-gray-60; // Calypso --color-masterbar-item-active-background + +// nav unification overrides +$nav-unification-sidebar-border: $studio-gray-5; // Calypso --color-sidebar-border +$nav-unification-sidebar-text-alternative: $studio-gray-90; // Calypso --color-sidebar-text-alternative + +// Calypso color variables used in e.g. inline help +$color-primary: $studio-gray-80; // Calypso --color-primary +$color-primary-dark: $studio-gray-100; // Calypso --color-primary-dark +$color-primary-light: $studio-gray-60; // Calypso --color-primary-light +$color-surface: $studio-white; // Calypso --color-surface +$color-neutral-100-rgb: $studio-gray-100-rgb; // Calypso --color-neutral-100-rgb + +// Sidebar Nudges +$menu-nudge-background: $studio-gray-80; +$menu-nudge-text-color: $studio-white; +$menu-nudge-cta-background: $highlight-color; +$menu-nudge-cta-color: $studio-white; +$menu-nudge-cta-background-hover: $studio-blue-60; diff --git a/projects/packages/masterbar/src/admin-color-schemes/colors/contrast/colors.scss b/projects/packages/masterbar/src/admin-color-schemes/colors/contrast/colors.scss new file mode 100644 index 0000000000000..07136f2553c45 --- /dev/null +++ b/projects/packages/masterbar/src/admin-color-schemes/colors/contrast/colors.scss @@ -0,0 +1,3 @@ +@import "variables"; +@import "../admin"; +@import "sidebar-notice"; diff --git a/projects/packages/masterbar/src/admin-color-schemes/colors/contrast/sidebar-notice.scss b/projects/packages/masterbar/src/admin-color-schemes/colors/contrast/sidebar-notice.scss new file mode 100644 index 0000000000000..3e1b6380cd81e --- /dev/null +++ b/projects/packages/masterbar/src/admin-color-schemes/colors/contrast/sidebar-notice.scss @@ -0,0 +1,5 @@ +/* + * Styles for sidebar notices are maintained in a separate file for each color scheme so we can load them independently when needed. +*/ +@import "variables"; +@import "../upsell-banner"; diff --git a/projects/packages/masterbar/src/admin-color-schemes/colors/ectoplasm/_variables.scss b/projects/packages/masterbar/src/admin-color-schemes/colors/ectoplasm/_variables.scss new file mode 100644 index 0000000000000..48013c2306103 --- /dev/null +++ b/projects/packages/masterbar/src/admin-color-schemes/colors/ectoplasm/_variables.scss @@ -0,0 +1,25 @@ +// make use of color studio variables for consistency across products +@import 'node_modules/@automattic/color-studio/dist/color-variables.scss'; +@import 'node_modules/@automattic/color-studio/dist/color-variables-rgb.scss'; + +$masterbar-background: #523f6d; +$menu-submenu-background: #413256; +$text-color: #fff; +$highlight-color: #a3b745; +$base-color-rgb: rgb(82, 63, 109); +$nav-unification-sidebar-text-alternative: #ffffff; +$menu-highlight-text: #fff; + +// Calypso color variables used in e.g. inline help +$color-primary: #a3b745; // Calypso --color-primary +$color-primary-dark: #536700; // Calypso --color-primary-dark +$color-primary-light: #b5de00; // Calypso --color-primary-light +$color-surface: $studio-white; // Calypso --color-surface +$color-neutral-100-rgb: $studio-gray-100-rgb; // Calypso --color-neutral-100-rgb + +// Sidebar Nudges +$menu-nudge-background: $studio-white; +$menu-nudge-text-color: $studio-black; +$menu-nudge-cta-background: $highlight-color; +$menu-nudge-cta-color: $studio-white; +$menu-nudge-cta-background-hover: rgb(100, 125, 0); diff --git a/projects/packages/masterbar/src/admin-color-schemes/colors/ectoplasm/colors.scss b/projects/packages/masterbar/src/admin-color-schemes/colors/ectoplasm/colors.scss new file mode 100644 index 0000000000000..5090216474c9a --- /dev/null +++ b/projects/packages/masterbar/src/admin-color-schemes/colors/ectoplasm/colors.scss @@ -0,0 +1,5 @@ +@import "variables"; +@import "../_core-overrides"; +@import "../_gutenberg"; +@import "../_inline-help"; +@import "sidebar-notice"; diff --git a/projects/packages/masterbar/src/admin-color-schemes/colors/ectoplasm/sidebar-notice.scss b/projects/packages/masterbar/src/admin-color-schemes/colors/ectoplasm/sidebar-notice.scss new file mode 100644 index 0000000000000..3e1b6380cd81e --- /dev/null +++ b/projects/packages/masterbar/src/admin-color-schemes/colors/ectoplasm/sidebar-notice.scss @@ -0,0 +1,5 @@ +/* + * Styles for sidebar notices are maintained in a separate file for each color scheme so we can load them independently when needed. +*/ +@import "variables"; +@import "../upsell-banner"; diff --git a/projects/packages/masterbar/src/admin-color-schemes/colors/fresh/_variables.scss b/projects/packages/masterbar/src/admin-color-schemes/colors/fresh/_variables.scss new file mode 100644 index 0000000000000..404bfefec8502 --- /dev/null +++ b/projects/packages/masterbar/src/admin-color-schemes/colors/fresh/_variables.scss @@ -0,0 +1,25 @@ +// make use of color studio variables for consistency across products +@import 'node_modules/@automattic/color-studio/dist/color-variables.scss'; +@import 'node_modules/@automattic/color-studio/dist/color-variables-rgb.scss'; + +$masterbar-background: #101517; +$menu-submenu-background: #333333; +$text-color: #fff; +$highlight-color: #006fad; +$base-color-rgb: rgb(35,40,45); +$nav-unification-sidebar-text-alternative: #a2aab2; +$menu-highlight-text: #00b9eb; + +// Calypso color variables used in e.g. inline help +$color-primary: $studio-gray-90; // Calypso --color-primary +$color-primary-dark: $studio-gray-70; // Calypso --color-primary-dark +$color-primary-light: $studio-gray-30; // Calypso --color-primary-light +$color-surface: $studio-white; // Calypso --color-surface +$color-neutral-100-rgb: $studio-gray-100-rgb; // Calypso --color-neutral-100-rgb + +// Sidebar Nudges +$menu-nudge-background: $studio-white; +$menu-nudge-text-color: $studio-black; +$menu-nudge-cta-background: #2271b1; +$menu-nudge-cta-color: $studio-white; +$menu-nudge-cta-background-hover: #135e96; diff --git a/projects/packages/masterbar/src/admin-color-schemes/colors/fresh/colors.scss b/projects/packages/masterbar/src/admin-color-schemes/colors/fresh/colors.scss new file mode 100644 index 0000000000000..ba9b84d3d952d --- /dev/null +++ b/projects/packages/masterbar/src/admin-color-schemes/colors/fresh/colors.scss @@ -0,0 +1,15 @@ +@import "variables"; +@import "../_core-overrides"; +@import "../_gutenberg"; +@import "../_inline-help"; +@import "sidebar-notice"; + +// Fresh theme specifics +#wpadminbar .ab-top-menu > #wp-admin-bar-blog.my-sites > .ab-item { + background: #23282d; +} + +.site__info > .site__badge { + background: #dcdcde; + color: #1d2327; +} diff --git a/projects/packages/masterbar/src/admin-color-schemes/colors/fresh/sidebar-notice.scss b/projects/packages/masterbar/src/admin-color-schemes/colors/fresh/sidebar-notice.scss new file mode 100644 index 0000000000000..3e1b6380cd81e --- /dev/null +++ b/projects/packages/masterbar/src/admin-color-schemes/colors/fresh/sidebar-notice.scss @@ -0,0 +1,5 @@ +/* + * Styles for sidebar notices are maintained in a separate file for each color scheme so we can load them independently when needed. +*/ +@import "variables"; +@import "../upsell-banner"; diff --git a/projects/packages/masterbar/src/admin-color-schemes/colors/light/_variables.scss b/projects/packages/masterbar/src/admin-color-schemes/colors/light/_variables.scss new file mode 100644 index 0000000000000..09fcaeebba2d8 --- /dev/null +++ b/projects/packages/masterbar/src/admin-color-schemes/colors/light/_variables.scss @@ -0,0 +1,25 @@ +// make use of color studio variables for consistency across products +@import 'node_modules/@automattic/color-studio/dist/color-variables.scss'; +@import 'node_modules/@automattic/color-studio/dist/color-variables-rgb.scss'; + +$masterbar-background: #e5e5e5; +$menu-submenu-background: #fff; +$text-color: #333; +$highlight-color: #888; +$base-color-rgb: rgb(229, 229, 229); +$nav-unification-sidebar-text-alternative: #1d2327; +$menu-highlight-text: #fff; + +// Calypso color variables used in e.g. inline help +$color-primary: #04a4cc; // Calypso --color-primary +$color-primary-dark: $studio-blue-70; // Calypso --color-primary-dark +$color-primary-light: $studio-blue-30; // Calypso --color-primary-light +$color-surface: $studio-white; // Calypso --color-surface +$color-neutral-100-rgb: $studio-gray-100-rgb; // Calypso --color-neutral-100-rgb + +// Sidebar Nudges +$menu-nudge-background: $studio-white; +$menu-nudge-text-color: $studio-black; +$menu-nudge-cta-background: $color-primary; +$menu-nudge-cta-color: $studio-white; +$menu-nudge-cta-background-hover: $studio-blue-60; diff --git a/projects/packages/masterbar/src/admin-color-schemes/colors/light/colors.scss b/projects/packages/masterbar/src/admin-color-schemes/colors/light/colors.scss new file mode 100644 index 0000000000000..5601a14f1e855 --- /dev/null +++ b/projects/packages/masterbar/src/admin-color-schemes/colors/light/colors.scss @@ -0,0 +1,13 @@ +@import "variables"; + +// Light theme specifics +#wpadminbar #wp-admin-bar-blog > a.ab-item:before, +#wpadminbar #wp-admin-bar-newdash > a.ab-item:before, +#wpadminbar #wp-admin-bar-notes .noticon-bell:before { + filter: brightness(0.1); +} + +@import "../_core-overrides"; +@import "../_gutenberg"; +@import "../_inline-help"; +@import "sidebar-notice"; diff --git a/projects/packages/masterbar/src/admin-color-schemes/colors/light/sidebar-notice.scss b/projects/packages/masterbar/src/admin-color-schemes/colors/light/sidebar-notice.scss new file mode 100644 index 0000000000000..3e1b6380cd81e --- /dev/null +++ b/projects/packages/masterbar/src/admin-color-schemes/colors/light/sidebar-notice.scss @@ -0,0 +1,5 @@ +/* + * Styles for sidebar notices are maintained in a separate file for each color scheme so we can load them independently when needed. +*/ +@import "variables"; +@import "../upsell-banner"; diff --git a/projects/packages/masterbar/src/admin-color-schemes/colors/midnight/_variables.scss b/projects/packages/masterbar/src/admin-color-schemes/colors/midnight/_variables.scss new file mode 100644 index 0000000000000..63aecdea5d4d1 --- /dev/null +++ b/projects/packages/masterbar/src/admin-color-schemes/colors/midnight/_variables.scss @@ -0,0 +1,25 @@ +// make use of color studio variables for consistency across products +@import 'node_modules/@automattic/color-studio/dist/color-variables.scss'; +@import 'node_modules/@automattic/color-studio/dist/color-variables-rgb.scss'; + +$masterbar-background: #363b3f; +$menu-submenu-background: #26292c; +$text-color: #fff; +$highlight-color: #e14d43; +$base-color-rgb: rgb(54, 59, 63); +$nav-unification-sidebar-text-alternative: #fff; +$menu-highlight-text: #fff; + +// Calypso color variables used in e.g. inline help +$color-primary: #e14d43; // Calypso --color-primary +$color-primary-dark: $studio-red-70; // Calypso --color-primary-dark +$color-primary-light: $studio-red-30; // Calypso --color-primary-light +$color-surface: $studio-white; // Calypso --color-surface +$color-neutral-100-rgb: $studio-gray-100-rgb; // Calypso --color-neutral-100-rgb + +// Sidebar Nudges +$menu-nudge-background: $studio-white; +$menu-nudge-text-color: $studio-black; +$menu-nudge-cta-background: $highlight-color; +$menu-nudge-cta-color: $studio-white; +$menu-nudge-cta-background-hover: #00417d; diff --git a/projects/packages/masterbar/src/admin-color-schemes/colors/midnight/colors.scss b/projects/packages/masterbar/src/admin-color-schemes/colors/midnight/colors.scss new file mode 100644 index 0000000000000..5090216474c9a --- /dev/null +++ b/projects/packages/masterbar/src/admin-color-schemes/colors/midnight/colors.scss @@ -0,0 +1,5 @@ +@import "variables"; +@import "../_core-overrides"; +@import "../_gutenberg"; +@import "../_inline-help"; +@import "sidebar-notice"; diff --git a/projects/packages/masterbar/src/admin-color-schemes/colors/midnight/sidebar-notice.scss b/projects/packages/masterbar/src/admin-color-schemes/colors/midnight/sidebar-notice.scss new file mode 100644 index 0000000000000..3e1b6380cd81e --- /dev/null +++ b/projects/packages/masterbar/src/admin-color-schemes/colors/midnight/sidebar-notice.scss @@ -0,0 +1,5 @@ +/* + * Styles for sidebar notices are maintained in a separate file for each color scheme so we can load them independently when needed. +*/ +@import "variables"; +@import "../upsell-banner"; diff --git a/projects/packages/masterbar/src/admin-color-schemes/colors/modern/_variables.scss b/projects/packages/masterbar/src/admin-color-schemes/colors/modern/_variables.scss new file mode 100644 index 0000000000000..b385c1ad7d4e5 --- /dev/null +++ b/projects/packages/masterbar/src/admin-color-schemes/colors/modern/_variables.scss @@ -0,0 +1,25 @@ +// make use of color studio variables for consistency across products +@import 'node_modules/@automattic/color-studio/dist/color-variables.scss'; +@import 'node_modules/@automattic/color-studio/dist/color-variables-rgb.scss'; + +$masterbar-background: #1e1e1e; +$menu-submenu-background: #0c0c0c; +$text-color: #fff; +$highlight-color: #3858e9; +$base-color-rgb: rgb(30, 30, 30); +$nav-unification-sidebar-text-alternative: #c3c4c7; +$menu-highlight-text: #fff; + +// Calypso color variables used in e.g. inline help +$color-primary: #3858e9; // Calypso --color-primary +$color-primary-dark: $studio-blue-70; // Calypso --color-primary-dark +$color-primary-light: $studio-blue-30; // Calypso --color-primary-light +$color-surface: $studio-white; // Calypso --color-surface +$color-neutral-100-rgb: $studio-gray-100-rgb; // Calypso --color-neutral-100-rgb + +// Sidebar Nudges +$menu-nudge-background: $studio-white; +$menu-nudge-text-color: $studio-black; +$menu-nudge-cta-background: $highlight-color; +$menu-nudge-cta-color: $studio-white; +$menu-nudge-cta-background-hover: #2145e6; diff --git a/projects/packages/masterbar/src/admin-color-schemes/colors/modern/colors.scss b/projects/packages/masterbar/src/admin-color-schemes/colors/modern/colors.scss new file mode 100644 index 0000000000000..5090216474c9a --- /dev/null +++ b/projects/packages/masterbar/src/admin-color-schemes/colors/modern/colors.scss @@ -0,0 +1,5 @@ +@import "variables"; +@import "../_core-overrides"; +@import "../_gutenberg"; +@import "../_inline-help"; +@import "sidebar-notice"; diff --git a/projects/packages/masterbar/src/admin-color-schemes/colors/modern/sidebar-notice.scss b/projects/packages/masterbar/src/admin-color-schemes/colors/modern/sidebar-notice.scss new file mode 100644 index 0000000000000..3e1b6380cd81e --- /dev/null +++ b/projects/packages/masterbar/src/admin-color-schemes/colors/modern/sidebar-notice.scss @@ -0,0 +1,5 @@ +/* + * Styles for sidebar notices are maintained in a separate file for each color scheme so we can load them independently when needed. +*/ +@import "variables"; +@import "../upsell-banner"; diff --git a/projects/packages/masterbar/src/admin-color-schemes/colors/nightfall/_variables.scss b/projects/packages/masterbar/src/admin-color-schemes/colors/nightfall/_variables.scss new file mode 100644 index 0000000000000..b29e6c4085420 --- /dev/null +++ b/projects/packages/masterbar/src/admin-color-schemes/colors/nightfall/_variables.scss @@ -0,0 +1,49 @@ +// make use of color studio variables for consistency across products +@import 'node_modules/@automattic/color-studio/dist/color-variables.scss'; +@import 'node_modules/@automattic/color-studio/dist/color-variables-rgb.scss'; + +// core variables (core _variables.scss) +$base-color: $studio-blue-100; // Calypso --color-masterbar-background +$icon-color: $studio-blue-10; // Calypso --color-sidebar-gridicon-fill +$highlight-color: $studio-blue-50;// Calypso --color-accent +$notification-color: $studio-blue-30; // Calypso --color-masterbar-unread-dot-background + +// global (core _variables.scss) +$body-background: $studio-gray-0; // Calypso --color-surface-backdrop + +// admin menu & admin-bar (core _variables.scss) +$menu-text: $studio-blue-5; // Calypso --color-sidebar-text +$menu-background: $studio-blue-80; // Calypso --color-sidebar-background +$menu-highlight-text: $studio-white; // Calypso --color-sidebar-menu-hover-text +$menu-highlight-icon: $menu-highlight-text; +$menu-highlight-background: $studio-blue-70; // Calypso --color-sidebar-menu-hover-background +$menu-current-text: $studio-white; // Calypso --color-sidebar-menu-selected-text +$menu-current-icon: $menu-current-text; +$menu-current-background: $studio-blue-100; // Calypso --color-sidebar-menu-selected-background +$menu-submenu-text: $studio-white; // Calypso --color-sidebar-submenu-text +$menu-submenu-background: $studio-blue-90; // Calypso --color-sidebar-submenu-background +$menu-submenu-focus-text: $studio-blue-20; // Calypso --color-sidebar-submenu-hover-text +$menu-submenu-current-text: $studio-white; // Calypso --color-sidebar-submenu-text + +// masterbar overrides +$masterbar-background: $studio-blue-100; // Calypso --color-masterbar-background +$masterbar-highlight-background: $studio-blue-90; // Calypso --color-masterbar-item-hover-background +$masterbar-active-background: $studio-blue-80; // Calypso --color-masterbar-item-active-background + +// nav unification overrides +$nav-unification-sidebar-border: $studio-blue-90; // Calypso --color-sidebar-border +$nav-unification-sidebar-text-alternative: $studio-blue-20; // Calypso --color-sidebar-text-alternative + +// Calypso color variables used in e.g. inline help +$color-primary: $studio-gray-90; // Calypso --color-primary +$color-primary-dark: $studio-gray-70; // Calypso --color-primary-dark +$color-primary-light: $studio-gray-30; // Calypso --color-primary-light +$color-surface: $studio-white; // Calypso --color-surface +$color-neutral-100-rgb: $studio-gray-100-rgb; // Calypso --color-neutral-100-rgb + +// Sidebar Nudges +$menu-nudge-background: $studio-white; +$menu-nudge-text-color: $studio-black; +$menu-nudge-cta-background: $highlight-color; +$menu-nudge-cta-color: $studio-white; +$menu-nudge-cta-background-hover: $studio-blue-60; diff --git a/projects/packages/masterbar/src/admin-color-schemes/colors/nightfall/colors.scss b/projects/packages/masterbar/src/admin-color-schemes/colors/nightfall/colors.scss new file mode 100644 index 0000000000000..07136f2553c45 --- /dev/null +++ b/projects/packages/masterbar/src/admin-color-schemes/colors/nightfall/colors.scss @@ -0,0 +1,3 @@ +@import "variables"; +@import "../admin"; +@import "sidebar-notice"; diff --git a/projects/packages/masterbar/src/admin-color-schemes/colors/nightfall/sidebar-notice.scss b/projects/packages/masterbar/src/admin-color-schemes/colors/nightfall/sidebar-notice.scss new file mode 100644 index 0000000000000..3e1b6380cd81e --- /dev/null +++ b/projects/packages/masterbar/src/admin-color-schemes/colors/nightfall/sidebar-notice.scss @@ -0,0 +1,5 @@ +/* + * Styles for sidebar notices are maintained in a separate file for each color scheme so we can load them independently when needed. +*/ +@import "variables"; +@import "../upsell-banner"; diff --git a/projects/packages/masterbar/src/admin-color-schemes/colors/ocean/_variables.scss b/projects/packages/masterbar/src/admin-color-schemes/colors/ocean/_variables.scss new file mode 100644 index 0000000000000..29ccb39585f46 --- /dev/null +++ b/projects/packages/masterbar/src/admin-color-schemes/colors/ocean/_variables.scss @@ -0,0 +1,25 @@ +// make use of color studio variables for consistency across products +@import 'node_modules/@automattic/color-studio/dist/color-variables.scss'; +@import 'node_modules/@automattic/color-studio/dist/color-variables-rgb.scss'; + +$masterbar-background: #738e96; +$menu-submenu-background: #627c83; +$text-color: #fff; +$highlight-color: #9ebaa0; +$base-color-rgb: rgb(115, 142, 150); +$nav-unification-sidebar-text-alternative: #fff; +$menu-highlight-text: #fff; + +// Calypso color variables used in e.g. inline help +$color-primary: #9ebaa0; // Calypso --color-primary +$color-primary-dark: $studio-celadon-70; // Calypso --color-primary-dark +$color-primary-light: $studio-celadon-30; // Calypso --color-primary-light +$color-surface: $studio-white; // Calypso --color-surface +$color-neutral-100-rgb: $studio-gray-100-rgb; // Calypso --color-neutral-100-rgb + +// Sidebar Nudges +$menu-nudge-background: $studio-white; +$menu-nudge-text-color: $studio-black; +$menu-nudge-cta-background: $highlight-color; +$menu-nudge-cta-color: $studio-white; +$menu-nudge-cta-background-hover: $studio-celadon-60; diff --git a/projects/packages/masterbar/src/admin-color-schemes/colors/ocean/colors.scss b/projects/packages/masterbar/src/admin-color-schemes/colors/ocean/colors.scss new file mode 100644 index 0000000000000..5090216474c9a --- /dev/null +++ b/projects/packages/masterbar/src/admin-color-schemes/colors/ocean/colors.scss @@ -0,0 +1,5 @@ +@import "variables"; +@import "../_core-overrides"; +@import "../_gutenberg"; +@import "../_inline-help"; +@import "sidebar-notice"; diff --git a/projects/packages/masterbar/src/admin-color-schemes/colors/ocean/sidebar-notice.scss b/projects/packages/masterbar/src/admin-color-schemes/colors/ocean/sidebar-notice.scss new file mode 100644 index 0000000000000..3e1b6380cd81e --- /dev/null +++ b/projects/packages/masterbar/src/admin-color-schemes/colors/ocean/sidebar-notice.scss @@ -0,0 +1,5 @@ +/* + * Styles for sidebar notices are maintained in a separate file for each color scheme so we can load them independently when needed. +*/ +@import "variables"; +@import "../upsell-banner"; diff --git a/projects/packages/masterbar/src/admin-color-schemes/colors/powder-snow/_variables.scss b/projects/packages/masterbar/src/admin-color-schemes/colors/powder-snow/_variables.scss new file mode 100644 index 0000000000000..1de98e7b49932 --- /dev/null +++ b/projects/packages/masterbar/src/admin-color-schemes/colors/powder-snow/_variables.scss @@ -0,0 +1,49 @@ +// make use of color studio variables for consistency across products +@import 'node_modules/@automattic/color-studio/dist/color-variables.scss'; +@import 'node_modules/@automattic/color-studio/dist/color-variables-rgb.scss'; + +// core variables (core _variables.scss) +$base-color: $studio-gray-100; // Calypso --color-masterbar-background +$icon-color: $studio-gray-50; // Calypso --color-sidebar-gridicon-fill +$highlight-color: $studio-blue-50; // Calypso --color-accent +$notification-color: $studio-blue-30; // Calypso --color-masterbar-unread-dot-background + +// global (core _variables.scss) +$body-background: $studio-gray-0; // Calypso --color-surface-backdrop + +// admin menu & admin-bar (core _variables.scss) +$menu-text: $studio-gray-80; // Calypso --color-sidebar-text +$menu-background: $studio-gray-5; // Calypso --color-sidebar-background +$menu-highlight-text: $studio-blue-60; // Calypso --color-sidebar-menu-hover-text +$menu-highlight-icon: $menu-highlight-text; +$menu-highlight-background: $studio-white; // Calypso --color-sidebar-menu-hover-background +$menu-current-text: $studio-white; // Calypso --color-sidebar-menu-selected-text +$menu-current-icon: $menu-current-text; +$menu-current-background: $studio-gray-60; // Calypso --color-sidebar-menu-selected-background +$menu-submenu-text: $studio-gray-80; // Calypso --color-sidebar-submenu-text +$menu-submenu-background: $studio-gray-10; // Calypso --color-sidebar-submenu-background +$menu-submenu-focus-text: $studio-blue-60; // Calypso --color-sidebar-submenu-hover-text +$menu-submenu-current-text: $studio-gray-80; // Calypso --color-sidebar-submenu-selected-text + +// masterbar overrides +$masterbar-background: $studio-gray-100; // Calypso --color-masterbar-background +$masterbar-highlight-background: $studio-gray-80; // Calypso --color-masterbar-item-hover-background +$masterbar-active-background: $studio-gray-70; // Calypso --color-masterbar-item-active-background + +// nav unification overrides +$nav-unification-sidebar-border: $studio-gray-10; // Calypso --color-sidebar-border +$nav-unification-sidebar-text-alternative: $studio-gray-60; // Calypso --color-sidebar-text-alternative + +// Calypso color variables used in e.g. inline help +$color-primary: $studio-gray-90; // Calypso --color-primary +$color-primary-dark: $studio-gray-70; // Calypso --color-primary-dark +$color-primary-light: $studio-gray-30; // Calypso --color-primary-light +$color-surface: $studio-white; // Calypso --color-surface +$color-neutral-100-rgb: $studio-gray-100-rgb; // Calypso --color-neutral-100-rgb + +// Sidebar Nudges +$menu-nudge-background: $studio-white; +$menu-nudge-text-color: $studio-black; +$menu-nudge-cta-background: $highlight-color; +$menu-nudge-cta-color: $studio-white; +$menu-nudge-cta-background-hover: $studio-blue-60; diff --git a/projects/packages/masterbar/src/admin-color-schemes/colors/powder-snow/colors.scss b/projects/packages/masterbar/src/admin-color-schemes/colors/powder-snow/colors.scss new file mode 100644 index 0000000000000..07136f2553c45 --- /dev/null +++ b/projects/packages/masterbar/src/admin-color-schemes/colors/powder-snow/colors.scss @@ -0,0 +1,3 @@ +@import "variables"; +@import "../admin"; +@import "sidebar-notice"; diff --git a/projects/packages/masterbar/src/admin-color-schemes/colors/powder-snow/sidebar-notice.scss b/projects/packages/masterbar/src/admin-color-schemes/colors/powder-snow/sidebar-notice.scss new file mode 100644 index 0000000000000..3e1b6380cd81e --- /dev/null +++ b/projects/packages/masterbar/src/admin-color-schemes/colors/powder-snow/sidebar-notice.scss @@ -0,0 +1,5 @@ +/* + * Styles for sidebar notices are maintained in a separate file for each color scheme so we can load them independently when needed. +*/ +@import "variables"; +@import "../upsell-banner"; diff --git a/projects/packages/masterbar/src/admin-color-schemes/colors/sakura/_variables.scss b/projects/packages/masterbar/src/admin-color-schemes/colors/sakura/_variables.scss new file mode 100644 index 0000000000000..803e34d036368 --- /dev/null +++ b/projects/packages/masterbar/src/admin-color-schemes/colors/sakura/_variables.scss @@ -0,0 +1,49 @@ +// make use of color studio variables for consistency across products +@import 'node_modules/@automattic/color-studio/dist/color-variables.scss'; +@import 'node_modules/@automattic/color-studio/dist/color-variables-rgb.scss'; + +// core variables (core _variables.scss) +$base-color: $studio-celadon-70; // Calypso --color-masterbar-background +$icon-color: $studio-pink-70; // Calypso --color-sidebar-gridicon-fill +$highlight-color: $studio-blue-50; // Calypso --color-accent +$notification-color: $studio-pink-20; // Calypso Classic Bright (!) --color-masterbar-unread-dot-background + +// global (core _variables.scss) +$body-background: $studio-gray-0; // Calypso --color-surface-backdrop + +// admin menu & admin-bar (core _variables.scss) +$menu-text: $studio-pink-80; // Calypso --color-sidebar-text +$menu-background: $studio-pink-5; // Calypso --color-sidebar-background +$menu-highlight-text: $studio-pink-90; // Calypso --color-sidebar-menu-hover-text +$menu-highlight-icon: $menu-highlight-text; +$menu-highlight-background: $studio-pink-10; // Calypso --color-sidebar-menu-hover-background +$menu-current-text: $studio-white; // Calypso --color-sidebar-menu-selected-text +$menu-current-icon: $menu-current-text; +$menu-current-background: $studio-blue-50; // Calypso --color-sidebar-menu-selected-background +$menu-submenu-text: $studio-pink-0; // Calypso --color-sidebar-submenu-text +$menu-submenu-background: $studio-pink-90; // Calypso --color-sidebar-submenu-background +$menu-submenu-focus-text: $studio-blue-20; // Calypso --color-sidebar-submenu-hover-text +$menu-submenu-current-text: $studio-pink-0; // Calypso --color-sidebar-submenu-selected-text + +// masterbar overrides +$masterbar-background: $studio-celadon-70; // Calypso --color-masterbar-background +$masterbar-highlight-background: $studio-celadon-80; // Calypso --color-masterbar-item-hover-background +$masterbar-active-background: $studio-celadon-90; // Calypso --color-masterbar-item-active-background + +// nav unification overrides +$nav-unification-sidebar-border: $studio-pink-10; // Calypso --color-sidebar-border +$nav-unification-sidebar-text-alternative: $studio-pink-60; // Calypso --color-sidebar-text-alternative + +// Calypso color variables used in e.g. inline help +$color-primary: $studio-celadon-50; // Calypso --color-primary +$color-primary-dark: $studio-celadon-70; // Calypso --color-primary-dark +$color-primary-light: $studio-celadon-30; // Calypso --color-primary-light +$color-surface: $studio-white; // Calypso --color-surface +$color-neutral-100-rgb: $studio-gray-100-rgb; // Calypso --color-neutral-100-rgb + +// Sidebar Nudges +$menu-nudge-background: $studio-white; +$menu-nudge-text-color: $studio-black; +$menu-nudge-cta-background: $highlight-color; +$menu-nudge-cta-color: $studio-white; +$menu-nudge-cta-background-hover: $studio-blue-60; diff --git a/projects/packages/masterbar/src/admin-color-schemes/colors/sakura/colors.scss b/projects/packages/masterbar/src/admin-color-schemes/colors/sakura/colors.scss new file mode 100644 index 0000000000000..07136f2553c45 --- /dev/null +++ b/projects/packages/masterbar/src/admin-color-schemes/colors/sakura/colors.scss @@ -0,0 +1,3 @@ +@import "variables"; +@import "../admin"; +@import "sidebar-notice"; diff --git a/projects/packages/masterbar/src/admin-color-schemes/colors/sakura/sidebar-notice.scss b/projects/packages/masterbar/src/admin-color-schemes/colors/sakura/sidebar-notice.scss new file mode 100644 index 0000000000000..3e1b6380cd81e --- /dev/null +++ b/projects/packages/masterbar/src/admin-color-schemes/colors/sakura/sidebar-notice.scss @@ -0,0 +1,5 @@ +/* + * Styles for sidebar notices are maintained in a separate file for each color scheme so we can load them independently when needed. +*/ +@import "variables"; +@import "../upsell-banner"; diff --git a/projects/packages/masterbar/src/admin-color-schemes/colors/sunrise/_variables.scss b/projects/packages/masterbar/src/admin-color-schemes/colors/sunrise/_variables.scss new file mode 100644 index 0000000000000..113980af0ef49 --- /dev/null +++ b/projects/packages/masterbar/src/admin-color-schemes/colors/sunrise/_variables.scss @@ -0,0 +1,25 @@ +// make use of color studio variables for consistency across products +@import 'node_modules/@automattic/color-studio/dist/color-variables.scss'; +@import 'node_modules/@automattic/color-studio/dist/color-variables-rgb.scss'; + +$masterbar-background: #cf4944; +$menu-submenu-background: #be3631; +$text-color: #fff; +$highlight-color: #dd823b; +$base-color-rgb: rgb(207, 73, 68); +$nav-unification-sidebar-text-alternative: #f6f7f7; +$menu-highlight-text: #fff; + +// Calypso color variables used in e.g. inline help +$color-primary: #dd823b; // Calypso --color-primary +$color-primary-dark: $studio-orange-70; // Calypso --color-primary-dark +$color-primary-light: $studio-orange-30; // Calypso --color-primary-light +$color-surface: $studio-white; // Calypso --color-surface +$color-neutral-100-rgb: $studio-gray-100-rgb; // Calypso --color-neutral-100-rgb + +// Sidebar Nudges +$menu-nudge-background: $studio-white; +$menu-nudge-text-color: $studio-black; +$menu-nudge-cta-background: $highlight-color; +$menu-nudge-cta-color: $studio-white; +$menu-nudge-cta-background-hover: $studio-orange-60; diff --git a/projects/packages/masterbar/src/admin-color-schemes/colors/sunrise/colors.scss b/projects/packages/masterbar/src/admin-color-schemes/colors/sunrise/colors.scss new file mode 100644 index 0000000000000..5090216474c9a --- /dev/null +++ b/projects/packages/masterbar/src/admin-color-schemes/colors/sunrise/colors.scss @@ -0,0 +1,5 @@ +@import "variables"; +@import "../_core-overrides"; +@import "../_gutenberg"; +@import "../_inline-help"; +@import "sidebar-notice"; diff --git a/projects/packages/masterbar/src/admin-color-schemes/colors/sunrise/sidebar-notice.scss b/projects/packages/masterbar/src/admin-color-schemes/colors/sunrise/sidebar-notice.scss new file mode 100644 index 0000000000000..3e1b6380cd81e --- /dev/null +++ b/projects/packages/masterbar/src/admin-color-schemes/colors/sunrise/sidebar-notice.scss @@ -0,0 +1,5 @@ +/* + * Styles for sidebar notices are maintained in a separate file for each color scheme so we can load them independently when needed. +*/ +@import "variables"; +@import "../upsell-banner"; diff --git a/projects/packages/masterbar/src/admin-color-schemes/colors/sunset/_variables.scss b/projects/packages/masterbar/src/admin-color-schemes/colors/sunset/_variables.scss new file mode 100644 index 0000000000000..03f7b325cfde6 --- /dev/null +++ b/projects/packages/masterbar/src/admin-color-schemes/colors/sunset/_variables.scss @@ -0,0 +1,50 @@ +// make use of color studio variables for consistency across products +@import 'node_modules/@automattic/color-studio/dist/color-variables.scss'; +@import 'node_modules/@automattic/color-studio/dist/color-variables-rgb.scss'; + +// core variables (core _variables.scss) +$base-color: $studio-red-80; // Calypso --color-masterbar-background +$icon-color: $studio-red-5; // Calypso --color-sidebar-gridicon-fill +$highlight-color: $studio-orange-50; // Calypso --color-accent +$notification-color: $studio-pink-20; // Calypso Classic Bright (!) --color-masterbar-unread-dot-background + +// global (core _variables.scss) +$body-background: $studio-gray-0; // Calypso --color-surface-backdrop + +// admin menu & admin-bar (core _variables.scss) +$menu-text: $studio-white; // Calypso --color-sidebar-text +$menu-background: $studio-red-70; // Calypso --color-sidebar-background +$menu-highlight-text: $studio-white; // Calypso --color-sidebar-menu-hover-text +$menu-highlight-icon: $menu-highlight-text; +$menu-highlight-background: $studio-red-80; // Calypso --color-sidebar-menu-hover-background +$menu-current-text: $studio-yellow-80; // Calypso --color-sidebar-menu-selected-text +$menu-current-icon: $menu-current-text; +$menu-current-background: $studio-yellow-20; // Calypso --color-sidebar-menu-selected-background +$menu-submenu-text: $studio-white; // Calypso --color-sidebar-submenu-text +$menu-submenu-background: $studio-red-60; // Calypso --color-sidebar-submenu-background +$menu-submenu-focus-text: $studio-yellow-20; // Calypso --color-sidebar-submenu-hover-text +$menu-submenu-current-text: $studio-white; // Calypso --color-sidebar-submenu-selected-text + +// masterbar overrides +$masterbar-background: $studio-red-80; // Calypso --color-masterbar-background +$masterbar-highlight-background: $studio-red-90; // Calypso --color-masterbar-item-hover-background +$masterbar-active-background: $studio-red-100; // Calypso --color-masterbar-item-active-background + +// nav unification overrides +$nav-unification-sidebar-border: $studio-red-80; // Calypso --color-sidebar-border +$nav-unification-sidebar-text-alternative: $studio-red-10; // Calypso --color-sidebar-text-alternative + +// Calypso color variables used in e.g. inline help +$color-primary: $studio-red-50; // Calypso --color-primary +$color-primary-dark: $studio-red-70; // Calypso --color-primary-dark +$color-primary-light: $studio-red-30; // Calypso --color-primary-light +$color-surface: $studio-white; // Calypso --color-surface +$color-neutral-100-rgb: $studio-gray-100-rgb; // Calypso --color-neutral-100-rgb + +// Sidebar Nudges +$menu-nudge-background: $studio-white; +$menu-nudge-text-color: $studio-black; +$menu-nudge-cta-background: $highlight-color; +$menu-nudge-cta-color: $studio-white; +$menu-nudge-cta-background-hover: $studio-orange-60; + diff --git a/projects/packages/masterbar/src/admin-color-schemes/colors/sunset/colors.scss b/projects/packages/masterbar/src/admin-color-schemes/colors/sunset/colors.scss new file mode 100644 index 0000000000000..07136f2553c45 --- /dev/null +++ b/projects/packages/masterbar/src/admin-color-schemes/colors/sunset/colors.scss @@ -0,0 +1,3 @@ +@import "variables"; +@import "../admin"; +@import "sidebar-notice"; diff --git a/projects/packages/masterbar/src/admin-color-schemes/colors/sunset/sidebar-notice.scss b/projects/packages/masterbar/src/admin-color-schemes/colors/sunset/sidebar-notice.scss new file mode 100644 index 0000000000000..3e1b6380cd81e --- /dev/null +++ b/projects/packages/masterbar/src/admin-color-schemes/colors/sunset/sidebar-notice.scss @@ -0,0 +1,5 @@ +/* + * Styles for sidebar notices are maintained in a separate file for each color scheme so we can load them independently when needed. +*/ +@import "variables"; +@import "../upsell-banner"; diff --git a/projects/packages/masterbar/src/admin-menu/admin-menu-nav-unification.css b/projects/packages/masterbar/src/admin-menu/admin-menu-nav-unification.css new file mode 100644 index 0000000000000..e78b3f1867407 --- /dev/null +++ b/projects/packages/masterbar/src/admin-menu/admin-menu-nav-unification.css @@ -0,0 +1,75 @@ +/* + * Styles for the nav-unification prototype (see pbAPfg-O2) + * TODO: depending on project outcome move or delete styles + */ +#wpadminbar #wp-admin-bar-notes #wpnt-notes-unread-count.wpn-unread { + top: 50%; + left: 50%; + transform: translate( -10px, -13px ); + +} + +#wpadminbar .quicklinks li#wp-admin-bar-my-account.with-avatar { + transform: translateX( 1px ); +} + +#wpadminbar #wp-admin-bar-notes.active .noticon-bell:before { + background-image: url("data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyNCAyNCIgd2lkdGg9IjI0IiBoZWlnaHQ9IjI0Ij48cmVjdCB4PSIwIiBmaWxsPSJub25lIiB3aWR0aD0iMjQiIGhlaWdodD0iMjQiLz48Zz48cGF0aCBmaWxsPSIjZmZmZmZmIiBkPSJNNi4xNCAxNC45N2wyLjgyOCAyLjgyN2MtLjM2Mi4zNjItLjg2Mi41ODYtMS40MTQuNTg2LTEuMTA1IDAtMi0uODk1LTItMiAwLS41NTIuMjI0LTEuMDUyLjU4Ni0xLjQxNHptOC44NjcgNS4zMjRMMTQuMyAyMSAzIDkuN2wuNzA2LS43MDcgMS4xMDIuMTU3Yy43NTQuMTA4IDEuNjktLjEyMiAyLjA3Ny0uNTFsMy44ODUtMy44ODRjMi4zNC0yLjM0IDYuMTM1LTIuMzQgOC40NzUgMHMyLjM0IDYuMTM1IDAgOC40NzVsLTMuODg1IDMuODg2Yy0uMzg4LjM4OC0uNjE4IDEuMzIzLS41MSAyLjA3N2wuMTU3IDEuMXoiLz48L2c+PC9zdmc+") !important; +} + +#wpadminbar > #wp-toolbar .wpnt-show span.noticon, +#wpadminbar #wp-admin-bar-notes.wpnt-show .noticon { + color: #ffffff; +} + +#wpadminbar .quicklinks ul#wp-admin-bar-root-default { + padding-left: 0 !important; +} + +#wpadminbar #wp-admin-bar-menu-toggle { + display: none; +} + +@media screen and (max-width: 782px) { + #wpadminbar #wp-toolbar > ul > li { + display: block; + } + + #wpadminbar .ab-top-menu > li > .ab-item { + box-sizing: border-box; + line-height: 32px; + } + + #wpadminbar #wp-admin-bar-ab-new-post > .ab-item { + box-sizing: inherit !important; + } + + #wpadminbar #wp-admin-bar-my-account > .ab-item { + padding: 7px 15px; + width: auto; + } + + #wpadminbar #wp-toolbar.quicklinks li#wp-admin-bar-my-account.with-avatar > a img { + display: block; + right: auto; + left: auto; + position: static; + margin-top: 3px; + top: 13px; + } + + /* Hide debug bar. */ + #wpadminbar #wp-toolbar.quicklinks li#wp-admin-bar-debug-bar { + display: none; + } +} + +@media screen and (max-width: 480px) { + #wpadminbar #wp-toolbar.quicklinks li#wp-admin-bar-my-account.with-avatar > a { + width: auto; + } + + #wpadminbar #wp-toolbar.quicklinks li#wp-admin-bar-my-account.with-avatar > a img { + margin-top: 12px; + } +} diff --git a/projects/packages/masterbar/src/admin-menu/admin-menu-nav-unification.js b/projects/packages/masterbar/src/admin-menu/admin-menu-nav-unification.js new file mode 100644 index 0000000000000..bd18380307412 --- /dev/null +++ b/projects/packages/masterbar/src/admin-menu/admin-menu-nav-unification.js @@ -0,0 +1 @@ +import './admin-menu-nav-unification.css'; diff --git a/projects/packages/masterbar/src/admin-menu/admin-menu.css b/projects/packages/masterbar/src/admin-menu/admin-menu.css new file mode 100644 index 0000000000000..14e4f9467c5e0 --- /dev/null +++ b/projects/packages/masterbar/src/admin-menu/admin-menu.css @@ -0,0 +1,513 @@ +#adminmenu { + margin: 0; +} + +/** + * Menu width + */ +#wpcontent, +#wpfooter { + margin-left: 272px; +} + +#adminmenuback, +#adminmenuwrap, +#adminmenu, +#adminmenu .wp-submenu { + width: 272px; +} + +#adminmenu .wp-submenu { + left: 272px; +} + +#adminmenu .wp-not-current-submenu .wp-submenu, +.folded #adminmenu .wp-has-current-submenu .wp-submenu { + min-width: 272px; +} + +/** + * Fixes Gutenberg in not fullscreen mode. + */ + @media (min-width: 783px) { + .interface-interface-skeleton, + .edit-post-layout .components-editor-notices__snackbar { + left: 272px; + } +} + +@media (min-width: 961px) { + body:not(.folded).auto-fold .interface-interface-skeleton, + .auto-fold .edit-post-layout .components-editor-notices__snackbar, + .components-snackbar-list.edit-widgets-notices__snackbar, + .jp-dialogue-modern-full__container { + left: 272px; + } + + .global-notices { + max-width: calc( 100% - 48px - 272px ); + } +} + +/** + * Jetpack logo + */ +#adminmenu [class*="activity-log"] .wp-menu-image img { + padding-top: 7px; +} + +/** + * Site Card + */ +#adminmenu .toplevel_page_site-card .wp-menu-name { + margin-left: 40px; /* icon width (32) + padding (8). */ + padding: 0; +} + +#adminmenu li.toplevel_page_site-card a { + padding: 10px 0 10px 8px; +} + +/** + * Site Notices + */ +#adminmenu a.toplevel_page_site-notices:hover, +#adminmenu a.toplevel_page_site-notices:focus, +#adminmenu li.toplevel_page_site-notices:hover, +#adminmenu li.toplevel_page_site-notices:focus { + background-color: inherit !important; + color: inherit !important; +} + +#adminmenu li.toplevel_page_site-notices .wp-menu-image { + display: none; +} + +#adminmenu .toplevel_page_site-notices .wp-menu-image { + border-radius: 2px; + background-color: #fff; +} + +#adminmenu .toplevel_page_site-notices .wp-menu-image:before { + content: '\f534'; + font-family: 'dashicons'; + font-size: 20px; + line-height: 20px; + background-color: #a7aaad; + color: white; + border-radius: 50%; + margin: 5px; + padding: 0; +} + +#adminmenu .toplevel_page_site-notices:hover .wp-menu-image:before { + color: #fff; +} + +#adminmenu .toplevel_page_site-notices .upsell_banner { + display: flex; + flex-grow: 1; + flex-wrap: nowrap; + align-items: center; + justify-content: space-between; + position: relative; + width: 100%; + padding: 7px 12px; + left: -28px; + border-radius: 2px; + font-size: 12px; + line-height: 1.4; + hyphens: none; +} + +#adminmenu .toplevel_page_site-notices .upsell_banner .banner__info { + margin-right: 12px; +} + +#adminmenu .toplevel_page_site-notices .upsell_banner .button { + font-size: 12px; + line-height: 12px; + padding: 0 7px; + border: 0; + min-height: 26px; +} + +#adminmenu .toplevel_page_site-notices .upsell_banner svg.dismissible-card__close-icon { + height: 24px; + width: 24px; + margin-left: 10px; +} + +@media screen and (min-width: 782px) { + .folded #adminmenu .toplevel_page_site-notices .wp-menu-image { + display: block; + width: 30px; + } + + .folded #adminmenu .toplevel_page_site-notices { + height: 50px; + display: flex; + align-items: center; + justify-content: center; + } +} + +@media screen and (min-width: 782px) and (max-width: 960px){ + .auto-fold #adminmenu .toplevel_page_site-notices .wp-menu-image { + display: block; + width: 30px; + } + + .auto-fold #adminmenu .toplevel_page_site-notices { + height: 50px; + display: flex; + align-items: center; + justify-content: center; + } +} + +/* Prevent box-shadow at the top of the sidebar */ +#adminmenu .site-switcher:hover, +#adminmenu .toplevel_page_site-card:hover, +#adminmenu .toplevel_page_site-notices:hover { + box-shadow: none; +} + +/** + * Site icon inline-styles for height and width are defined in set_site_icon_inline_styles + */ +#adminmenu .toplevel_page_site-card .wp-menu-image { + background-image: none; + background-position: center; + background-repeat: no-repeat; + background-size: 18px 18px; + transform: translateZ(0); + transition-property: background-image,background-color; + transition-duration: .2s; +} + +#adminmenu a.toplevel_page_site-card:hover, +#adminmenu li.toplevel_page_site-card:hover { + background-color: inherit; +} + +#adminmenu .toplevel_page_site-card img { + opacity: initial; +} + +#adminmenu .toplevel_page_site-card.has-site-icon img { + padding: 0; +} + +#adminmenu .toplevel_page_site-card:hover div.wp-menu-image, +#adminmenu .toplevel_page_site-card a:focus div.wp-menu-image { + background-image: url("data:image/svg+xml,%3Csvg class='gridicon gridicons-house' height='24' width='24' xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cg%3E%3Cpath fill='%23fff' d='M22 9L12 1 2 9v2h2v10h5v-4c0-1.657 1.343-3 3-3s3 1.343 3 3v4h5V11h2V9z'/%3E%3C/g%3E%3C/svg%3E%0A"); +} + +#adminmenu .toplevel_page_site-card:not(.has-site-icon) .wp-menu-image { + background-color: #c3c4c7; + height: 32px; + width: 32px; +} + +#adminmenu .toplevel_page_site-card:not(.has-site-icon) .wp-menu-image img { + width: 18px; + height: 18px; + padding: 7px; +} + +#adminmenu .toplevel_page_site-card:hover div.wp-menu-image img, +#adminmenu .toplevel_page_site-card a:focus div.wp-menu-image img { + display: none; +} + +.site__info .site__title { + display: block; + font-size: 14px; + font-weight: 400; + line-height: 1.3; +} + +.site__info .site__domain { + display: block; + max-width: 95%; + font-size: 12px; + line-height: 1.4; + margin-top: 2px; +} + +.site__info .site__title, +.site__info .site__domain { + overflow: hidden; + white-space: nowrap; +} +.site__info .site__title::after, +.site__info .site__domain::after { + content: ""; + display: block; + position: absolute; + -webkit-touch-callout: none; + -webkit-user-select: none; + user-select: none; + pointer-events: none; + top: 0; + bottom: 0; + right: 0; + left: auto; + width: 20%; + height: auto; +} + +.site__info > .site__badge { + font-size: 12px; + border-radius: 12px; + clear: both; + display: inline-block; + margin-top: 6px; + margin-right: 3px; + padding: 1px 10px; +} + +.site__info > .site__badge.site__badge-staging { + background-color: #f0c930; + color: #4f3500; +} + +/** + * Inline text in a menu title + */ +.inline-text { + display: block !important; + position: absolute; + right: 20px; + top: 50%; + transform: translateY( -50% ); + opacity: 0.8; +} + +/** + * Stats + */ +[class*="toplevel_page_https://wordpress.com/stats/day"] .sidebar-unified__sparkline { + float: right; + margin-right: 8px; +} + +/** + * Folded State + */ +.folded #adminmenu a.menu-top { + height: 31px; +} + +.folded #adminmenu li.toplevel_page_site-card a { + padding-left: 0; +} + +/* Auto-folding of the admin menu */ +@media only screen and (max-width: 960px) { + #adminmenu, + #adminmenuwrap, + #adminmenuback { + width: 272px; + } + + .auto-fold #adminmenu a[class*="toplevel_page_http"].wp-first-item { + height: auto; + } + + .wp-responsive-open #adminmenu a.menu-top { + height: auto; + } + + .auto-fold #adminmenu div.wp-menu-image { + width: 36px; + } +} + +@media screen and (min-width: 782px) and (max-width: 960px) { + .auto-fold #adminmenu a.menu-top { + height: 34px; + } + + .auto-fold #adminmenu li.toplevel_page_site-card a { + height: 36px; + padding-left: 1px; + } +} + +@media screen and (max-width: 782px) { + #adminmenu li.menu-top .wp-submenu>li>a, + .auto-fold #adminmenu li.menu-top .wp-submenu>li>a { + padding-left: 42px; + } + + .wp-responsive-open #wpbody { + right: inherit; + } + + .wp-responsive-open #wpcontent { + margin-left: 272px; + } + + .auto-fold #adminmenu, .auto-fold #adminmenuback, .auto-fold #adminmenuwrap { + width: 272px; + } + + .auto-fold #adminmenu a.site-switcher, + #adminmenu a.site-switcher { + font-size: 14px; + } +} + +@media only screen and (max-width: 660px) { + #adminmenuback, + #adminmenuwrap, + #adminmenu, + #adminmenu .wp-submenu, + .auto-fold #adminmenu, + .auto-fold #adminmenuback, + .auto-fold #adminmenuwrap { + width: 100%; + z-index: 171; + } + + .wp-responsive-open #wpcontent { + margin-left: 0; + } + + ul#adminmenu a.wp-has-current-submenu:after, + ul#adminmenu>li.current>a.current:after, + ul#adminmenu li:hover a.wp-has-current-submenu:after, + .auto-fold ul#adminmenu li:hover a.wp-has-current-submenu:after { + display: none; + } + + .auto-fold #adminmenu li.toplevel_page_site-card a { + padding: 18px 0 18px 12px; + } +} + +/* Dashboard Switcher */ +#view-link-wrap { + float: left; + margin: 0 0 0 6px; +} + +.screen-options-tab__wrapper { + position:relative +} + +.screen-options-tab__dropdown { + background-color: #fff; + border: 1px solid var(--color-neutral-5); + border-radius: 4px; + box-shadow: 0 4px 10px #0000001a; + padding: 3px; + position: absolute; + right: 20px; + top: 37px; + width:215px; + z-index: 9999; +} + +@media screen and (max-width: 782px) { + .screen-options-tab__dropdown { + right: 10px; + top: 47px; + } +} + +@media screen and (max-width: 600px) { + .screen-options-tab__dropdown { + top: 93px; + } +} + +.screen-switcher:not(:hover) .screen-switcher__button:nth-child(2) > strong { + color:var(--wp-admin-theme-color) +} + +.screen-switcher__button, a.screen-switcher__button { + background: transparent; + border: 1px solid #0000; + border-radius: 4px; + color: var(--color-text); + cursor: pointer; + display: inline-block; + font-size: .75rem; + line-height: normal; + text-decoration: none; + padding: 8px; + text-align:left +} + +.screen-switcher__button:nth-child(2), a.screen-switcher__button:nth-child(2) { + border-color: var(--wp-admin-theme-color); + margin-bottom:4px +} + +.screen-switcher__button:last-child, a.screen-switcher__button:last-child { + margin-bottom:0 +} + +.screen-switcher__button strong, a.screen-switcher__button strong { + display: block; + font-size: 13px; + margin-bottom:4px +} + +.screen-switcher__button:focus > strong, .screen-switcher__button:hover > strong, a.screen-switcher__button:focus > strong, a.screen-switcher__button:hover > strong { + color:var(--wp-admin-theme-color) +} + +/** Add new site button **/ +#adminmenu { + display: flex; + flex-direction: column; +} +#adminmenu .menu-top[class*="/start?ref=calypso-sidebar"] { + order: 99999; +} + +body:not(.folded) #adminmenu .menu-top[class*="/start?ref=calypso-sidebar"] { + padding: 8px; + width: auto; +} + +body:not(.folded) #adminmenu .menu-top[class*="/start?ref=calypso-sidebar"] a { + background: transparent; + border-color: var(--transparent-button-text-color, currentcolor); + display: flex; + justify-content: center; + margin-top: auto; + background: transparent; + border-style: solid; + border-width: 1px; + cursor: pointer; + margin: 0; + outline: 0; + overflow: hidden; + text-align: center; + text-overflow: ellipsis; + text-decoration: none; + vertical-align: top; + box-sizing: border-box; + font-size: 14px; + line-height: 22px; + border-radius: 2px; + padding: 8px 14px; + appearance: none; +} + +body:not(.folded) #adminmenu .menu-top[class*="/start?ref=calypso-sidebar"] a:hover { + box-shadow: none; +} + +body:not(.folded) #adminmenu .menu-top[class*="/start?ref=calypso-sidebar"] a .wp-menu-name { + padding: 0; +} + +body:not(.folded) #adminmenu .menu-top[class*="/start?ref=calypso-sidebar"] a .wp-menu-image { + display: none; +} diff --git a/projects/packages/masterbar/src/admin-menu/admin-menu.js b/projects/packages/masterbar/src/admin-menu/admin-menu.js new file mode 100644 index 0000000000000..013aa2a3c1b37 --- /dev/null +++ b/projects/packages/masterbar/src/admin-menu/admin-menu.js @@ -0,0 +1,216 @@ +/* eslint-disable jsdoc/require-description,jsdoc/require-param-description,jsdoc/require-param-type,jsdoc/require-returns */ +/* global ajaxurl, jetpackAdminMenu */ +import './admin-menu.css'; + +( function ( $ ) { + /** + * + */ + function init() { + const adminbar = document.querySelector( '#wpadminbar' ); + const wpwrap = document.querySelector( '#wpwrap' ); + const adminMenu = document.querySelector( '#adminmenu' ); + const dismissClass = 'dismissible-card__close-icon'; + + if ( ! adminbar ) { + return; + } + + /** + * + * @param value + */ + function setAriaExpanded( value ) { + const anchors = adminbar.querySelectorAll( '#wp-admin-bar-blog a' ); + for ( let i = 0; i < anchors.length; i++ ) { + anchors[ i ].setAttribute( 'aria-expanded', value ); + } + } + + setFocusOnActiveMenuItem(); + setAriaExpanded( 'false' ); + + const adminbarBlog = adminbar.querySelector( '#wp-admin-bar-blog.my-sites > a' ); + + // Toggle sidebar when toggle is clicked. + if ( adminbarBlog && ! document.body.classList.contains( 'wpcom-admin-interface' ) ) { + // We need to remove an event listener and attribute from the my sites button to prevent default behavior of the wp-responsive-overlay. + $( '#wp-admin-bar-blog.my-sites > a' ).off( 'click.wp-responsive' ); + adminbarBlog.removeAttribute( 'aria-haspopup' ); + // Toggle the sidebar when the 'My Sites' button is clicked in a mobile view. + adminbarBlog.addEventListener( 'click', toggleSidebar ); + // Detect a click outside the sidebar and close it if its open. + document.addEventListener( 'click', closeSidebarWhenClickedOutside ); + } + + /** + * + * @param event + */ + function closeSidebarWhenClickedOutside( event ) { + const isClickOnToggle = !! event.target.closest( '#wp-admin-bar-blog > a' ); + const isClickInsideMenu = document.getElementById( 'adminmenu' ).contains( event.target ); + const sidebarIsOpen = wpwrap.classList.contains( 'wp-responsive-open' ); + const shouldCloseSidebar = sidebarIsOpen && ! isClickOnToggle && ! isClickInsideMenu; + if ( shouldCloseSidebar ) { + toggleSidebar( event ); + } + } + + /** + * + * @param event + */ + function toggleSidebar( event ) { + // Prevent the menu toggle from being triggered when the screen is not in mobile view. + // This allows the toggle to function as a link to the site's dashboard the same way it works in Calypso. + if ( $( window ).width() > 782 ) { + event.stopImmediatePropagation(); // Prevent propagation to conflicting event handlers. + return true; + } + + event.preventDefault(); + + // Remove event handlers from the original toggle as its hidden and conflicts with the new toggle. + $( '#wp-admin-bar-menu-toggle' ).off( 'click.wp-responsive' ); + + // Close any open toolbar submenus. + const hovers = adminbar.querySelectorAll( '.hover' ); + + const hoverLength = hovers.length; + for ( let i = 0; i < hoverLength; i++ ) { + hovers[ i ].classList.remove( 'hover' ); + } + wpwrap.classList.toggle( 'wp-responsive-open' ); + if ( wpwrap.classList.contains( 'wp-responsive-open' ) ) { + setAriaExpanded( 'true' ); + const first = document.querySelector( '#adminmenu a' ); + if ( first ) { + first.focus(); + } + } else { + setAriaExpanded( 'false' ); + } + } + + if ( adminMenu ) { + const collapseButton = adminMenu.querySelector( '#collapse-button' ); + // Nav-Unification feature: + // Saves the sidebar state in server when "Collapse menu" is clicked. + // This is needed so that we update WPCOM for this preference in real-time. + if ( collapseButton ) { + collapseButton.addEventListener( 'click', function ( event ) { + // Let's the core event listener be triggered first. + setTimeout( function () { + saveSidebarIsExpanded( event.target.parentNode.getAttribute( 'aria-expanded' ) ); + }, 50 ); + } ); + } + + adminMenu.addEventListener( 'click', function ( event ) { + if ( + event.target.classList.contains( dismissClass ) || + event.target.closest( '.' + dismissClass ) + ) { + event.preventDefault(); + + const siteNotice = document.getElementById( 'toplevel_page_site-notices' ); + if ( siteNotice ) { + siteNotice.style.display = 'none'; + } + + const jitmDismissButton = event.target; + + makeAjaxRequest( + 'POST', + ajaxurl, + 'application/x-www-form-urlencoded; charset=UTF-8', + 'id=' + + encodeURIComponent( jitmDismissButton.dataset.feature_id ) + + '&feature_class=' + + encodeURIComponent( jitmDismissButton.dataset.feature_class ) + + '&action=jitm_dismiss' + + '&_ajax_nonce=' + + jetpackAdminMenu.jitmDismissNonce + ); + } + } ); + + makeAjaxRequest( + 'GET', + ajaxurl + '?action=upsell_nudge_jitm&_ajax_nonce=' + jetpackAdminMenu.upsellNudgeJitm, + undefined, + null, + function ( xhr ) { + try { + if ( xhr.readyState === XMLHttpRequest.DONE ) { + if ( xhr.status === 200 && xhr.responseText ) { + adminMenu + .querySelector( '#toplevel_page_site_card' ) + .insertAdjacentHTML( 'afterend', xhr.responseText ); + } + } + } catch ( error ) { + // On failure, we just won't display an upsell nudge + } + } + ); + } + } + + /** + * + * @param method + * @param url + * @param contentType + * @param body + * @param callback + */ + function makeAjaxRequest( method, url, contentType, body = null, callback = null ) { + const xhr = new XMLHttpRequest(); + xhr.open( method, url, true ); + xhr.setRequestHeader( 'X-Requested-With', 'XMLHttpRequest' ); + if ( contentType ) { + xhr.setRequestHeader( 'Content-Type', contentType ); + } + xhr.withCredentials = true; + if ( callback ) { + xhr.onreadystatechange = function () { + callback( xhr ); + }; + } + xhr.send( body ); + } + + /** + * + * @param expanded + */ + function saveSidebarIsExpanded( expanded ) { + makeAjaxRequest( + 'POST', + ajaxurl, + 'application/x-www-form-urlencoded; charset=UTF-8', + 'action=sidebar_state&expanded=' + expanded + ); + } + + /** + * + */ + function setFocusOnActiveMenuItem() { + const currentMenuItem = document.querySelector( '.wp-submenu .current > a' ); + + if ( ! currentMenuItem ) { + return; + } + + currentMenuItem.focus( { preventScroll: true } ); + } + + if ( document.readyState === 'loading' ) { + document.addEventListener( 'DOMContentLoaded', init ); + } else { + init(); + } +} )( jQuery ); diff --git a/projects/packages/masterbar/src/admin-menu/class-admin-menu.php b/projects/packages/masterbar/src/admin-menu/class-admin-menu.php new file mode 100644 index 0000000000000..c72faf0436ec6 --- /dev/null +++ b/projects/packages/masterbar/src/admin-menu/class-admin-menu.php @@ -0,0 +1,527 @@ +add_stats_menu(); + $this->add_upgrades_menu(); + $this->add_posts_menu(); + $this->add_media_menu(); + $this->add_page_menu(); + $this->add_testimonials_menu(); + $this->add_portfolio_menu(); + $this->add_comments_menu(); + $this->add_appearance_menu(); + $this->add_plugins_menu(); + $this->add_users_menu(); + $this->add_tools_menu(); + $this->add_options_menu(); + $this->add_jetpack_menu(); + + // Remove Links Manager menu since its usage is discouraged. https://github.com/Automattic/wp-calypso/issues/51188. + // @see https://core.trac.wordpress.org/ticket/21307#comment:73. + if ( $this->should_disable_links_manager() ) { + remove_menu_page( 'link-manager.php' ); + } + + ksort( $GLOBALS['menu'] ); + } + + /** + * Get the preferred view for the given screen. + * + * @param string $screen Screen identifier. + * @param bool $fallback_global_preference (Optional) Whether the global preference for all screens should be used + * as fallback if there is no specific preference for the given screen. + * Default: true. + * @return string + */ + public function get_preferred_view( $screen, $fallback_global_preference = true ) { + $force_default_view = in_array( $screen, array( 'users.php', 'options-general.php' ), true ); + $use_wp_admin = $this->use_wp_admin_interface(); + + // When no preferred view has been set for "Users > All Users" or "Settings > General", keep the previous + // behavior that forced the default view regardless of the global preference. + // This behavior is overriden by the wpcom_admin_interface option when it is set to wp-admin. + if ( ! $use_wp_admin && $fallback_global_preference && $force_default_view ) { + $preferred_view = parent::get_preferred_view( $screen, false ); + if ( self::UNKNOWN_VIEW === $preferred_view ) { + return self::DEFAULT_VIEW; + } + return $preferred_view; + } + + return parent::get_preferred_view( $screen, $fallback_global_preference ); + } + + /** + * Check if Links Manager is being used. + */ + public function should_disable_links_manager() { + // The max ID number of the auto-generated links. + // See /wp-content/mu-plugins/wpcom-wp-install-defaults.php in WP.com. + $max_default_id = 10; + + // We are only checking the latest entry link_id so are limiting the query to 1. + $link_manager_links = get_bookmarks( + array( + 'orderby' => 'link_id', + 'order' => 'DESC', + 'limit' => 1, + 'hide_invisible' => 0, + ) + ); + + // Ordered links by ID descending, check if the first ID is more than $max_default_id. + if ( is_countable( $link_manager_links ) && count( $link_manager_links ) > 0 && $link_manager_links[0]->link_id > $max_default_id ) { + return false; + } + + return true; + } + + /** + * Adds My Home menu. + */ + public function add_my_home_menu() { + + if ( self::DEFAULT_VIEW !== $this->get_preferred_view( 'index.php' ) ) { + // @phan-suppress-next-line PhanTypeMismatchArgumentProbablyReal -- Core should ideally document null for no-callback arg. TODO add link with Trac issue. + add_menu_page( __( 'My Home', 'jetpack-masterbar' ), __( 'My Home', 'jetpack-masterbar' ), 'read', 'https://wordpress.com/home/' . $this->domain, null, 'dashicons-admin-home', 1.5 ); + return; + } + + $this->update_menu( 'index.php', 'https://wordpress.com/home/' . $this->domain, __( 'My Home', 'jetpack-masterbar' ), 'read', 'dashicons-admin-home' ); + } + + /** + * Adds My Mailboxes menu. + */ + public function add_my_mailboxes_menu() { + // @phan-suppress-next-line PhanTypeMismatchArgumentProbablyReal -- Core should ideally document null for no-callback arg. TODO add link with Trac issue. + add_menu_page( __( 'My Mailboxes', 'jetpack-masterbar' ), __( 'My Mailboxes', 'jetpack-masterbar' ), 'manage_options', 'https://wordpress.com/mailboxes/' . $this->domain, null, 'dashicons-email', 4.64424 ); + } + + /** + * Adds Stats menu. + */ + public function add_stats_menu() { + // @phan-suppress-next-line PhanTypeMismatchArgumentProbablyReal -- Core should ideally document null for no-callback arg. TODO add link with Trac issue. + add_menu_page( __( 'Stats', 'jetpack-masterbar' ), __( 'Stats', 'jetpack-masterbar' ), 'view_stats', 'https://wordpress.com/stats/day/' . $this->domain, null, 'dashicons-chart-bar', 3 ); + } + + /** + * Adds Upgrades menu. + * + * @param string $plan The current WPCOM plan of the blog. + */ + public function add_upgrades_menu( $plan = null ) { + global $menu; + + $menu_exists = false; + foreach ( $menu as $item ) { + if ( 'paid-upgrades.php' === $item[2] ) { + $menu_exists = true; + break; + } + } + + if ( ! $menu_exists ) { + if ( $plan ) { + // Add display:none as a default for cases when CSS is not loaded. + $site_upgrades = '%1$s'; + $site_upgrades = sprintf( + $site_upgrades, + __( 'Upgrades', 'jetpack-masterbar' ), + // phpcs:ignore WordPress.WP.I18n.NonSingularStringLiteralText + __( $plan, 'jetpack-masterbar' ) + ); + } else { + $site_upgrades = __( 'Upgrades', 'jetpack-masterbar' ); + } + // @phan-suppress-next-line PhanTypeMismatchArgumentProbablyReal -- Core should ideally document null for no-callback arg. TODO add link with Trac issue. + add_menu_page( __( 'Upgrades', 'jetpack-masterbar' ), $site_upgrades, 'manage_options', 'paid-upgrades.php', null, 'dashicons-cart', 4 ); + } + // @phan-suppress-next-line PhanTypeMismatchArgumentProbablyReal -- Core should ideally document null for no-callback arg. TODO add link with Trac issue. + add_submenu_page( 'paid-upgrades.php', __( 'Plans', 'jetpack-masterbar' ), __( 'Plans', 'jetpack-masterbar' ), 'manage_options', 'https://wordpress.com/plans/' . $this->domain, null, 1 ); + // @phan-suppress-next-line PhanTypeMismatchArgumentProbablyReal -- Core should ideally document null for no-callback arg. TODO add link with Trac issue. + add_submenu_page( 'paid-upgrades.php', __( 'Purchases', 'jetpack-masterbar' ), __( 'Purchases', 'jetpack-masterbar' ), 'manage_options', 'https://wordpress.com/purchases/subscriptions/' . $this->domain, null, 2 ); + + if ( ! $menu_exists ) { + // Remove the submenu auto-created by Core. + $this->hide_submenu_page( 'paid-upgrades.php', 'paid-upgrades.php' ); + } + } + + /** + * Adds Posts menu. + */ + public function add_posts_menu() { + $submenus_to_update = array(); + + if ( self::DEFAULT_VIEW === $this->get_preferred_view( 'edit.php' ) ) { + $submenus_to_update['edit.php'] = 'https://wordpress.com/posts/' . $this->domain; + $submenus_to_update['post-new.php'] = 'https://wordpress.com/post/' . $this->domain; + $this->update_submenus( 'edit.php', $submenus_to_update ); + } + + if ( self::DEFAULT_VIEW === $this->get_preferred_view( 'edit-tags.php?taxonomy=category' ) ) { + $this->update_submenus( 'edit.php', array( 'edit-tags.php?taxonomy=category' => 'https://wordpress.com/settings/taxonomies/category/' . $this->domain ) ); + } + + if ( self::DEFAULT_VIEW === $this->get_preferred_view( 'edit-tags.php?taxonomy=post_tag' ) ) { + $this->update_submenus( 'edit.php', array( 'edit-tags.php?taxonomy=post_tag' => 'https://wordpress.com/settings/taxonomies/post_tag/' . $this->domain ) ); + } + } + + /** + * Adds Media menu. + */ + public function add_media_menu() { + if ( self::CLASSIC_VIEW === $this->get_preferred_view( 'upload.php' ) ) { + return; + } + + $this->hide_submenu_page( 'upload.php', 'media-new.php' ); + + $this->update_menu( 'upload.php', 'https://wordpress.com/media/' . $this->domain ); + } + + /** + * Adds Page menu. + */ + public function add_page_menu() { + if ( self::CLASSIC_VIEW === $this->get_preferred_view( 'edit.php?post_type=page' ) ) { + return; + } + + $submenus_to_update = array( + 'edit.php?post_type=page' => 'https://wordpress.com/pages/' . $this->domain, + 'post-new.php?post_type=page' => 'https://wordpress.com/page/' . $this->domain, + ); + $this->update_submenus( 'edit.php?post_type=page', $submenus_to_update ); + } + + /** + * Adds Testimonials menu. + */ + public function add_testimonials_menu() { + $this->add_custom_post_type_menu( 'jetpack-testimonial' ); + } + + /** + * Adds Portfolio menu. + */ + public function add_portfolio_menu() { + $this->add_custom_post_type_menu( 'jetpack-portfolio' ); + } + + /** + * Adds a custom post type menu. + * + * @param string $post_type Custom post type. + */ + public function add_custom_post_type_menu( $post_type ) { + if ( self::CLASSIC_VIEW === $this->get_preferred_view( 'edit.php?post_type=' . $post_type ) ) { + return; + } + + $submenus_to_update = array( + 'edit.php?post_type=' . $post_type => 'https://wordpress.com/types/' . $post_type . '/' . $this->domain, + 'post-new.php?post_type=' . $post_type => 'https://wordpress.com/edit/' . $post_type . '/' . $this->domain, + ); + $this->update_submenus( 'edit.php?post_type=' . $post_type, $submenus_to_update ); + } + + /** + * Adds Comments menu. + */ + public function add_comments_menu() { + if ( self::CLASSIC_VIEW === $this->get_preferred_view( 'edit-comments.php' ) ) { + return; + } + + $this->update_menu( 'edit-comments.php', 'https://wordpress.com/comments/all/' . $this->domain ); + } + + /** + * Adds Appearance menu. + * + * @return string The Customizer URL. + */ + public function add_appearance_menu() { + $request_uri = isset( $_SERVER['REQUEST_URI'] ) ? esc_url_raw( wp_unslash( $_SERVER['REQUEST_URI'] ) ) : ''; + $default_customize_slug = add_query_arg( 'return', rawurlencode( remove_query_arg( wp_removable_query_args(), $request_uri ) ), 'customize.php' ); + $default_customize_header_slug_1 = add_query_arg( array( 'autofocus' => array( 'control' => 'header_image' ) ), $default_customize_slug ); + // TODO: Remove WPCom_Theme_Customizer::modify_header_menu_links() and WPcom_Custom_Header::modify_admin_menu_links(). + $default_customize_header_slug_2 = admin_url( 'themes.php?page=custom-header' ); + $default_customize_background_slug_1 = add_query_arg( array( 'autofocus' => array( 'control' => 'background_image' ) ), $default_customize_slug ); + // TODO: Remove Colors_Manager::modify_header_menu_links() and Colors_Manager_Common::modify_header_menu_links(). + $default_customize_background_slug_2 = add_query_arg( array( 'autofocus' => array( 'section' => 'colors_manager_tool' ) ), admin_url( 'customize.php' ) ); + + if ( $this->is_api_request ) { + // In case this is an api request we will have to add the 'return' querystring via JS. + $customize_url = 'customize.php'; + } else { + $customize_url = $default_customize_slug; + } + + $submenus_to_update = array( + $default_customize_slug => $customize_url, + $default_customize_header_slug_1 => add_query_arg( array( 'autofocus' => array( 'control' => 'header_image' ) ), $customize_url ), + $default_customize_header_slug_2 => add_query_arg( array( 'autofocus' => array( 'control' => 'header_image' ) ), $customize_url ), + $default_customize_background_slug_1 => add_query_arg( array( 'autofocus' => array( 'section' => 'colors_manager_tool' ) ), $customize_url ), + $default_customize_background_slug_2 => add_query_arg( array( 'autofocus' => array( 'section' => 'colors_manager_tool' ) ), $customize_url ), + ); + + if ( self::DEFAULT_VIEW === $this->get_preferred_view( 'themes.php' ) ) { + $submenus_to_update['themes.php'] = 'https://wordpress.com/themes/' . $this->domain; + } + + $this->update_submenus( 'themes.php', $submenus_to_update ); + + $this->hide_submenu_page( 'themes.php', 'custom-header' ); + $this->hide_submenu_page( 'themes.php', 'custom-background' ); + + return $customize_url; + } + + /** + * Adds Plugins menu. + */ + public function add_plugins_menu() { + if ( self::CLASSIC_VIEW === $this->get_preferred_view( 'plugins.php' ) ) { + return; + } + $this->hide_submenu_page( 'plugins.php', 'plugin-install.php' ); + $this->hide_submenu_page( 'plugins.php', 'plugin-editor.php' ); + + $this->update_menu( 'plugins.php', 'https://wordpress.com/plugins/' . $this->domain ); + } + + /** + * Adds Users menu. + */ + public function add_users_menu() { + $submenus_to_update = array( + 'profile.php' => 'https://wordpress.com/me', + ); + + if ( self::DEFAULT_VIEW === $this->get_preferred_view( 'users.php' ) ) { + $submenus_to_update['users.php'] = 'https://wordpress.com/people/team/' . $this->domain; + $submenus_to_update['user-new.php'] = 'https://wordpress.com/people/new/' . $this->domain; + } + + $slug = current_user_can( 'list_users' ) ? 'users.php' : 'profile.php'; + $this->update_submenus( $slug, $submenus_to_update ); + add_submenu_page( $slug, esc_attr__( 'Account Settings', 'jetpack-masterbar' ), __( 'Account Settings', 'jetpack-masterbar' ), 'read', 'https://wordpress.com/me/account' ); + } + + /** + * Adds Tools menu. + */ + public function add_tools_menu() { + $submenus_to_update = array(); + if ( self::DEFAULT_VIEW === $this->get_preferred_view( 'import.php' ) ) { + $submenus_to_update['import.php'] = 'https://wordpress.com/import/' . $this->domain; + } + if ( self::DEFAULT_VIEW === $this->get_preferred_view( 'export.php' ) ) { + $submenus_to_update['export.php'] = 'https://wordpress.com/export/' . $this->domain; + } + $this->update_submenus( 'tools.php', $submenus_to_update ); + + $this->hide_submenu_page( 'tools.php', 'tools.php' ); + $this->hide_submenu_page( 'tools.php', 'delete-blog' ); + + // @phan-suppress-next-line PhanTypeMismatchArgumentProbablyReal -- Core should ideally document null for no-callback arg. TODO add link with Trac issue. + add_submenu_page( 'tools.php', esc_attr__( 'Marketing', 'jetpack-masterbar' ), __( 'Marketing', 'jetpack-masterbar' ), 'publish_posts', 'https://wordpress.com/marketing/tools/' . $this->domain, null, 0 ); + if ( ! $this->use_wp_admin_interface() ) { + // @phan-suppress-next-line PhanTypeMismatchArgumentProbablyReal -- Core should ideally document null for no-callback arg. TODO add link with Trac issue. + add_submenu_page( 'tools.php', esc_attr__( 'Monetize', 'jetpack-masterbar' ), __( 'Monetize', 'jetpack-masterbar' ), 'manage_options', 'https://wordpress.com/earn/' . $this->domain, null, 1 ); + } + } + + /** + * Adds Settings menu. + */ + public function add_options_menu() { + $submenus_to_update = array(); + + if ( self::DEFAULT_VIEW === $this->get_preferred_view( 'options-general.php' ) ) { + $this->hide_submenu_page( 'options-general.php', 'sharing' ); + } + + if ( self::DEFAULT_VIEW === $this->get_preferred_view( 'options-general.php' ) ) { + $submenus_to_update['options-general.php'] = 'https://wordpress.com/settings/general/' . $this->domain; + } + + if ( self::DEFAULT_VIEW === $this->get_preferred_view( 'options-writing.php' ) ) { + $submenus_to_update['options-writing.php'] = 'https://wordpress.com/settings/writing/' . $this->domain; + } + + if ( self::DEFAULT_VIEW === $this->get_preferred_view( 'options-reading.php' ) + ) { + $submenus_to_update['options-reading.php'] = 'https://wordpress.com/settings/reading/' . $this->domain; + } + + if ( self::DEFAULT_VIEW === $this->get_preferred_view( 'options-discussion.php' ) ) { + $submenus_to_update['options-discussion.php'] = 'https://wordpress.com/settings/discussion/' . $this->domain; + } + + $this->update_submenus( 'options-general.php', $submenus_to_update ); + + // @phan-suppress-next-line PhanTypeMismatchArgumentProbablyReal -- Core should ideally document null for no-callback arg. TODO add link with Trac issue. + add_submenu_page( 'options-general.php', esc_attr__( 'Newsletter', 'jetpack-masterbar' ), __( 'Newsletter', 'jetpack-masterbar' ), 'manage_options', 'https://wordpress.com/settings/newsletter/' . $this->domain, null, 7 ); + // @phan-suppress-next-line PhanTypeMismatchArgumentProbablyReal -- Core should ideally document null for no-callback arg. TODO add link with Trac issue. + add_submenu_page( 'options-general.php', esc_attr__( 'Podcasting', 'jetpack-masterbar' ), __( 'Podcasting', 'jetpack-masterbar' ), 'manage_options', 'https://wordpress.com/settings/podcasting/' . $this->domain, null, 8 ); + // @phan-suppress-next-line PhanTypeMismatchArgumentProbablyReal -- Core should ideally document null for no-callback arg. TODO add link with Trac issue. + add_submenu_page( 'options-general.php', esc_attr__( 'Performance', 'jetpack-masterbar' ), __( 'Performance', 'jetpack-masterbar' ), 'manage_options', 'https://wordpress.com/settings/performance/' . $this->domain, null, 9 ); + } + + /** + * Create Jetpack menu. + * + * @param int $position Menu position. + * @param bool $separator Whether to add a separator before the menu. + */ + public function create_jetpack_menu( $position = 50, $separator = true ) { + if ( $separator ) { + $this->add_admin_menu_separator( $position, 'manage_options' ); + ++$position; + } + + $icon = ( new Logo() )->get_base64_logo(); + $is_menu_updated = $this->update_menu( 'jetpack', null, null, null, $icon, $position ); + if ( ! $is_menu_updated ) { + // @phan-suppress-next-line PhanTypeMismatchArgumentProbablyReal -- Core should ideally document null for no-callback arg. TODO add link with Trac issue. + add_menu_page( esc_attr__( 'Jetpack', 'jetpack-masterbar' ), __( 'Jetpack', 'jetpack-masterbar' ), 'manage_options', 'jetpack', null, $icon, $position ); + } + // @phan-suppress-next-line PhanTypeMismatchArgumentProbablyReal -- Core should ideally document null for no-callback arg. TODO add link with Trac issue. + add_submenu_page( 'jetpack', esc_attr__( 'Activity Log', 'jetpack-masterbar' ), __( 'Activity Log', 'jetpack-masterbar' ), 'manage_options', 'https://wordpress.com/activity-log/' . $this->domain, null, 2 ); + // @phan-suppress-next-line PhanTypeMismatchArgumentProbablyReal -- Core should ideally document null for no-callback arg. TODO add link with Trac issue. + add_submenu_page( 'jetpack', esc_attr__( 'Backup', 'jetpack-masterbar' ), __( 'Backup', 'jetpack-masterbar' ), 'manage_options', 'https://wordpress.com/backup/' . $this->domain, null, 3 ); + + if ( self::DEFAULT_VIEW === $this->get_preferred_view( 'jetpack' ) ) { + $this->hide_submenu_page( 'jetpack', 'jetpack#/settings' ); + $this->hide_submenu_page( 'jetpack', 'stats' ); + $this->hide_submenu_page( 'jetpack', esc_url( Redirect::get_url( 'calypso-backups' ) ) ); + $this->hide_submenu_page( 'jetpack', esc_url( Redirect::get_url( 'calypso-scanner' ) ) ); + } + + if ( ! $is_menu_updated ) { + // Remove the submenu auto-created by Core just to be sure that there no issues on non-admin roles. + remove_submenu_page( 'jetpack', 'jetpack' ); + } + } + + /** + * Adds Jetpack menu. + */ + public function add_jetpack_menu() { + $this->create_jetpack_menu(); + } + + /** + * Add the calypso /woocommerce-installation/ menu item. + * + * @param array $current_plan The site's plan if they have one. This is passed from WPcom_Admin_Menu to prevent + * redundant database queries. + */ + public function add_woocommerce_installation_menu( $current_plan = null ) { + /** + * Whether to show the WordPress.com WooCommerce Installation menu. + * + * @use add_filter( 'jetpack_show_wpcom_woocommerce_installation_menu', '__return_true' ); + * @module masterbar + * @since jetpack-10.3.0 + * @param bool $jetpack_show_wpcom_woocommerce_installation_menu Load the WordPress.com WooCommerce Installation menu item. Default to false. + * @param array $current_plan Data about the current site's plan. + */ + if ( apply_filters( 'jetpack_show_wpcom_woocommerce_installation_menu', false, $current_plan ) ) { + $this->add_admin_menu_separator( 54, 'activate_plugins' ); + + $icon_url = 'data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAxMDI0IDEwMjQiPjxwYXRoIGZpbGw9IiNhMmFhYjIiIGQ9Ik02MTIuMTkyIDQyNi4zMzZjMC02Ljg5Ni0zLjEzNi01MS42LTI4LTUxLjYtMzcuMzYgMC00Ni43MDQgNzIuMjU2LTQ2LjcwNCA4Mi42MjQgMCAzLjQwOCAzLjE1MiA1OC40OTYgMjguMDMyIDU4LjQ5NiAzNC4xOTItLjAzMiA0Ni42NzItNzIuMjg4IDQ2LjY3Mi04OS41MnptMjAyLjE5MiAwYzAtNi44OTYtMy4xNTItNTEuNi0yOC4wMzItNTEuNi0zNy4yOCAwLTQ2LjYwOCA3Mi4yNTYtNDYuNjA4IDgyLjYyNCAwIDMuNDA4IDMuMDcyIDU4LjQ5NiAyNy45NTIgNTguNDk2IDM0LjE5Mi0uMDMyIDQ2LjY4OC03Mi4yODggNDYuNjg4LTg5LjUyek0xNDEuMjk2Ljc2OGMtNjguMjI0IDAtMTIzLjUwNCA1NS40ODgtMTIzLjUwNCAxMjMuOTJ2NjUwLjcyYzAgNjguNDMyIDU1LjI5NiAxMjMuOTIgMTIzLjUwNCAxMjMuOTJoMzM5LjgwOGwxMjMuNTA0IDEyMy45MzZWODk5LjMyOGgyNzguMDQ4YzY4LjIyNCAwIDEyMy41Mi01NS40NzIgMTIzLjUyLTEyMy45MnYtNjUwLjcyYzAtNjguNDMyLTU1LjI5Ni0xMjMuOTItMTIzLjUyLTEyMy45MmgtNzQxLjM2em01MjYuODY0IDQyMi4xNmMwIDU1LjA4OC0zMS4wODggMTU0Ljg4LTEwMi42NCAxNTQuODgtNi4yMDggMC0xOC40OTYtMy42MTYtMjUuNDI0LTYuMDE2LTMyLjUxMi0xMS4xNjgtNTAuMTkyLTQ5LjY5Ni01Mi4zNTItNjYuMjU2IDAgMC0zLjA3Mi0xNy43OTItMy4wNzItNDAuNzUyIDAtMjIuOTkyIDMuMDcyLTQ1LjMyOCAzLjA3Mi00NS4zMjggMTUuNTUyLTc1LjcyOCA0My41NTItMTA2LjczNiA5Ni40NDgtMTA2LjczNiA1OS4wNzItLjAzMiA4My45NjggNTguNTI4IDgzLjk2OCAxMTAuMjA4ek00ODYuNDk2IDMwMi40YzAgMy4zOTItNDMuNTUyIDE0MS4xNjgtNDMuNTUyIDIxMy40MjR2NzUuNzEyYy0yLjU5MiAxMi4wOC00LjE2IDI0LjE0NC0yMS44MjQgMjQuMTQ0LTQ2LjYwOCAwLTg4Ljg4LTE1MS40NzItOTIuMDE2LTE2MS44NC02LjIwOCA2Ljg5Ni02Mi4yNCAxNjEuODQtOTYuNDQ4IDE2MS44NC0yNC44NjQgMC00My41NTItMTEzLjY0OC00Ni42MDgtMTIzLjkzNkMxNzYuNzA0IDQzNi42NzIgMTYwIDMzNC4yMjQgMTYwIDMyNy4zMjhjMC0yMC42NzIgMS4xNTItMzguNzM2IDI2LjA0OC0zOC43MzYgNi4yMDggMCAyMS42IDYuMDY0IDIzLjcxMiAxNy4xNjggMTEuNjQ4IDYyLjAzMiAxNi42ODggMTIwLjUxMiAyOS4xNjggMTg1Ljk2OCAxLjg1NiAyLjkyOCAxLjUwNCA3LjAwOCA0LjU2IDEwLjQzMiAzLjE1Mi0xMC4yODggNjYuOTI4LTE2OC43ODQgOTQuOTYtMTY4Ljc4NCAyMi41NDQgMCAzMC40IDQ0LjU5MiAzMy41MzYgNjEuODI0IDYuMjA4IDIwLjY1NiAxMy4wODggNTUuMjE2IDIyLjQxNiA4Mi43NTIgMC0xMy43NzYgMTIuNDgtMjAzLjEyIDY1LjM5Mi0yMDMuMTIgMTguNTkyLjAzMiAyNi43MDQgNi45MjggMjYuNzA0IDI3LjU2OHpNODcwLjMyIDQyMi45MjhjMCA1NS4wODgtMzEuMDg4IDE1NC44OC0xMDIuNjQgMTU0Ljg4LTYuMTkyIDAtMTguNDQ4LTMuNjE2LTI1LjQyNC02LjAxNi0zMi40MzItMTEuMTY4LTUwLjE3Ni00OS42OTYtNTIuMjg4LTY2LjI1NiAwIDAtMy44ODgtMTcuOTItMy44ODgtNDAuODk2czMuODg4LTQ1LjE4NCAzLjg4OC00NS4xODRjMTUuNTUyLTc1LjcyOCA0My40ODgtMTA2LjczNiA5Ni4zODQtMTA2LjczNiA1OS4xMDQtLjAzMiA4My45NjggNTguNTI4IDgzLjk2OCAxMTAuMjA4eiIvPjwvc3ZnPg=='; + $menu_url = 'https://wordpress.com/woocommerce-installation/' . $this->domain; + + // Only show the menu if the user has the capability to activate_plugins. + // @phan-suppress-next-line PhanTypeMismatchArgumentProbablyReal -- Core should ideally document null for no-callback arg. TODO add link with Trac issue. + add_menu_page( esc_attr__( 'WooCommerce', 'jetpack-masterbar' ), esc_attr__( 'WooCommerce', 'jetpack-masterbar' ), 'activate_plugins', $menu_url, null, $icon_url, 55 ); + } + } + + /** + * AJAX handler for retrieving the upsell nudge. + */ + public function wp_ajax_upsell_nudge_jitm() { + check_ajax_referer( 'upsell_nudge_jitm' ); + + $nudge = $this->get_upsell_nudge(); + if ( ! $nudge ) { + wp_die(); + } + + $link = $nudge['link']; + if ( str_starts_with( $link, '/' ) ) { + $link = 'https://wordpress.com' . $link; + } + ?> + + is_api_request ) { + add_filter( 'submenu_file', array( $this, 'override_the_theme_installer' ), 10, 2 ); + } + + add_action( + 'admin_menu', + function () { + // @phan-suppress-next-line PhanUndeclaredFunctionInCallable -- This is temp, pending pf4qpu-nc-p2 + remove_action( 'admin_menu', 'gutenberg_menu', 9 ); + }, + 0 + ); + + // Add notices to the settings pages when there is a Calypso page available. + if ( get_option( 'wpcom_admin_interface' ) === 'wp-admin' ) { + add_action( 'current_screen', array( $this, 'add_settings_page_notice' ) ); + } + } + + /** + * Dequeues unnecessary scripts. + */ + public function dequeue_scripts() { + wp_dequeue_script( 'a8c_wpcom_masterbar_overrides' ); // Initially loaded in modules/masterbar/masterbar/class-masterbar.php. + } + + /** + * Determines whether the current locale is right-to-left (RTL). + * + * Performs the check against the current locale set on the WordPress.com's account settings. + * See `Masterbar::__construct` in `modules/masterbar/masterbar/class-masterbar.php`. + */ + public function is_rtl() { + return get_user_option( 'jetpack_wpcom_is_rtl' ); + } + + /** + * Create the desired menu output. + */ + public function reregister_menu_items() { + parent::reregister_menu_items(); + + $this->add_my_home_menu(); + $this->remove_gutenberg_menu(); + + // We don't need the `My Mailboxes` when the interface is set to wp-admin or the site is a staging site, + if ( get_option( 'wpcom_admin_interface' ) !== 'wp-admin' && ! get_option( 'wpcom_is_staging_site' ) ) { + $this->add_my_mailboxes_menu(); + } + + // Not needed outside of wp-admin. + if ( ! $this->is_api_request ) { + $this->add_browse_sites_link(); + $this->add_site_card_menu(); + $this->add_new_site_link(); + } + + $this->add_woocommerce_installation_menu(); + ksort( $GLOBALS['menu'] ); + } + + /** + * Get the preferred view for the given screen. + * + * @param string $screen Screen identifier. + * @param bool $fallback_global_preference (Optional) Whether the global preference for all screens should be used + * as fallback if there is no specific preference for the given screen. + * Default: true. + * @return string + */ + public function get_preferred_view( $screen, $fallback_global_preference = true ) { + + // Export on Atomic sites are always managed on WP Admin. + if ( in_array( $screen, array( 'export.php' ), true ) ) { + return self::CLASSIC_VIEW; + } + + /** + * When Jetpack SSO is disabled, we need to force Calypso because it might create confusion to be redirected to WP-Admin. + * Furthermore, because we don't display the quick switcher, users having an WP-Admin interface by default won't be able to go back to the Calyso version. + */ + if ( ! ( new Modules() )->is_active( 'sso' ) ) { + return self::DEFAULT_VIEW; + } + + return parent::get_preferred_view( $screen, $fallback_global_preference ); + } + + /** + * Adds Users menu. + */ + public function add_users_menu() { + $slug = current_user_can( 'list_users' ) ? 'users.php' : 'profile.php'; + if ( self::DEFAULT_VIEW === $this->get_preferred_view( 'users.php' ) ) { + $submenus_to_update = array( + 'users.php' => 'https://wordpress.com/people/team/' . $this->domain, + ); + $this->update_submenus( $slug, $submenus_to_update ); + } + + if ( ! $this->use_wp_admin_interface() ) { + // The 'Subscribers' menu exists in the Jetpack menu for Classic wp-admin interface, so only add it for non-wp-admin interfaces. + // // @phan-suppress-next-line PhanTypeMismatchArgumentProbablyReal -- Core should ideally document null for no-callback arg. TODO add link with Trac issue. + add_submenu_page( 'users.php', esc_attr__( 'Subscribers', 'jetpack-masterbar' ), __( 'Subscribers', 'jetpack-masterbar' ), 'list_users', 'https://wordpress.com/subscribers/' . $this->domain, null ); + + // When the interface is not set to wp-admin, we replace the Profile submenu. + remove_submenu_page( 'users.php', 'profile.php' ); + // @phan-suppress-next-line PhanTypeMismatchArgumentProbablyReal -- Core should ideally document null for no-callback arg. TODO add link with Trac issue. + add_submenu_page( 'users.php', esc_attr__( 'My Profile', 'jetpack-masterbar' ), __( 'My Profile', 'jetpack-masterbar' ), 'read', 'https://wordpress.com/me/', null ); + } + + // Users who can't 'list_users' will see "Profile" menu & "Profile > Account Settings" as submenu. + add_submenu_page( $slug, esc_attr__( 'Account Settings', 'jetpack-masterbar' ), __( 'Account Settings', 'jetpack-masterbar' ), 'read', 'https://wordpress.com/me/account' ); + } + + /** + * Adds Plugins menu. + */ + public function add_plugins_menu() { + + global $submenu; + + // Calypso plugins screens link. + $plugins_slug = 'https://wordpress.com/plugins/' . $this->domain; + + // Link to the Marketplace from Plugins > Add New on Atomic sites where the wpcom_admin_interface option is set to wp-admin. + if ( self::CLASSIC_VIEW === $this->get_preferred_view( 'plugins.php' ) ) { + $submenus_to_update = array( 'plugin-install.php' => $plugins_slug ); + $this->update_submenus( 'plugins.php', $submenus_to_update ); + return; + } + + // Link to the Marketplace on sites that can't manage plugins. + if ( + function_exists( 'wpcom_site_has_feature' ) && + ! wpcom_site_has_feature( \WPCOM_Features::MANAGE_PLUGINS ) + ) { + // @phan-suppress-next-line PhanTypeMismatchArgumentProbablyReal -- Core should ideally document null for no-callback arg. TODO add link with Trac issue. + add_menu_page( __( 'Plugins', 'jetpack-masterbar' ), __( 'Plugins', 'jetpack-masterbar' ), 'manage_options', $plugins_slug, null, 'dashicons-admin-plugins', 65 ); + return; + } + + if ( ! isset( $submenu['plugins.php'] ) ) { + return; + } + + $plugins_submenu = $submenu['plugins.php']; + + // Move "Add New" plugin submenu to the top position. + foreach ( $plugins_submenu as $submenu_key => $submenu_keys ) { + if ( 'plugin-install.php' === $submenu_keys[2] ) { + // phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited + $submenu['plugins.php'] = array( $submenu_key => $plugins_submenu[ $submenu_key ] ) + $plugins_submenu; + } + } + + $submenus_to_update = array( 'plugin-install.php' => $plugins_slug ); + + $this->update_submenus( 'plugins.php', $submenus_to_update ); + } + + /** + * Adds the site switcher link if user has more than one site. + */ + public function add_browse_sites_link() { + $site_count = get_user_option( 'wpcom_site_count' ); + if ( ! $site_count || $site_count < 2 ) { + return; + } + + // Add the menu item. + // @phan-suppress-next-line PhanTypeMismatchArgumentProbablyReal -- Core should ideally document null for no-callback arg. TODO add link with Trac issue. + add_menu_page( __( 'site-switcher', 'jetpack-masterbar' ), __( 'Browse sites', 'jetpack-masterbar' ), 'read', 'https://wordpress.com/sites', null, 'dashicons-arrow-left-alt2', 0 ); + add_filter( 'add_menu_classes', array( $this, 'set_browse_sites_link_class' ) ); + } + + /** + * Adds a custom element class for Site Switcher menu item. + * + * @param array $menu Associative array of administration menu items. + * + * @return array + */ + public function set_browse_sites_link_class( array $menu ) { + foreach ( $menu as $key => $menu_item ) { + if ( 'site-switcher' !== $menu_item[3] ) { + continue; + } + + $menu[ $key ][4] = add_cssclass( 'site-switcher', $menu_item[4] ); + break; + } + + return $menu; + } + + /** + * Adds a link to the menu to create a new site. + */ + public function add_new_site_link() { + $site_count = get_user_option( 'wpcom_site_count' ); + if ( $site_count && $site_count > 1 ) { + return; + } + + // @phan-suppress-next-line PhanTypeMismatchArgumentProbablyReal -- Core should ideally document null for no-callback arg. TODO add link with Trac issue. + add_menu_page( __( 'Add New Site', 'jetpack-masterbar' ), __( 'Add New Site', 'jetpack-masterbar' ), 'read', 'https://wordpress.com/start?ref=calypso-sidebar', null, 'dashicons-plus-alt' ); + } + + /** + * Adds site card component. + */ + public function add_site_card_menu() { + $default = plugins_url( 'globe-icon.svg', __FILE__ ); + $icon = get_site_icon_url( 32, $default ); + $blog_name = get_option( 'blogname' ) !== '' ? get_option( 'blogname' ) : $this->domain; + $is_coming_soon = ( new Status() )->is_coming_soon(); + + $badge = ''; + + if ( get_option( 'wpcom_is_staging_site' ) ) { + $badge .= '' . esc_html__( 'Staging', 'jetpack-masterbar' ) . ''; + } + + // @phan-suppress-next-line PhanUndeclaredFunction -- This is temp, pending pf4qpu-nc-p2 + if ( ( function_exists( 'site_is_private' ) && site_is_private() ) || $is_coming_soon ) { + $badge .= sprintf( + '%s', + $is_coming_soon ? esc_html__( 'Coming Soon', 'jetpack-masterbar' ) : esc_html__( 'Private', 'jetpack-masterbar' ) + ); + } + + $site_card = ' +
+
%1$s
+
%2$s
+ %3$s +
'; + + $site_card = sprintf( + $site_card, + $blog_name, + $this->domain, + $badge + ); + + // @phan-suppress-next-line PhanTypeMismatchArgumentProbablyReal -- Core should ideally document null for no-callback arg. TODO add link with Trac issue. + add_menu_page( 'site-card', $site_card, 'read', get_home_url(), null, $icon, 1 ); + add_filter( 'add_menu_classes', array( $this, 'set_site_card_menu_class' ) ); + } + + /** + * Adds a custom element class and id for Site Card's menu item. + * + * @param array $menu Associative array of administration menu items. + * + * @return array + */ + public function set_site_card_menu_class( array $menu ) { + foreach ( $menu as $key => $menu_item ) { + if ( 'site-card' !== $menu_item[3] ) { + continue; + } + + $classes = ' toplevel_page_site-card'; + + // webclip.png is the default on WoA sites. Anything other than that means we have a custom site icon. + if ( has_site_icon() && 'https://s0.wp.com/i/webclip.png' !== get_site_icon_url( 512 ) ) { + $classes .= ' has-site-icon'; + } + + $menu[ $key ][4] = $menu_item[4] . $classes; + $menu[ $key ][5] = 'toplevel_page_site_card'; + break; + } + + return $menu; + } + + /** + * Returns the first available upsell nudge. + * + * @return array + */ + public function get_upsell_nudge() { + $jitm = JITM::get_instance(); + $message_path = 'calypso:sites:sidebar_notice'; + $message = $jitm->get_messages( $message_path, wp_json_encode( array( 'message_path' => $message_path ) ), false ); + + if ( isset( $message[0] ) ) { + $message = $message[0]; + return array( + 'content' => $message->content->message, + 'cta' => $message->CTA->message, // phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase + 'link' => $message->CTA->link, // phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase + 'tracks_impression_event_name' => $message->tracks->display->name, + 'tracks_impression_cta_name' => $message->tracks->display->props->cta_name, + 'tracks_click_event_name' => $message->tracks->click->name, + 'tracks_click_cta_name' => $message->tracks->click->props->cta_name, + 'dismissible' => $message->is_dismissible, + 'feature_class' => $message->feature_class, + 'id' => $message->id, + ); + } + } + + /** + * Adds Jetpack menu. + */ + public function add_jetpack_menu() { + // This is supposed to be the same as class-admin-menu but with a different position specified for the Jetpack menu. + if ( 'wp-admin' === get_option( 'wpcom_admin_interface' ) ) { + parent::create_jetpack_menu( 2, false ); + } else { + parent::add_jetpack_menu(); + } + + global $submenu; + $backup_submenu_label = __( 'Backup', 'jetpack-masterbar' ); + $submenu_labels = array_column( $submenu['jetpack'], 3 ); + $backup_position = array_search( $backup_submenu_label, $submenu_labels, true ); + $scan_position = $backup_position !== false ? $backup_position + 1 : $this->get_submenu_item_count( 'jetpack' ) - 1; + + // @phan-suppress-next-line PhanTypeMismatchArgumentProbablyReal -- Core should ideally document null for no-callback arg. TODO add link with Trac issue. + add_submenu_page( 'jetpack', esc_attr__( 'Scan', 'jetpack-masterbar' ), __( 'Scan', 'jetpack-masterbar' ), 'manage_options', 'https://wordpress.com/scan/history/' . $this->domain, null, $scan_position ); + + /** + * Prevent duplicate menu items that link to Jetpack Backup. + * Hide the one that's shown when the standalone backup plugin is not installed, since Jetpack Backup is already included in Atomic sites. + * + * @see https://github.com/Automattic/jetpack/pull/33955 + */ + $this->hide_submenu_page( 'jetpack', esc_url( Redirect::get_url( 'calypso-backups' ) ) ); + } + + /** + * Adds Stats menu. + */ + public function add_stats_menu() { + $menu_title = __( 'Stats', 'jetpack-masterbar' ); + if ( + ! $this->is_api_request && + ( new Modules() )->is_active( 'stats' ) && + function_exists( 'stats_get_image_chart_src' ) + ) { + $img_src = esc_attr( + stats_get_image_chart_src( 'admin-bar-hours-scale-2x', array( 'masterbar' => '' ) ) + ); + $alt = esc_attr__( 'Hourly views', 'jetpack-masterbar' ); + + $menu_title .= "$alt"; + } + + // @phan-suppress-next-line PhanTypeMismatchArgumentProbablyReal -- Core should ideally document null for no-callback arg. TODO add link with Trac issue. + add_menu_page( __( 'Stats', 'jetpack-masterbar' ), $menu_title, 'view_stats', 'https://wordpress.com/stats/day/' . $this->domain, null, 'dashicons-chart-bar', 3 ); + } + + /** + * Adds Upgrades menu. + * + * @param string $plan The current WPCOM plan of the blog. + */ + public function add_upgrades_menu( $plan = null ) { + + if ( get_option( 'wpcom_is_staging_site' ) ) { + return; + } + $products = Jetpack_Plan::get(); + if ( array_key_exists( 'product_name_short', $products ) ) { + $plan = $products['product_name_short']; + } + parent::add_upgrades_menu( $plan ); + + $last_upgrade_submenu_position = $this->get_submenu_item_count( 'paid-upgrades.php' ); + + // @phan-suppress-next-line PhanTypeMismatchArgumentProbablyReal -- Core should ideally document null for no-callback arg. TODO add link with Trac issue. + add_submenu_page( 'paid-upgrades.php', __( 'Domains', 'jetpack-masterbar' ), __( 'Domains', 'jetpack-masterbar' ), 'manage_options', 'https://wordpress.com/domains/manage/' . $this->domain, null, $last_upgrade_submenu_position - 1 ); + + /** + * Whether to show the WordPress.com Emails submenu under the main Upgrades menu. + * + * @use add_filter( 'jetpack_show_wpcom_upgrades_email_menu', '__return_true' ); + * @module masterbar + * + * @since jetpack-9.7.0 + * + * @param bool $show_wpcom_upgrades_email_menu Load the WordPress.com Emails submenu item. Default to false. + */ + if ( apply_filters( 'jetpack_show_wpcom_upgrades_email_menu', false ) ) { + // @phan-suppress-next-line PhanTypeMismatchArgumentProbablyReal -- Core should ideally document null for no-callback arg. TODO add link with Trac issue. + add_submenu_page( 'paid-upgrades.php', __( 'Emails', 'jetpack-masterbar' ), __( 'Emails', 'jetpack-masterbar' ), 'manage_options', 'https://wordpress.com/email/' . $this->domain, null, $last_upgrade_submenu_position ); + } + } + + /** + * Adds Settings menu. + */ + public function add_options_menu() { + parent::add_options_menu(); + + if ( Jetpack_Plan::supports( 'security-settings' ) ) { + add_submenu_page( + 'options-general.php', + esc_attr__( 'Security', 'jetpack-masterbar' ), + __( 'Security', 'jetpack-masterbar' ), + 'manage_options', + 'https://wordpress.com/settings/security/' . $this->domain, + null, // @phan-suppress-current-line PhanTypeMismatchArgumentProbablyReal -- Core should ideally document null for no-callback arg. TODO add link with Trac issue. + 2 + ); + } + + // @phan-suppress-next-line PhanTypeMismatchArgumentProbablyReal -- Core should ideally document null for no-callback arg. TODO add link with Trac issue. + add_submenu_page( 'options-general.php', esc_attr__( 'Hosting Configuration', 'jetpack-masterbar' ), __( 'Hosting Configuration', 'jetpack-masterbar' ), 'manage_options', 'https://wordpress.com/hosting-config/' . $this->domain, null, 11 ); + + // Page Optimize is active by default on all Atomic sites and registers a Settings > Performance submenu which + // would conflict with our own Settings > Performance that links to Calypso, so we hide it it since the Calypso + // performance settings already have a link to Page Optimize settings page. + $this->hide_submenu_page( 'options-general.php', 'page-optimize' ); + + // Hide Settings > Performance when the interface is set to wp-admin. + // This is due to these settings are mostly also available in Jetpack > Settings, in the Performance tab. + if ( get_option( 'wpcom_admin_interface' ) === 'wp-admin' ) { + $this->hide_submenu_page( 'options-general.php', 'https://wordpress.com/settings/performance/' . $this->domain ); + } + } + + /** + * Adds Tools menu entries. + */ + public function add_tools_menu() { + parent::add_tools_menu(); + + // Link the Tools menu to Available Tools when the interface is set to wp-admin. + if ( get_option( 'wpcom_admin_interface' ) === 'wp-admin' ) { + // @phan-suppress-next-line PhanTypeMismatchArgumentProbablyReal -- Core should ideally document null for no-callback arg. TODO add link with Trac issue. + add_submenu_page( 'tools.php', esc_attr__( 'Available Tools', 'jetpack-masterbar' ), __( 'Available Tools', 'jetpack-masterbar' ), 'edit_posts', 'tools.php', null, 0 ); + } + + /** + * Adds the WordPress.com Site Monitoring submenu under the main Tools menu. + */ + // @phan-suppress-next-line PhanTypeMismatchArgumentProbablyReal -- Core should ideally document null for no-callback arg. TODO add link with Trac issue. + add_submenu_page( 'tools.php', esc_attr__( 'Site Monitoring', 'jetpack-masterbar' ), __( 'Site Monitoring', 'jetpack-masterbar' ), 'manage_options', 'https://wordpress.com/site-monitoring/' . $this->domain, null, 7 ); + + /** + * Adds the WordPress.com GitHub Deployments submenu under the main Tools menu. + */ + // @phan-suppress-next-line PhanTypeMismatchArgumentProbablyReal -- Core should ideally document null for no-callback arg. TODO add link with Trac issue. + add_submenu_page( 'tools.php', esc_attr__( 'GitHub Deployments', 'jetpack-masterbar' ), __( 'GitHub Deployments', 'jetpack-masterbar' ), 'manage_options', 'https://wordpress.com/github-deployments/' . $this->domain, null, 8 ); + } + + /** + * Override the global submenu_file for theme-install.php page so the WP Admin menu item gets highlighted correctly. + * + * @param string $submenu_file The current pages $submenu_file global variable value. + * @return string | null + */ + public function override_the_theme_installer( $submenu_file ) { + global $pagenow; + + if ( 'themes.php' === $submenu_file && 'theme-install.php' === $pagenow ) { + return null; + } + return $submenu_file; + } + + /** + * Also remove the Gutenberg plugin menu. + */ + public function remove_gutenberg_menu() { + // Always remove the Gutenberg menu. + remove_menu_page( 'gutenberg' ); + } + + /** + * Saves the sidebar state ( expanded / collapsed ) via an ajax request. + */ + public function ajax_sidebar_state() { + $expanded = isset( $_REQUEST['expanded'] ) ? filter_var( wp_unslash( $_REQUEST['expanded'] ), FILTER_VALIDATE_BOOLEAN ) : false; // phpcs:ignore WordPress.Security.NonceVerification.Recommended + Client::wpcom_json_api_request_as_user( + '/me/preferences', + '2', + array( + 'method' => 'POST', + ), + array( 'calypso_preferences' => (object) array( 'sidebarCollapsed' => ! $expanded ) ), + 'wpcom' + ); + + wp_die(); + } + + /** + * Handle ajax requests to dismiss a just-in-time-message + */ + public function wp_ajax_jitm_dismiss() { + check_ajax_referer( 'jitm_dismiss' ); + $jitm = \Automattic\Jetpack\JITMS\JITM::get_instance(); + if ( isset( $_REQUEST['id'] ) && isset( $_REQUEST['feature_class'] ) ) { + $jitm->dismiss( sanitize_text_field( wp_unslash( $_REQUEST['id'] ) ), sanitize_text_field( wp_unslash( $_REQUEST['feature_class'] ) ) ); + } + wp_die(); + } + + /** + * Adds a notice above each settings page while using the Classic view to indicate + * that the Default view offers more features. Links to the default view. + * + * @return void + */ + public function add_settings_page_notice() { + if ( ! is_admin() ) { + return; + } + + $current_screen = get_current_screen(); + + if ( ! $current_screen instanceof \WP_Screen ) { + return; + } + + // Show the notice for the following screens and map them to the Calypso page. + $screen_map = array( + 'options-general' => 'general', + 'options-reading' => 'reading', + ); + + $mapped_screen = $screen_map[ $current_screen->id ] ?? false; + + if ( ! $mapped_screen ) { + return; + } + + $switch_url = sprintf( 'https://wordpress.com/settings/%s/%s', $mapped_screen, $this->domain ); + + // Close over the $switch_url variable. + $admin_notices = function () use ( $switch_url ) { + wp_admin_notice( + wp_kses( + sprintf( + // translators: %s is a link to the Calypso settings page. + __( 'You are currently using the Classic view, which doesn’t offer the same set of features as the Default view. To access additional settings and features, switch to the Default view. ', 'jetpack-masterbar' ), + esc_url( $switch_url ) + ), + array( 'a' => array( 'href' => array() ) ) + ), + array( + 'type' => 'warning', + ) + ); + }; + + add_action( 'admin_notices', $admin_notices ); + } +} diff --git a/projects/packages/masterbar/src/admin-menu/class-base-admin-menu.php b/projects/packages/masterbar/src/admin-menu/class-base-admin-menu.php new file mode 100644 index 0000000000000..e714d0cdb7b29 --- /dev/null +++ b/projects/packages/masterbar/src/admin-menu/class-base-admin-menu.php @@ -0,0 +1,796 @@ +is_api_request = defined( 'REST_REQUEST' ) && REST_REQUEST || isset( $_SERVER['REQUEST_URI'] ) && str_starts_with( filter_var( wp_unslash( $_SERVER['REQUEST_URI'] ) ), '/?rest_route=%2Fwpcom%2Fv2%2Fadmin-menu' ); + $this->domain = ( new Status() )->get_site_suffix(); + + add_action( 'admin_menu', array( $this, 'reregister_menu_items' ), 99998 ); + add_action( 'admin_menu', array( $this, 'hide_parent_of_hidden_submenus' ), 99999 ); + + if ( ! $this->is_api_request ) { + add_filter( 'admin_menu', array( $this, 'override_svg_icons' ), 99999 ); + add_action( 'admin_enqueue_scripts', array( $this, 'enqueue_scripts' ), 11 ); + add_action( 'admin_head', array( $this, 'set_site_icon_inline_styles' ) ); + add_action( 'in_admin_header', array( $this, 'add_dashboard_switcher' ) ); + add_action( 'admin_footer', array( $this, 'dashboard_switcher_scripts' ) ); + add_action( 'admin_menu', array( $this, 'handle_preferred_view' ), 99997 ); + add_filter( 'admin_body_class', array( $this, 'admin_body_class' ) ); + + // Do not inject core mobile toggle when the user wants to use the WP Admin interface. + if ( ! $this->use_wp_admin_interface() ) { + add_action( 'adminmenu', array( $this, 'inject_core_mobile_toggle' ) ); + } + } + } + + /** + * Returns class instance. + * + * @return static + */ + public static function get_instance() { + $class = static::class; + + if ( empty( static::$instances[ $class ] ) ) { + // @phan-suppress-next-line PhanTypeInstantiateAbstract -- If someone calls `Admin_Menu_Base::get_instance()` they deserve what they get. + static::$instances[ $class ] = new $class(); + } + + return static::$instances[ $class ]; + } + + /** + * Updates the menu data of the given menu slug. + * + * @param string $slug Slug of the menu to update. + * @param ?string $url New menu URL. Defaults to null. + * @param ?string $title New menu title. Defaults to null. + * @param ?string $cap New menu capability. Defaults to null. + * @param ?string $icon New menu icon. Defaults to null. + * @param ?int $position New menu position. Defaults to null. + * @return bool Whether the menu has been updated. + */ + public function update_menu( $slug, $url = null, $title = null, $cap = null, $icon = null, $position = null ) { + global $menu, $submenu; + + $menu_item = null; + $menu_position = 0; + + foreach ( $menu as $i => $item ) { + if ( $slug === $item[2] ) { + $menu_item = $item; + $menu_position = $i; + break; + } + } + + if ( ! $menu_item ) { + return false; + } + + if ( $title ) { + $menu_item[0] = $title; + $menu_item[3] = esc_attr( $title ); + } + + if ( $cap ) { + $menu_item[1] = $cap; + } + + // Change parent slug only if there are no submenus (the slug of the 1st submenu will be used if there are submenus). + if ( $url ) { + $this->hide_submenu_page( $slug, $slug ); + + if ( ! isset( $submenu[ $slug ] ) || ! $this->has_visible_items( $submenu[ $slug ] ) ) { + $menu_item[2] = $url; + } + } + + if ( $icon ) { + $menu_item[4] = 'menu-top'; + $menu_item[6] = $icon; + } + + // phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited + unset( $menu[ $menu_position ] ); + if ( $position ) { + $menu_position = $position; + } + $this->set_menu_item( $menu_item, $menu_position ); + + // Only add submenu when there are other submenu items. + if ( $url && isset( $submenu[ $slug ] ) && $this->has_visible_items( $submenu[ $slug ] ) ) { + // @phan-suppress-next-line PhanTypeMismatchArgumentProbablyReal -- Core should ideally document null for no-callback arg. TODO add link with Trac issue. + add_submenu_page( $slug, $menu_item[3], $menu_item[0], $menu_item[1], $url, null, 0 ); + } + + return true; + } + + /** + * Updates the submenus of the given menu slug. + * + * It hides the menu by adding the `hide-if-js` css class and duplicates the submenu with the new slug. + * + * @param string $slug Menu slug. + * @param array $submenus_to_update Array of new submenu slugs. + */ + public function update_submenus( $slug, $submenus_to_update ) { + global $submenu; + + if ( ! isset( $submenu[ $slug ] ) ) { + return; + } + + // This is needed for cases when the submenus to update have the same new slug. + $submenus_to_update = array_filter( + $submenus_to_update, + static function ( $item, $old_slug ) { + return $item !== $old_slug; + }, + ARRAY_FILTER_USE_BOTH + ); + + /** + * Iterate over all submenu items and add the hide the submenus with CSS classes. + * This is done separately of the second foreach because the position of the submenu might change. + */ + foreach ( $submenu[ $slug ] as $index => $item ) { + if ( ! array_key_exists( $item[2], $submenus_to_update ) ) { + continue; + } + + $this->hide_submenu_element( $index, $slug, $item ); + } + + $submenu_items = array_values( $submenu[ $slug ] ); + + /** + * Iterate again over the submenu array. We need a copy of the array because add_submenu_page will add new elements + * to submenu array that might cause an infinite loop. + */ + foreach ( $submenu_items as $i => $submenu_item ) { + if ( ! array_key_exists( $submenu_item[2], $submenus_to_update ) ) { + continue; + } + + add_submenu_page( + $slug, + $submenu_item[3] ?? '', + $submenu_item[0] ?? '', + $submenu_item[1] ?? 'read', + $submenus_to_update[ $submenu_item[2] ], + null, // @phan-suppress-current-line PhanTypeMismatchArgumentProbablyReal -- Core should ideally document null for no-callback arg. TODO add link with Trac issue. + 0 === $i ? 0 : $i + 1 + ); + } + } + + /** + * Adds a menu separator. + * + * @param int $position The position in the menu order this item should appear. + * @param string $cap Optional. The capability required for this menu to be displayed to the user. + * Default: 'read'. + */ + public function add_admin_menu_separator( $position = null, $cap = 'read' ) { + $menu_item = array( + '', // Menu title (ignored). + $cap, // Required capability. + wp_unique_id( 'separator-custom-' ), // URL or file (ignored, but must be unique). + '', // Page title (ignored). + 'wp-menu-separator', // CSS class. Identifies this item as a separator. + ); + + $this->set_menu_item( $menu_item, $position ); + } + + /** + * Enqueues scripts and styles. + */ + public function enqueue_scripts() { + $assets_base_path = '../../dist/admin-menu/'; + if ( $this->is_rtl() ) { + $css_path = 'admin-menu.rtl.css'; + } else { + $css_path = 'admin-menu.css'; + } + + $css_path = $assets_base_path . $css_path; + + Assets::register_script( + 'jetpack-admin-menu', + $assets_base_path . 'admin-menu.js', + __FILE__, + array( + 'enqueue' => true, + 'css_path' => $assets_base_path . 'admin-menu.css', + ) + ); + + wp_localize_script( + 'jetpack-admin-menu', + 'jetpackAdminMenu', + array( + 'upsellNudgeJitm' => wp_create_nonce( 'upsell_nudge_jitm' ), + 'jitmDismissNonce' => wp_create_nonce( 'jitm_dismiss' ), + ) + ); + + // Load nav unification styles when the user isn't using wp-admin interface style. + if ( ! $this->use_wp_admin_interface() ) { + Assets::register_script( + 'jetpack-admin-nav-unification', + $assets_base_path . 'admin-menu-nav-unification.js', + __FILE__, + array( + 'enqueue' => true, + 'css_path' => $assets_base_path . 'admin-menu-nav-unification.css', + ) + ); + } + + $this->configure_colors_for_rtl_stylesheets(); + } + + /** + * Mark the core colors stylesheets as RTL depending on the value from the environment. + * This fixes a core issue where the extra RTL data is not added to the colors stylesheet. + * https://core.trac.wordpress.org/ticket/53090 + */ + public function configure_colors_for_rtl_stylesheets() { + wp_style_add_data( 'colors', 'rtl', $this->is_rtl() ); + } + + /** + * Injects inline-styles for site icon for when third-party plugins remove enqueued stylesheets. + * Unable to use wp_add_inline_style as plugins remove styles from all non-standard handles + */ + public function set_site_icon_inline_styles() { + echo ''; + } + + /** + * Hide the submenu page based on slug and return the item that was hidden. + * + * Instead of actually removing the submenu item, a safer approach is to hide it and filter it in the API response. + * In this manner we'll avoid breaking third-party plugins depending on items that no longer exist. + * + * A false|array value is returned to be consistent with remove_submenu_page() function + * + * @param string $menu_slug The parent menu slug. + * @param string $submenu_slug The submenu slug that should be hidden. + * @return false|array + */ + public function hide_submenu_page( $menu_slug, $submenu_slug ) { + global $submenu; + + if ( ! isset( $submenu[ $menu_slug ] ) ) { + return false; + } + + foreach ( $submenu[ $menu_slug ] as $i => $item ) { + if ( $submenu_slug !== $item[2] ) { + continue; + } + + $this->hide_submenu_element( $i, $menu_slug, $item ); + + return $item; + } + + return false; + } + + /** + * Apply the hide-if-js CSS class to a submenu item. + * + * @param int $index The position of a submenu item in the submenu array. + * @param string $parent_slug The parent slug. + * @param array $item The submenu item. + */ + public function hide_submenu_element( $index, $parent_slug, $item ) { + global $submenu; + + $css_classes = empty( $item[4] ) ? self::HIDE_CSS_CLASS : $item[4] . ' ' . self::HIDE_CSS_CLASS; + + // phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited + $submenu [ $parent_slug ][ $index ][4] = $css_classes; + } + + /** + * Check if the menu has submenu items visible + * + * @param array $submenu_items The submenu items. + * @return bool + */ + public function has_visible_items( $submenu_items ) { + $visible_items = array_filter( + $submenu_items, + array( $this, 'is_item_visible' ) + ); + + return array() !== $visible_items; + } + + /** + * Return the number of existing submenu items under the supplied parent slug. + * + * @param string $parent_slug The slug of the parent menu. + * @return int The number of submenu items under $parent_slug. + */ + public function get_submenu_item_count( $parent_slug ) { + global $submenu; + + if ( empty( $parent_slug ) || empty( $submenu[ $parent_slug ] ) || ! is_array( $submenu[ $parent_slug ] ) ) { + return 0; + } + + return count( $submenu[ $parent_slug ] ); + } + + /** + * Adds the given menu item in the specified position. + * + * @param array $item The menu item to add. + * @param int $position The position in the menu order this item should appear. + */ + public function set_menu_item( $item, $position = null ) { + global $menu; + + // Handle position (avoids overwriting menu items already populated in the given position). + // Inspired by https://core.trac.wordpress.org/browser/trunk/src/wp-admin/menu.php?rev=49837#L160. + if ( null === $position ) { + $menu[] = $item; // phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited + } elseif ( isset( $menu[ "$position" ] ) ) { + $position += (int) substr( base_convert( md5( $item[2] . $item[0] ), 16, 10 ), -5 ) * 0.00001; + $menu[ "$position" ] = $item; // phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited + } else { + $menu[ $position ] = $item; // phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited + } + } + + /** + * Determines whether the current locale is right-to-left (RTL). + */ + public function is_rtl() { + return is_rtl(); + } + + /** + * Checks for any SVG icons in the menu, and overrides things so that + * we can display the icon in the correct colour for the theme. + */ + public function override_svg_icons() { + global $menu; + + $svg_items = array(); + foreach ( $menu as $idx => $menu_item ) { + // Menu items that don't have icons, for example separators, have less than 7 + // elements, partly because the 7th is the icon. So, if we have less than 7, + // let's skip it. + if ( ! is_countable( $menu_item ) || ( count( $menu_item ) < 7 ) ) { + continue; + } + + // If the hookname contain a URL than sanitize it by replacing invalid characters. + if ( str_contains( $menu_item[5], '://' ) ) { + $menu_item[5] = preg_replace( '![:/.]+!', '_', $menu_item[5] ); + } + + if ( str_starts_with( $menu_item[6], 'data:image/svg+xml' ) && 'site-card' !== $menu_item[3] ) { + $svg_items[] = array( + 'icon' => $menu_item[6], + 'id' => $menu_item[5], + ); + $menu_item[4] .= ' menu-svg-icon'; + $menu_item[6] = 'none'; + } + // phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited + $menu[ $idx ] = $menu_item; + } + if ( $svg_items !== array() ) { + $styles = '.menu-svg-icon .wp-menu-image { background-repeat: no-repeat; background-position: center center } '; + foreach ( $svg_items as $svg_item ) { + $styles .= sprintf( '#%s .wp-menu-image { background-image: url( "%s" ) }', $svg_item['id'], $svg_item['icon'] ); + } + $styles .= '@supports ( mask-image: none ) or ( -webkit-mask-image: none ) { '; + $styles .= '.menu-svg-icon .wp-menu-image { background-image: none; } '; + $styles .= '.menu-svg-icon .wp-menu-image::before { background-color: currentColor; '; + $styles .= 'mask-size: contain; mask-position: center center; mask-repeat: no-repeat; '; + $styles .= '-webkit-mask-size: contain; -webkit-mask-position: center center; -webkit-mask-repeat: no-repeat; content:"" } '; + foreach ( $svg_items as $svg_item ) { + $styles .= sprintf( + '#%s .wp-menu-image { background-image: none; } #%s .wp-menu-image::before{ mask-image: url( "%s" ); -webkit-mask-image: url( "%s" ) }', + $svg_item['id'], + $svg_item['id'], + $svg_item['icon'], + $svg_item['icon'] + ); + } + $styles .= '}'; + + wp_register_style( 'svg-menu-overrides', false, array(), '20210331' ); + wp_enqueue_style( 'svg-menu-overrides' ); + wp_add_inline_style( 'svg-menu-overrides', $styles ); + } + } + + /** + * Hide menus that are unauthorized and don't have visible submenus and cases when the menu has the same slug + * as the first submenu item. + * + * This must be done at the end of menu and submenu manipulation in order to avoid performing this check each time + * the submenus are altered. + */ + public function hide_parent_of_hidden_submenus() { + global $menu, $submenu; + + $this->sort_hidden_submenus(); + + foreach ( $menu as $menu_index => $menu_item ) { + $has_submenus = isset( $submenu[ $menu_item[2] ] ); + + // Skip if the menu doesn't have submenus. + if ( ! $has_submenus || ! is_array( $submenu[ $menu_item[2] ] ) ) { + continue; + } + + // If the first submenu item is hidden then we should also hide the parent. + // Since the submenus are ordered by self::HIDE_CSS_CLASS (hidden submenus should be at the end of the array), + // we can say that if the first submenu is hidden then we should also hide the menu. + $first_submenu_item = array_values( $submenu[ $menu_item[2] ] )[0]; + $is_first_submenu_visible = $this->is_item_visible( $first_submenu_item ); + + // if the user does not have access to the menu and the first submenu is hidden, then hide the menu. + if ( ! current_user_can( $menu_item[1] ) && ! $is_first_submenu_visible ) { + // phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited + $menu[ $menu_index ][4] = self::HIDE_CSS_CLASS; + } + + // if the menu has the same slug as the first submenu then hide the submenu. + if ( $menu_item[2] === $first_submenu_item[2] && ! $is_first_submenu_visible ) { + // phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited + $menu[ $menu_index ][4] = self::HIDE_CSS_CLASS; + } + } + } + + /** + * Sort the hidden submenus by moving them at the end of the array in order to avoid WP using them as default URLs. + * + * This operation has to be done at the end of submenu manipulation in order to guarantee that the hidden submenus + * are at the end of the array. + */ + public function sort_hidden_submenus() { + global $submenu; + + foreach ( $submenu as $menu_slug => $submenu_items ) { + if ( ! $submenu_items ) { + continue; + } + + foreach ( $submenu_items as $submenu_index => $submenu_item ) { + if ( $this->is_item_visible( $submenu_item ) ) { + continue; + } + + unset( $submenu[ $menu_slug ][ $submenu_index ] ); + // phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited + $submenu[ $menu_slug ][] = $submenu_item; + } + } + } + + /** + * Check if the given item is visible or not in the admin menu. + * + * @param array $item A menu or submenu array. + */ + public function is_item_visible( $item ) { + return ! isset( $item[4] ) || ! str_contains( $item[4], self::HIDE_CSS_CLASS ); + } + + /** + * Adds a dashboard switcher to the list of screen meta links of the current page. + */ + public function add_dashboard_switcher() { + $menu_mappings = require __DIR__ . '/menu-mappings.php'; + $screen = $this->get_current_screen(); + + // Let's show the switcher only in screens that we have a Calypso mapping to switch to. + if ( empty( $menu_mappings[ $screen ] ) ) { + return; + } + ?> + + + get_preferred_views(); + $screen = str_replace( '?post_type=post', '', $screen ); + $preferred_views[ $screen ] = $view; + update_user_option( get_current_user_id(), 'jetpack_admin_menu_preferred_views', $preferred_views ); + } + + /** + * Get the preferred views for all screens. + * + * @return array + */ + public function get_preferred_views() { + $preferred_views = get_user_option( 'jetpack_admin_menu_preferred_views' ); + + if ( ! $preferred_views ) { + return array(); + } + + return $preferred_views; + } + + /** + * Get the preferred view for the given screen. + * + * @param string $screen Screen identifier. + * @param bool $fallback_global_preference (Optional) Whether the global preference for all screens should be used + * as fallback if there is no specific preference for the given screen. + * Default: true. + * @return string + */ + public function get_preferred_view( $screen, $fallback_global_preference = true ) { + $preferred_views = $this->get_preferred_views(); + + if ( ! isset( $preferred_views[ $screen ] ) ) { + if ( ! $fallback_global_preference ) { + return self::UNKNOWN_VIEW; + } + + $should_link_to_wp_admin = $this->should_link_to_wp_admin() || $this->use_wp_admin_interface(); + return $should_link_to_wp_admin ? self::CLASSIC_VIEW : self::DEFAULT_VIEW; + } + + return $preferred_views[ $screen ]; + } + + /** + * Gets the identifier of the current screen. + * + * @return string + */ + public function get_current_screen() { + // phpcs:disable WordPress.Security.NonceVerification + global $pagenow; + $screen = isset( $_REQUEST['screen'] ) ? sanitize_text_field( wp_unslash( $_REQUEST['screen'] ) ) : $pagenow; + if ( isset( $_GET['post_type'] ) ) { + $screen = add_query_arg( 'post_type', sanitize_text_field( wp_unslash( $_GET['post_type'] ) ), $screen ); + } + if ( isset( $_GET['taxonomy'] ) ) { + $screen = add_query_arg( 'taxonomy', sanitize_text_field( wp_unslash( $_GET['taxonomy'] ) ), $screen ); + } + if ( isset( $_GET['page'] ) ) { + $screen = add_query_arg( 'page', sanitize_text_field( wp_unslash( $_GET['page'] ) ), $screen ); + } + return $screen; + // phpcs:enable WordPress.Security.NonceVerification + } + + /** + * Stores the preferred view for the current screen. + */ + public function handle_preferred_view() { + // phpcs:disable WordPress.Security.NonceVerification + if ( ! isset( $_GET['preferred-view'] ) ) { + return; + } + + // phpcs:disable WordPress.Security.NonceVerification + $preferred_view = sanitize_key( $_GET['preferred-view'] ); + + if ( ! in_array( $preferred_view, array( self::DEFAULT_VIEW, self::CLASSIC_VIEW ), true ) ) { + return; + } + + $current_screen = $this->get_current_screen(); + + $this->set_preferred_view( $current_screen, $preferred_view ); + + /** + * Dashboard Quick switcher action triggered when a user switches to a different view. + * + * @module masterbar + * + * @since jetpack-9.9.1 + * + * @param string The current screen of the user. + * @param string The preferred view the user selected. + */ + \do_action( 'jetpack_dashboard_switcher_changed_view', $current_screen, $preferred_view ); + + if ( self::DEFAULT_VIEW === $preferred_view ) { + // Redirect to default view if that's the newly preferred view. + $menu_mappings = require __DIR__ . '/menu-mappings.php'; + if ( isset( $menu_mappings[ $current_screen ] ) ) { + // Using `wp_redirect` intentionally because we're redirecting to Calypso. + wp_redirect( $menu_mappings[ $current_screen ] . $this->domain ); // phpcs:ignore WordPress.Security.SafeRedirect + exit; + } + } elseif ( self::CLASSIC_VIEW === $preferred_view ) { + // Removes the `preferred-view` param from the URL to avoid issues with + // screens that don't expect this param to be present in the URL. + wp_safe_redirect( remove_query_arg( 'preferred-view' ) ); + exit; + } + // phpcs:enable WordPress.Security.NonceVerification + } + + /** + * Adds the necessary CSS class to the admin body class. + * + * @param string $admin_body_classes Contains all the admin body classes. + * + * @return string + */ + public function admin_body_class( $admin_body_classes ) { + return " is-nav-unification $admin_body_classes "; + } + + /** + * Whether to use wp-admin pages rather than Calypso. + * + * Options: + * false - Calypso (Default). + * true - wp-admin. + * + * @return bool + */ + public function should_link_to_wp_admin() { + return get_user_option( 'jetpack_admin_menu_link_destination' ); + } + + /** + * Injects the core's mobile toggle for proper positioning of the submenus. + * + * @see https://core.trac.wordpress.org/ticket/32747 + * + * @return void + */ + public function inject_core_mobile_toggle() { + echo ''; + } + + /** + * Whether the current user has indicated they want to use the wp-admin interface for the given screen. + * + * @return bool + */ + public function use_wp_admin_interface() { + return 'wp-admin' === get_option( 'wpcom_admin_interface' ); + } + + /** + * Create the desired menu output. + */ + abstract public function reregister_menu_items(); +} diff --git a/projects/packages/masterbar/src/admin-menu/class-dashboard-switcher-tracking.php b/projects/packages/masterbar/src/admin-menu/class-dashboard-switcher-tracking.php new file mode 100644 index 0000000000000..58b2d49e00aff --- /dev/null +++ b/projects/packages/masterbar/src/admin-menu/class-dashboard-switcher-tracking.php @@ -0,0 +1,175 @@ +tracking = $tracking; + $this->plan = $plan; + $this->wpcom_tracking = $wpcom_tracking; + } + + /** + * Create an event for the Quick switcher when the user changes it's preferred view. + * + * @param string $screen The screen page. + * @param string $view The new preferred view. + */ + public function record_switch_event( $screen, $view ) { + $event_props = array( + 'current_page' => $screen, + 'destination' => $view, + 'plan' => $this->plan, + ); + + if ( defined( 'IS_WPCOM' ) && IS_WPCOM ) { + $event_props['blog_id'] = get_current_blog_id(); + + /** + * Callable injected in the constructor with the static::wpcom_tracks_record_event() static method. + * + * @see wpcom_tracks_record_event A static method from this class that executes the actual WPCOM event record. + */ + $wpcom_tracking = $this->wpcom_tracking; + $wpcom_tracking( $event_props ); + } else { + $this->record_jetpack_event( $event_props ); + } + } + + /** + * Get the current site plan or 'N/A' when we cannot determine site's plan. + * + * @todo: This method can be reused as a wrapper over WPCOM and Atomic as way to get site's current plan (display name). + * + * @return string + */ + public static function get_plan() { + if ( defined( 'IS_WPCOM' ) && IS_WPCOM ) { + if ( class_exists( '\WPCOM_Store_API' ) ) { + // @todo: Maybe introduce a wrapper for this since we are duplicating it from WPCOM_Admin_Menu:253 + $products = \WPCOM_Store_API::get_current_plan( \get_current_blog_id() ); + if ( ! empty( $products['product_slug'] ) ) { + return $products['product_slug']; + } + } + + return 'N/A'; // maybe we should return free or null? At the moment it's safe to return 'N/A' since we use it only for passing it to the event. + } + + // @todo: Maybe introduce a helper for this since we are duplicating it from Atomic_Admin_Menu:240 + $products = Jetpack_Plan::get(); + if ( ! empty( $products['product_slug'] ) ) { + return $products['product_slug']; + } + + return 'N/A'; // maybe we should return free or null? At the moment we use it for passing it to the event. + } + + /** + * Record the event with Jetpack implementation. + * + * For Atomic sites we mark the Jetpack ToS option temporary as read. + * + * @todo Remove the jetpack_options_tos_agreed filter for Atomic sites after the Tracking is properly working for AT sites. + * + * @param array $event_properties The event properties. + */ + private function record_jetpack_event( $event_properties ) { + $woa = ( new Host() )->is_woa_site(); + if ( $woa ) { + add_filter( 'jetpack_options', array( __CLASS__, 'mark_jetpack_tos_as_read' ), 10, 2 ); + } + + $this->tracking->record_user_event( self::JETPACK_EVENT_NAME, $event_properties ); + + if ( $woa ) { + \remove_filter( 'jetpack_options', array( __CLASS__, 'mark_jetpack_tos_as_read' ) ); + } + } + + /** + * Trigger the WPCOM tracks_record_event. + * + * @param array $event_props Event props. + */ + public static function wpcom_tracks_record_event( $event_props ) { + require_lib( 'tracks/client' ); + \tracks_record_event( \wp_get_current_user(), self::WPCOM_EVENT_NAME, $event_props ); + } + + /** + * Get the tracking product name for the Tracking library. + * + * The tracking product name is used by the Tracking as a prefix for the event name. + * + * @return string + */ + public static function get_jetpack_tracking_product() { + return ( new Host() )->is_woa_site() ? 'atomic' : 'jetpack'; + } + + /** + * Mark the Jetpack ToS as read for Atomic Sites. + * + * @param mixed $option_value The value of the Jetpack option. + * @param string $option_name The name of the Jetpack option. + * + * @return bool + */ + public static function mark_jetpack_tos_as_read( $option_value, $option_name ) { + if ( Terms_Of_Service::OPTION_NAME === $option_name ) { + return true; + } + + return $option_value; + } +} diff --git a/projects/packages/masterbar/src/admin-menu/class-domain-only-admin-menu.php b/projects/packages/masterbar/src/admin-menu/class-domain-only-admin-menu.php new file mode 100644 index 0000000000000..9ba1a7932db73 --- /dev/null +++ b/projects/packages/masterbar/src/admin-menu/class-domain-only-admin-menu.php @@ -0,0 +1,68 @@ +email_subscriptions_checker = $email_subscriptions_checker; + + if ( empty( $this->email_subscriptions_checker ) ) { + $this->set_email_subscription_checker( new WPCOM_Email_Subscription_Checker() ); + } + } + + /** + * This setter lets us inject an WPCOM_Email_Subscription_Checker instance. + * + * @param WPCOM_Email_Subscription_Checker $email_subscriptions_checker An WPCOM_Email_Subscription_Checker instance. + * + * @return void + */ + public function set_email_subscription_checker( $email_subscriptions_checker ) { + $this->email_subscriptions_checker = $email_subscriptions_checker; + } + + /** + * Create the desired menu output. + */ + public function reregister_menu_items() { + global $menu, $submenu; + + $menu = array(); // phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited + $submenu = array(); // phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited + + // @phan-suppress-next-line PhanTypeMismatchArgumentProbablyReal -- Core should ideally document null for no-callback arg. TODO add link with Trac issue. + add_menu_page( esc_attr__( 'Manage Domain', 'jetpack-masterbar' ), __( 'Manage Domain', 'jetpack-masterbar' ), 'manage_options', 'https://wordpress.com/domains/manage/' . $this->domain . '/edit/' . $this->domain, null, 'dashicons-admin-settings' ); + + if ( $this->email_subscriptions_checker->has_email() ) { + // @phan-suppress-next-line PhanTypeMismatchArgumentProbablyReal -- Core should ideally document null for no-callback arg. TODO add link with Trac issue. + add_menu_page( esc_attr__( 'Manage Email', 'jetpack-masterbar' ), __( 'Manage Email', 'jetpack-masterbar' ), 'manage_options', 'https://wordpress.com/email/' . $this->domain . '/manage/' . $this->domain, null, 'dashicons-admin-settings' ); + } + // @phan-suppress-next-line PhanTypeMismatchArgumentProbablyReal -- Core should ideally document null for no-callback arg. TODO add link with Trac issue. + add_menu_page( esc_attr__( 'Manage Purchases', 'jetpack-masterbar' ), __( 'Manage Purchases', 'jetpack-masterbar' ), 'manage_options', 'https://wordpress.com/purchases/subscriptions/' . $this->domain, null, 'dashicons-cart' ); + // @phan-suppress-next-line PhanTypeMismatchArgumentProbablyReal -- Core should ideally document null for no-callback arg. TODO add link with Trac issue. + add_menu_page( esc_attr__( 'My Mailboxes', 'jetpack-masterbar' ), __( 'My Mailboxes', 'jetpack-masterbar' ), 'manage_options', 'https://wordpress.com/mailboxes/' . $this->domain, null, 'dashicons-email' ); + } +} diff --git a/projects/packages/masterbar/src/admin-menu/class-jetpack-admin-menu.php b/projects/packages/masterbar/src/admin-menu/class-jetpack-admin-menu.php new file mode 100644 index 0000000000000..2908269555e5d --- /dev/null +++ b/projects/packages/masterbar/src/admin-menu/class-jetpack-admin-menu.php @@ -0,0 +1,334 @@ +add_feedback_menu(); + $this->add_cpt_menus(); + $this->add_wp_admin_menu(); + + ksort( $GLOBALS['menu'] ); + } + + /** + * Get the preferred view for the given screen. + * + * @param string $screen Screen identifier. + * @param bool $fallback_global_preference (Optional) Whether the global preference for all screens should be used + * as fallback if there is no specific preference for the given screen. + * Default: true. + * @return string + */ + public function get_preferred_view( $screen, $fallback_global_preference = true ) { // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable + // Force default views (Calypso) on Jetpack sites since Nav Unification is disabled on WP Admin. + return self::DEFAULT_VIEW; + } + + /** + * Get the Calypso or wp-admin link to CPT page. + * + * @param object $ptype_obj The post type object. + * @return string The link to Calypso if SSO is enabled and the post_type + * supports rest or to WP Admin if SSO is disabled. + */ + public function get_cpt_menu_link( $ptype_obj ) { + + $post_type = $ptype_obj->name; + + if ( ( new Modules() )->is_active( 'sso' ) && $ptype_obj->show_in_rest ) { + return 'https://wordpress.com/types/' . $post_type . '/' . $this->domain; + } else { + return 'edit.php?post_type=' . $post_type; + } + } + + /** + * Adds Posts menu. + */ + public function add_posts_menu() { + $post = get_post_type_object( 'post' ); + // @phan-suppress-next-line PhanTypeMismatchArgumentProbablyReal -- Core should ideally document null for no-callback arg. TODO add link with Trac issue. + add_menu_page( esc_attr( $post->labels->menu_name ), $post->labels->menu_name, $post->cap->edit_posts, 'https://wordpress.com/posts/' . $this->domain, null, 'dashicons-admin-post' ); + } + + /** + * Adds Media menu. + */ + public function add_media_menu() { + // @phan-suppress-next-line PhanTypeMismatchArgumentProbablyReal -- Core should ideally document null for no-callback arg. TODO add link with Trac issue. + add_menu_page( __( 'Media', 'jetpack-masterbar' ), __( 'Media', 'jetpack-masterbar' ), 'upload_files', 'https://wordpress.com/media/' . $this->domain, null, 'dashicons-admin-media' ); + } + + /** + * Adds Page menu. + */ + public function add_page_menu() { + $page = get_post_type_object( 'page' ); + // @phan-suppress-next-line PhanTypeMismatchArgumentProbablyReal -- Core should ideally document null for no-callback arg. TODO add link with Trac issue. + add_menu_page( esc_attr( $page->labels->menu_name ), $page->labels->menu_name, $page->cap->edit_posts, 'https://wordpress.com/pages/' . $this->domain, null, 'dashicons-admin-page' ); + } + + /** + * Adds a custom post type menu. + * + * @param string $post_type Custom post type. + * @param int|null $position Optional. Position where to display the menu item. Default null. + */ + public function add_custom_post_type_menu( $post_type, $position = null ) { + $ptype_obj = get_post_type_object( $post_type ); + if ( empty( $ptype_obj ) ) { + return; + } + + $menu_slug = $this->get_cpt_menu_link( $ptype_obj ); + + // Menu icon. + $menu_icon = 'dashicons-admin-post'; + if ( is_string( $ptype_obj->menu_icon ) ) { + // Special handling for data:image/svg+xml and Dashicons. + if ( str_starts_with( $ptype_obj->menu_icon, 'data:image/svg+xml;base64,' ) || str_starts_with( $ptype_obj->menu_icon, 'dashicons-' ) ) { + $menu_icon = $ptype_obj->menu_icon; + } else { + $menu_icon = esc_url( $ptype_obj->menu_icon ); + } + } + + // @phan-suppress-next-line PhanTypeMismatchArgumentProbablyReal -- Core should ideally document null for no-callback arg. TODO add link with Trac issue. + add_menu_page( esc_attr( $ptype_obj->labels->menu_name ), $ptype_obj->labels->menu_name, $ptype_obj->cap->edit_posts, $menu_slug, null, $menu_icon, $position ); + } + + /** + * Adds Comments menu. + */ + public function add_comments_menu() { + // @phan-suppress-next-line PhanTypeMismatchArgumentProbablyReal -- Core should ideally document null for no-callback arg. TODO add link with Trac issue. + add_menu_page( esc_attr__( 'Comments', 'jetpack-masterbar' ), __( 'Comments', 'jetpack-masterbar' ), 'edit_posts', 'https://wordpress.com/comments/all/' . $this->domain, null, 'dashicons-admin-comments' ); + } + + /** + * Adds Feedback menu. + */ + public function add_feedback_menu() { + $post_type = 'feedback'; + + $ptype_obj = get_post_type_object( $post_type ); + if ( empty( $ptype_obj ) ) { + return; + } + + $slug = 'edit.php?post_type=' . $post_type; + $name = __( 'Feedback', 'jetpack-masterbar' ); + $capability = $ptype_obj->cap->edit_posts; + $icon = $ptype_obj->menu_icon; + $position = 45; // Before Jetpack. + + // @phan-suppress-next-line PhanTypeMismatchArgumentProbablyReal -- Core should ideally document null for no-callback arg. TODO add link with Trac issue. + add_menu_page( esc_attr( $name ), $name, $capability, $slug, null, $icon, $position ); + } + + /** + * Adds CPT menu items + */ + public function add_cpt_menus() { + + $post_type_list = get_post_types( + array( + 'show_in_menu' => true, + '_builtin' => false, + ) + ); + + foreach ( $post_type_list as $post_type ) { + $position = 46; // After Feedback. + $this->add_custom_post_type_menu( $post_type, $position ); + } + } + + /** + * Adds Jetpack menu. + */ + public function add_jetpack_menu() { + parent::add_jetpack_menu(); + + /* translators: Jetpack sidebar menu item. */ + add_submenu_page( 'jetpack', esc_attr__( 'Search', 'jetpack-masterbar' ), __( 'Search', 'jetpack-masterbar' ), 'manage_options', 'jetpack-search', admin_url( 'admin.php?page=jetpack-search' ), 4 ); + + // Place "Scan" submenu after Backup. + $position = 0; + global $submenu; + foreach ( $submenu['jetpack'] as $submenu_item ) { + ++$position; + if ( __( 'Backup', 'jetpack-masterbar' ) === $submenu_item[3] ) { + break; + } + } + // @phan-suppress-next-line PhanTypeMismatchArgumentProbablyReal -- Core should ideally document null for no-callback arg. TODO add link with Trac issue. + add_submenu_page( 'jetpack', esc_attr__( 'Scan', 'jetpack-masterbar' ), __( 'Scan', 'jetpack-masterbar' ), 'manage_options', 'https://wordpress.com/scan/' . $this->domain, null, $position ); + } + + /** + * Adds Appearance menu. + * + * @return string The Customizer URL. + */ + public function add_appearance_menu() { + $themes_url = 'https://wordpress.com/themes/' . $this->domain; + // Customize on Jetpack sites is always done on WP Admin (unsupported by Calypso). + $customize_url = 'customize.php'; + // @phan-suppress-next-line PhanTypeMismatchArgumentProbablyReal -- Core should ideally document null for no-callback arg. TODO add link with Trac issue. + add_menu_page( esc_attr__( 'Appearance', 'jetpack-masterbar' ), __( 'Appearance', 'jetpack-masterbar' ), 'switch_themes', $themes_url, null, 'dashicons-admin-appearance', 60 ); + add_submenu_page( $themes_url, esc_attr__( 'Themes', 'jetpack-masterbar' ), __( 'Themes', 'jetpack-masterbar' ), 'switch_themes', 'https://wordpress.com/themes/' . $this->domain ); + + if ( ! has_action( 'customize_register' ) && wp_is_block_theme() ) { + return $customize_url; + } + + add_submenu_page( $themes_url, esc_attr__( 'Customize', 'jetpack-masterbar' ), __( 'Customize', 'jetpack-masterbar' ), 'customize', $customize_url ); + + return $customize_url; + } + + /** + * Adds Plugins menu. + */ + public function add_plugins_menu() { + // @phan-suppress-next-line PhanTypeMismatchArgumentProbablyReal -- Core should ideally document null for no-callback arg. TODO add link with Trac issue. + add_menu_page( esc_attr__( 'Plugins', 'jetpack-masterbar' ), __( 'Plugins', 'jetpack-masterbar' ), 'activate_plugins', 'https://wordpress.com/plugins/' . $this->domain, null, 'dashicons-admin-plugins', 65 ); + } + + /** + * Adds Users menu. + */ + public function add_users_menu() { + if ( current_user_can( 'list_users' ) ) { + $users_url = 'https://wordpress.com/people/team/' . $this->domain; + // @phan-suppress-next-line PhanTypeMismatchArgumentProbablyReal -- Core should ideally document null for no-callback arg. TODO add link with Trac issue. + add_menu_page( esc_attr__( 'Users', 'jetpack-masterbar' ), __( 'Users', 'jetpack-masterbar' ), 'list_users', $users_url, null, 'dashicons-admin-users', 70 ); + // @phan-suppress-next-line PhanTypeMismatchArgumentProbablyReal -- Core should ideally document null for no-callback arg. TODO add link with Trac issue. + add_submenu_page( $users_url, esc_attr__( 'All Users', 'jetpack-masterbar' ), __( 'All Users', 'jetpack-masterbar' ), 'list_users', $users_url, null, 10 ); + // @phan-suppress-next-line PhanTypeMismatchArgumentProbablyReal -- Core should ideally document null for no-callback arg. TODO add link with Trac issue. + add_submenu_page( $users_url, esc_attr__( 'Add New User', 'jetpack-masterbar' ), __( 'Add New User', 'jetpack-masterbar' ), 'promote_users', 'https://wordpress.com/people/new/' . $this->domain, null, 20 ); + // @phan-suppress-next-line PhanTypeMismatchArgumentProbablyReal -- Core should ideally document null for no-callback arg. TODO add link with Trac issue. + add_submenu_page( $users_url, esc_attr__( 'Subscribers', 'jetpack-masterbar' ), __( 'Subscribers', 'jetpack-masterbar' ), 'list_users', 'https://wordpress.com/subscribers/' . $this->domain, null, 30 ); + // @phan-suppress-next-line PhanTypeMismatchArgumentProbablyReal -- Core should ideally document null for no-callback arg. TODO add link with Trac issue. + add_submenu_page( $users_url, esc_attr__( 'My Profile', 'jetpack-masterbar' ), __( 'My Profile', 'jetpack-masterbar' ), 'read', 'https://wordpress.com/me', null, 40 ); + // @phan-suppress-next-line PhanTypeMismatchArgumentProbablyReal -- Core should ideally document null for no-callback arg. TODO add link with Trac issue. + add_submenu_page( $users_url, esc_attr__( 'Account Settings', 'jetpack-masterbar' ), __( 'Account Settings', 'jetpack-masterbar' ), 'read', 'https://wordpress.com/me/account', null, 50 ); + } else { + // @phan-suppress-next-line PhanTypeMismatchArgumentProbablyReal -- Core should ideally document null for no-callback arg. TODO add link with Trac issue. + add_menu_page( esc_attr__( 'My Profile', 'jetpack-masterbar' ), __( 'Profile', 'jetpack-masterbar' ), 'read', 'https://wordpress.com/me', null, 'dashicons-admin-users', 70 ); + } + } + + /** + * Adds Tools menu. + */ + public function add_tools_menu() { + // @phan-suppress-next-line PhanTypeMismatchArgumentProbablyReal -- Core should ideally document null for no-callback arg. TODO add link with Trac issue. + add_menu_page( esc_attr__( 'Tools', 'jetpack-masterbar' ), __( 'Tools', 'jetpack-masterbar' ), 'publish_posts', 'tools.php', null, 'dashicons-admin-tools', 75 ); + add_submenu_page( 'tools.php', esc_attr__( 'Marketing', 'jetpack-masterbar' ), __( 'Marketing', 'jetpack-masterbar' ), 'publish_posts', 'https://wordpress.com/marketing/tools/' . $this->domain ); + + if ( Blaze::should_initialize() ) { + // @phan-suppress-next-line PhanTypeMismatchArgumentProbablyReal -- Core should ideally document null for no-callback arg. TODO add link with Trac issue. + add_submenu_page( 'tools.php', esc_attr__( 'Advertising', 'jetpack-masterbar' ), __( 'Advertising', 'jetpack-masterbar' ), 'manage_options', 'https://wordpress.com/advertising/' . $this->domain, null, 1 ); + } + + add_submenu_page( 'tools.php', esc_attr__( 'Monetize', 'jetpack-masterbar' ), __( 'Monetize', 'jetpack-masterbar' ), 'manage_options', 'https://wordpress.com/earn/' . $this->domain ); + + // Import/Export on Jetpack sites is always handled on WP Admin. + add_submenu_page( 'tools.php', esc_attr__( 'Import', 'jetpack-masterbar' ), __( 'Import', 'jetpack-masterbar' ), 'import', 'import.php' ); + add_submenu_page( 'tools.php', esc_attr__( 'Export', 'jetpack-masterbar' ), __( 'Export', 'jetpack-masterbar' ), 'export', 'export.php' ); + + // Remove the submenu auto-created by Core. + $this->hide_submenu_page( 'tools.php', 'tools.php' ); + } + + /** + * Adds Settings menu. + */ + public function add_options_menu() { + $slug = 'https://wordpress.com/settings/general/' . $this->domain; + // @phan-suppress-next-line PhanTypeMismatchArgumentProbablyReal -- Core should ideally document null for no-callback arg. TODO add link with Trac issue. + add_menu_page( esc_attr__( 'Settings', 'jetpack-masterbar' ), __( 'Settings', 'jetpack-masterbar' ), 'manage_options', $slug, null, 'dashicons-admin-settings', 80 ); + add_submenu_page( $slug, esc_attr__( 'General', 'jetpack-masterbar' ), __( 'General', 'jetpack-masterbar' ), 'manage_options', $slug ); + add_submenu_page( $slug, esc_attr__( 'Security', 'jetpack-masterbar' ), __( 'Security', 'jetpack-masterbar' ), 'manage_options', 'https://wordpress.com/settings/security/' . $this->domain ); + add_submenu_page( $slug, esc_attr__( 'Performance', 'jetpack-masterbar' ), __( 'Performance', 'jetpack-masterbar' ), 'manage_options', 'https://wordpress.com/settings/performance/' . $this->domain ); + add_submenu_page( $slug, esc_attr__( 'Writing', 'jetpack-masterbar' ), __( 'Writing', 'jetpack-masterbar' ), 'manage_options', 'https://wordpress.com/settings/writing/' . $this->domain ); + add_submenu_page( $slug, esc_attr__( 'Reading', 'jetpack-masterbar' ), __( 'Reading', 'jetpack-masterbar' ), 'manage_options', 'https://wordpress.com/settings/reading/' . $this->domain ); + add_submenu_page( $slug, esc_attr__( 'Discussion', 'jetpack-masterbar' ), __( 'Discussion', 'jetpack-masterbar' ), 'manage_options', 'https://wordpress.com/settings/discussion/' . $this->domain ); + add_submenu_page( $slug, esc_attr__( 'Newsletter', 'jetpack-masterbar' ), __( 'Newsletter', 'jetpack-masterbar' ), 'manage_options', 'https://wordpress.com/settings/newsletter/' . $this->domain ); + + $plan_supports_scan = Jetpack_Plan::supports( 'scan' ); + $products = Jetpack_Plan::get_products(); + $has_scan_product = false; + + if ( is_array( $products ) ) { + foreach ( $products as $product ) { + if ( strpos( $product['product_slug'], 'jetpack_scan' ) === 0 ) { + $has_scan_product = true; + break; + } + } + } + + $has_scan = $plan_supports_scan || $has_scan_product; + $rewind_state = get_transient( 'jetpack_rewind_state' ); + $has_backup = $rewind_state && in_array( $rewind_state->state, array( 'awaiting_credentials', 'provisioning', 'active' ), true ); + if ( $has_scan || $has_backup ) { + add_submenu_page( $slug, esc_attr__( 'Jetpack', 'jetpack-masterbar' ), __( 'Jetpack', 'jetpack-masterbar' ), 'manage_options', 'https://wordpress.com/settings/jetpack/' . $this->domain ); + } + } + + /** + * Adds WP Admin menu. + */ + public function add_wp_admin_menu() { + global $menu; + + // Attempt to get last position. + ksort( $menu ); + end( $menu ); + $position = key( $menu ); + + $this->add_admin_menu_separator( ++$position ); + // @phan-suppress-next-line PhanTypeMismatchArgumentProbablyReal -- Core should ideally document null for no-callback arg. TODO add link with Trac issue. + add_menu_page( __( 'WP Admin', 'jetpack-masterbar' ), __( 'WP Admin', 'jetpack-masterbar' ), 'read', 'index.php', null, 'dashicons-wordpress-alt', $position ); + } +} diff --git a/projects/packages/masterbar/src/admin-menu/class-p2-admin-menu.php b/projects/packages/masterbar/src/admin-menu/class-p2-admin-menu.php new file mode 100644 index 0000000000000..cc1500ec054a3 --- /dev/null +++ b/projects/packages/masterbar/src/admin-menu/class-p2-admin-menu.php @@ -0,0 +1,201 @@ +is_hub = \WPForTeams\Workspace\is_workspace_hub( $current_blog_id ); + $this->is_paid = \WPForTeams\has_p2_plus_plan( \WPForTeams\Workspace\get_hub_blog_id_from_blog_id( $current_blog_id ) ); + } + // Appearance -> AMP. This needs to be called here in the constructor. + // Running it from reregister_menu_items is not early enough. + remove_action( 'admin_menu', 'amp_add_customizer_link' ); + } + + /** + * Create the desired menu output. + */ + public function reregister_menu_items() { + parent::reregister_menu_items(); + + if ( ! $this->is_hub ) { + $this->remove_menus_for_p2_space(); + } else { + $this->remove_menus_for_hub(); + } + + $this->remove_menus_for_all_p2s(); + } + + /** + * Remove menu items that are not applicable for P2 workspace sites. + */ + private function remove_menus_for_p2_space() { + // Non-hub P2s can't have plans at all. + remove_menu_page( $this->upgrades_slug ); + // Jetpack -> Backup. + remove_submenu_page( $this->jetpack_slug, 'https://wordpress.com/backup/' . $this->domain ); + // Appearance -> Themes. + remove_submenu_page( $this->appearance_slug, 'https://wordpress.com/themes/' . $this->domain ); + // Appearance -> Additional CSS. + $customize_custom_css_url = add_query_arg( + array( 'autofocus' => array( 'section' => 'css_nudge' ) ), + 'https://wordpress.com/customize/' . $this->domain + ); + remove_submenu_page( $this->appearance_slug, $customize_custom_css_url ); + + // Tools + remove_submenu_page( $this->tools_slug, 'https://wordpress.com/marketing/tools/' . $this->domain ); + remove_submenu_page( $this->tools_slug, 'https://wordpress.com/earn/' . $this->domain ); + } + + /** + * Remove menu items that are not applicable for P2 hubs. + */ + private function remove_menus_for_hub() { + // Hubs can have plans, but not domain and email products. + remove_submenu_page( $this->upgrades_slug, 'https://wordpress.com/domains/manage/' . $this->domain ); + remove_submenu_page( $this->upgrades_slug, 'https://wordpress.com/email/' . $this->domain ); + // Stats. + remove_menu_page( 'https://wordpress.com/stats/day/' . $this->domain ); + // Hide posts. + remove_menu_page( 'edit.php' ); + // Hide pages. + remove_menu_page( 'edit.php?post_type=page' ); + // Hide media. + remove_menu_page( 'https://wordpress.com/media/' . $this->domain ); + // Hide comments. + remove_menu_page( 'https://wordpress.com/comments/all/' . $this->domain ); + // Hide appearance. + remove_menu_page( $this->appearance_slug ); + // Tools. + remove_submenu_page( $this->tools_slug, 'https://wordpress.com/marketing/tools/' . $this->domain ); + remove_submenu_page( $this->tools_slug, 'https://wordpress.com/earn/' . $this->domain ); + remove_submenu_page( $this->tools_slug, 'https://wordpress.com/import/' . $this->domain ); + remove_submenu_page( $this->tools_slug, 'https://wordpress.com/export/' . $this->domain ); + // @phan-suppress-next-line PhanTypeMismatchArgumentProbablyReal -- Core should ideally document null for no-callback arg. TODO add link with Trac issue. + add_submenu_page( $this->tools_slug, __( 'Integrations', 'jetpack-masterbar' ), __( 'Integrations', 'jetpack-masterbar' ), 'manage_options', 'https://wordpress.com/marketing/connections/' . $this->domain, null, 0 ); + // Hide settings. + remove_submenu_page( 'options-general.php', 'options-reading.php' ); + remove_submenu_page( 'options-general.php', 'options-writing.php' ); + remove_submenu_page( 'options-general.php', 'options-discussion.php' ); + } + + /** + * Remove menu items that are not applicable for all P2s. + */ + private function remove_menus_for_all_p2s() { + // Remove Jetpack menu item. + remove_menu_page( $this->jetpack_slug ); + + // The following menu items are hidden for both hubs and P2 sites. + remove_menu_page( 'link-manager.php' ); + remove_menu_page( 'feedback' ); + remove_menu_page( $this->plugins_slug ); + remove_menu_page( 'https://wordpress.com/plugins/' . $this->domain ); + remove_menu_page( 'https://wordpress.com/inbox/' . $this->domain ); + + remove_submenu_page( 'https://wordpress.com/settings/general/' . $this->domain, 'sharing' ); + remove_submenu_page( 'https://wordpress.com/settings/general/' . $this->domain, 'polls&action=options' ); + remove_submenu_page( 'https://wordpress.com/settings/general/' . $this->domain, 'ratings&action=options' ); + remove_submenu_page( + 'options-general.php', + 'https://wordpress.com/hosting-config/' . $this->domain + ); + remove_submenu_page( + 'https://wordpress.com/settings/general/' . $this->domain, + 'https://wordpress.com/marketing/sharing-buttons/' . $this->domain + ); + + /** This action is documented in `wp-content/plugins/p2-editor/classes/p2-editor-admin.php` */ + if ( apply_filters( 'p2tenberg_admin_patterns', apply_filters( 'p2editor_admin_patterns', true ) ) !== true ) { + remove_menu_page( 'edit.php?post_type=p2_pattern' ); + } + remove_submenu_page( + 'edit.php?post_type=p2_pattern', + 'edit-tags.php?taxonomy=post_tag&post_type=p2_pattern' + ); + + // Hide performance settings. + remove_submenu_page( 'options-general.php', 'https://wordpress.com/settings/performance/' . $this->domain ); + } + + /** + * Override, don't add the woocommerce installation menu on any p2s. + * + * @param array|null $current_plan The site's plan. + */ + public function add_woocommerce_installation_menu( $current_plan = null ) {} // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable +} diff --git a/projects/packages/masterbar/src/admin-menu/class-wpcom-admin-menu.php b/projects/packages/masterbar/src/admin-menu/class-wpcom-admin-menu.php new file mode 100644 index 0000000000000..ca5396e2f7a0c --- /dev/null +++ b/projects/packages/masterbar/src/admin-menu/class-wpcom-admin-menu.php @@ -0,0 +1,536 @@ +add_my_home_menu(); + $this->add_my_mailboxes_menu(); + $this->remove_gutenberg_menu(); + + // Not needed outside of wp-admin. + if ( ! $this->is_api_request ) { + $this->add_browse_sites_link(); + $this->add_site_card_menu(); + $this->add_new_site_link(); + } + + $this->add_woocommerce_installation_menu( $this->get_current_plan() ); + + ksort( $GLOBALS['menu'] ); + } + + /** + * Get the preferred view for the given screen. + * + * @param string $screen Screen identifier. + * @param bool $fallback_global_preference (Optional) Whether the global preference for all screens should be used + * as fallback if there is no specific preference for the given screen. + * Default: true. + * @return string + */ + public function get_preferred_view( $screen, $fallback_global_preference = true ) { + // When no preferred view has been set for Themes, keep the previous behavior that forced the default view + // regardless of the global preference. + if ( $fallback_global_preference && 'themes.php' === $screen ) { + $preferred_view = parent::get_preferred_view( $screen, false ); + if ( self::UNKNOWN_VIEW === $preferred_view ) { + return self::DEFAULT_VIEW; + } + return $preferred_view; + } + + // Plugins on Simple sites are always managed on Calypso. + if ( 'plugins.php' === $screen ) { + return self::DEFAULT_VIEW; + } + + return parent::get_preferred_view( $screen, $fallback_global_preference ); + } + + /** + * Retrieve the number of blogs that the current user has. + * + * @return int + */ + public function get_current_user_blog_count() { + if ( function_exists( '\get_blog_count_for_user' ) ) { + return \get_blog_count_for_user( get_current_user_id() ); + } + + $blogs = get_blogs_of_user( get_current_user_id() ); + return is_countable( $blogs ) ? count( $blogs ) : 0; + } + + /** + * Adds the site switcher link if user has more than one site. + */ + public function add_browse_sites_link() { + if ( $this->get_current_user_blog_count() < 2 ) { + return; + } + + // Add the menu item. + // @phan-suppress-next-line PhanTypeMismatchArgumentProbablyReal -- Core should ideally document null for no-callback arg. TODO add link with Trac issue. + add_menu_page( __( 'site-switcher', 'jetpack-masterbar' ), __( 'Browse sites', 'jetpack-masterbar' ), 'read', 'https://wordpress.com/sites', null, 'dashicons-arrow-left-alt2', 0 ); + add_filter( 'add_menu_classes', array( $this, 'set_browse_sites_link_class' ) ); + } + + /** + * Adds a custom element class for Site Switcher menu item. + * + * @param array $menu Associative array of administration menu items. + * @return array + */ + public function set_browse_sites_link_class( array $menu ) { + foreach ( $menu as $key => $menu_item ) { + if ( 'site-switcher' !== $menu_item[3] ) { + continue; + } + + $menu[ $key ][4] = add_cssclass( 'site-switcher', $menu_item[4] ); + break; + } + + return $menu; + } + + /** + * Adds a link to the menu to create a new site. + */ + public function add_new_site_link() { + if ( $this->get_current_user_blog_count() > 1 ) { + return; + } + + $this->add_admin_menu_separator(); + // @phan-suppress-next-line PhanTypeMismatchArgumentProbablyReal -- Core should ideally document null for no-callback arg. TODO add link with Trac issue. + add_menu_page( __( 'Add New Site', 'jetpack-masterbar' ), __( 'Add New Site', 'jetpack-masterbar' ), 'read', 'https://wordpress.com/start?ref=calypso-sidebar', null, 'dashicons-plus-alt' ); + } + + /** + * Adds site card component. + */ + public function add_site_card_menu() { + $default = plugins_url( 'globe-icon.svg', __FILE__ ); + $icon = get_site_icon_url( 32, $default ); + $blog_name = get_option( 'blogname' ) !== '' ? get_option( 'blogname' ) : $this->domain; + $status = new Status(); + $is_private_site = $status->is_private_site(); + $is_coming_soon = $status->is_coming_soon(); + + if ( $default === $icon && blavatar_exists( $this->domain ) ) { + $icon = blavatar_url( $this->domain, 'img', 32 ); + } + + $badge = ''; + if ( $is_private_site || $is_coming_soon ) { + $badge .= sprintf( + '%s', + $is_coming_soon ? esc_html__( 'Coming Soon', 'jetpack-masterbar' ) : esc_html__( 'Private', 'jetpack-masterbar' ) + ); + } + + // @todo is_simple_site_redirect accepts the blog id. How is this even working with the domain? + if ( function_exists( 'is_simple_site_redirect' ) && is_simple_site_redirect( $this->domain ) ) { + $badge .= '' . esc_html__( 'Redirect', 'jetpack-masterbar' ) . ''; + } + + if ( ! empty( get_option( 'options' )['is_domain_only'] ) ) { + $badge .= '' . esc_html__( 'Domain', 'jetpack-masterbar' ) . ''; + } + + $site_card = ' +
+
%1$s
+
%2$s
+ %3$s +
'; + + $site_card = sprintf( + $site_card, + $blog_name, + $this->domain, + $badge + ); + + // @phan-suppress-next-line PhanTypeMismatchArgumentProbablyReal -- Core should ideally document null for no-callback arg. TODO add link with Trac issue. + add_menu_page( 'site-card', $site_card, 'read', get_home_url(), null, $icon, 1 ); + add_filter( 'add_menu_classes', array( $this, 'set_site_card_menu_class' ) ); + } + + /** + * Adds a custom element class and id for Site Card's menu item. + * + * @param array $menu Associative array of administration menu items. + * @return array + */ + public function set_site_card_menu_class( array $menu ) { + foreach ( $menu as $key => $menu_item ) { + if ( 'site-card' !== $menu_item[3] ) { + continue; + } + + $classes = ' toplevel_page_site-card'; + if ( blavatar_exists( $this->domain ) ) { + $classes .= ' has-site-icon'; + } + + $menu[ $key ][4] = $menu_item[4] . $classes; + $menu[ $key ][5] = 'toplevel_page_site_card'; + break; + } + + return $menu; + } + + /** + * Returns the first available upsell nudge. + * + * @return array + */ + public function get_upsell_nudge() { + require_lib( 'jetpack-jitm/jitm-engine' ); + $jitm_engine = new JITM\Engine(); + + $message_path = 'calypso:sites:sidebar_notice'; + $current_user = wp_get_current_user(); + $user_id = $current_user->ID; + $user_roles = implode( ',', $current_user->roles ); + $query_string = array( + 'message_path' => $message_path, + ); + + // Get the top message only. + $message = $jitm_engine->get_top_messages( $message_path, $user_id, $user_roles, $query_string ); + + if ( isset( $message[0] ) ) { + $message = $message[0]; + return array( + 'content' => $message->content['message'], + 'cta' => $message->CTA['message'], // phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase + 'link' => $message->CTA['link'], // phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase + 'tracks_impression_event_name' => $message->tracks['display']['name'] ?? null, + 'tracks_impression_cta_name' => $message->tracks['display']['props']['cta_name'] ?? null, + 'tracks_click_event_name' => $message->tracks['click']['name'] ?? null, + 'tracks_click_cta_name' => $message->tracks['click']['props']['cta_name'] ?? null, + 'dismissible' => $message->is_dismissible, + 'feature_class' => $message->feature_class, + 'id' => $message->id, + ); + } + } + + /** + * Adds Stats menu. + */ + public function add_stats_menu() { + $menu_title = __( 'Stats', 'jetpack-masterbar' ); + + if ( ! $this->is_api_request ) { + $menu_title .= sprintf( + '%2$s', + esc_url( site_url( 'wp-includes/charts/admin-bar-hours-scale-2x.php?masterbar=1&s=' . get_current_blog_id() ) ), + esc_attr__( 'Hourly views', 'jetpack-masterbar' ) + ); + } + + // @phan-suppress-next-line PhanTypeMismatchArgumentProbablyReal -- Core should ideally document null for no-callback arg. TODO add link with Trac issue. + add_menu_page( __( 'Stats', 'jetpack-masterbar' ), $menu_title, 'read', 'https://wordpress.com/stats/day/' . $this->domain, null, 'dashicons-chart-bar', 3 ); + } + + /** + * Gets the current plan and stores it in $this->current_plan so the database is only called once per request. + * + * @return array + */ + private function get_current_plan() { + if ( empty( $this->current_plan ) && class_exists( 'WPCOM_Store_API' ) ) { + $this->current_plan = \WPCOM_Store_API::get_current_plan( get_current_blog_id() ); + } + return $this->current_plan; + } + + /** + * Adds Upgrades menu. + * + * @param string $plan The current WPCOM plan of the blog. + */ + public function add_upgrades_menu( $plan = null ) { + $current_plan = $this->get_current_plan(); + if ( ! empty( $current_plan['product_name_short'] ) ) { + $plan = $current_plan['product_name_short']; + } + + parent::add_upgrades_menu( $plan ); + + $last_upgrade_submenu_position = $this->get_submenu_item_count( 'paid-upgrades.php' ); + + // @phan-suppress-next-line PhanTypeMismatchArgumentProbablyReal -- Core should ideally document null for no-callback arg. TODO add link with Trac issue. + add_submenu_page( 'paid-upgrades.php', __( 'Domains', 'jetpack-masterbar' ), __( 'Domains', 'jetpack-masterbar' ), 'manage_options', 'https://wordpress.com/domains/manage/' . $this->domain, null, $last_upgrade_submenu_position - 1 ); + + /** This filter is already documented in modules/masterbar/admin-menu/class-atomic-admin-menu.php */ + if ( apply_filters( 'jetpack_show_wpcom_upgrades_email_menu', false ) ) { + // @phan-suppress-next-line PhanTypeMismatchArgumentProbablyReal -- Core should ideally document null for no-callback arg. TODO add link with Trac issue. + add_submenu_page( 'paid-upgrades.php', __( 'Emails', 'jetpack-masterbar' ), __( 'Emails', 'jetpack-masterbar' ), 'manage_options', 'https://wordpress.com/email/' . $this->domain, null, $last_upgrade_submenu_position ); + } + + if ( defined( 'WPCOM_ENABLE_ADD_ONS_MENU_ITEM' ) && WPCOM_ENABLE_ADD_ONS_MENU_ITEM ) { + // @phan-suppress-next-line PhanTypeMismatchArgumentProbablyReal -- Core should ideally document null for no-callback arg. TODO add link with Trac issue. + add_submenu_page( 'paid-upgrades.php', __( 'Add-Ons', 'jetpack-masterbar' ), __( 'Add-Ons', 'jetpack-masterbar' ), 'manage_options', 'https://wordpress.com/add-ons/' . $this->domain, null, 1 ); + } + } + + /** + * Adds Appearance menu. + * + * @return string The Customizer URL. + */ + public function add_appearance_menu() { + $customize_url = parent::add_appearance_menu(); + + $this->hide_submenu_page( 'themes.php', 'theme-editor.php' ); + + $user_can_customize = current_user_can( 'customize' ); + + if ( wp_is_block_theme() ) { + add_filter( 'safecss_is_freetrial', '__return_false', PHP_INT_MAX ); + if ( class_exists( 'Jetpack_Custom_CSS' ) && empty( Jetpack_Custom_CSS::get_css() ) ) { + $user_can_customize = false; + } + remove_filter( 'safecss_is_freetrial', '__return_false', PHP_INT_MAX ); + } + + if ( $user_can_customize ) { + $customize_custom_css_url = add_query_arg( array( 'autofocus' => array( 'section' => 'jetpack_custom_css' ) ), $customize_url ); + // @phan-suppress-next-line PhanTypeMismatchArgumentProbablyReal -- Core should ideally document null for no-callback arg. TODO add link with Trac issue. + add_submenu_page( 'themes.php', esc_attr__( 'Additional CSS', 'jetpack-masterbar' ), __( 'Additional CSS', 'jetpack-masterbar' ), 'customize', esc_url( $customize_custom_css_url ), null, 20 ); + } + + return $customize_url; + } + + /** + * Adds Users menu. + */ + public function add_users_menu() { + $submenus_to_update = array( + 'grofiles-editor' => 'https://wordpress.com/me', + 'grofiles-user-settings' => 'https://wordpress.com/me/account', + ); + + if ( self::DEFAULT_VIEW === $this->get_preferred_view( 'users.php' ) ) { + $submenus_to_update['users.php'] = 'https://wordpress.com/people/team/' . $this->domain; + } + + $slug = current_user_can( 'list_users' ) ? 'users.php' : 'profile.php'; + $this->update_submenus( $slug, $submenus_to_update ); + // @phan-suppress-next-line PhanTypeMismatchArgumentProbablyReal -- Core should ideally document null for no-callback arg. TODO add link with Trac issue. + add_submenu_page( 'users.php', esc_attr__( 'Add New User', 'jetpack-masterbar' ), __( 'Add New User', 'jetpack-masterbar' ), 'promote_users', 'https://wordpress.com/people/new/' . $this->domain, null, 1 ); + // @phan-suppress-next-line PhanTypeMismatchArgumentProbablyReal -- Core should ideally document null for no-callback arg. TODO add link with Trac issue. + add_submenu_page( 'users.php', esc_attr__( 'Subscribers', 'jetpack-masterbar' ), __( 'Subscribers', 'jetpack-masterbar' ), 'list_users', 'https://wordpress.com/subscribers/' . $this->domain, null, 3 ); + } + + /** + * Adds Settings menu. + */ + public function add_options_menu() { + parent::add_options_menu(); + + // @phan-suppress-next-line PhanTypeMismatchArgumentProbablyReal -- Core should ideally document null for no-callback arg. TODO add link with Trac issue. + add_submenu_page( 'options-general.php', esc_attr__( 'Hosting Configuration', 'jetpack-masterbar' ), __( 'Hosting Configuration', 'jetpack-masterbar' ), 'manage_options', 'https://wordpress.com/hosting-config/' . $this->domain, null, 10 ); + } + + /** + * Adds My Home menu. + */ + public function add_my_home_menu() { + $this->update_menu( 'index.php', 'https://wordpress.com/home/' . $this->domain, __( 'My Home', 'jetpack-masterbar' ), 'read', 'dashicons-admin-home' ); + } + + /** + * Also remove the Gutenberg plugin menu. + */ + public function remove_gutenberg_menu() { + // Always remove the Gutenberg menu. + remove_menu_page( 'gutenberg' ); + } + + /** + * Whether to use wp-admin pages rather than Calypso. + * + * @return bool + */ + public function should_link_to_wp_admin() { + $result = false; // Calypso. + + $user_attribute = get_user_attribute( get_current_user_id(), 'calypso_preferences' ); + if ( ! empty( $user_attribute['linkDestination'] ) ) { + $result = $user_attribute['linkDestination']; + } + + return $result; + } + + /** + * Adds Plugins menu. + */ + public function add_plugins_menu() { + // TODO: Remove wpcom_menu (/wp-content/admin-plugins/wpcom-misc.php). + $count = ''; + if ( ! is_multisite() && current_user_can( 'update_plugins' ) ) { + $update_data = wp_get_update_data(); + $count = sprintf( + '%s', + $update_data['counts']['plugins'], + number_format_i18n( $update_data['counts']['plugins'] ) + ); + } + + add_menu_page( + esc_attr__( 'Plugins', 'jetpack-masterbar' ), + sprintf( + /* translators: %s: Number of pending plugin updates. */ + __( 'Plugins %s', 'jetpack-masterbar' ), + $count + ), + 'activate_plugins', + 'plugins.php', + null, // @phan-suppress-current-line PhanTypeMismatchArgumentProbablyReal -- Core should ideally document null for no-callback arg. TODO add link with Trac issue. + 'dashicons-admin-plugins', + 65 + ); + + parent::add_plugins_menu(); + } + + /** + * Saves the sidebar state ( expanded / collapsed ) via an ajax request. + * + * @return never + */ + public function ajax_sidebar_state() { + $expanded = isset( $_REQUEST['expanded'] ) ? filter_var( wp_unslash( $_REQUEST['expanded'] ), FILTER_VALIDATE_BOOLEAN ) : false; // phpcs:ignore WordPress.Security.NonceVerification.Recommended + $user_id = get_current_user_id(); + $preferences = get_user_attribute( $user_id, 'calypso_preferences' ); + if ( empty( $preferences ) ) { + $preferences = array(); + } + + $value = array_merge( (array) $preferences, array( 'sidebarCollapsed' => ! $expanded ) ); + $value = array_filter( + $value, + function ( $preference ) { + return null !== $preference; + } + ); + + update_user_attribute( $user_id, 'calypso_preferences', $value ); + + die(); + } + + /** + * Handle ajax requests to dismiss a just-in-time-message + */ + public function wp_ajax_jitm_dismiss() { + check_ajax_referer( 'jitm_dismiss' ); + require_lib( 'jetpack-jitm/jitm-engine' ); + if ( isset( $_REQUEST['id'] ) && isset( $_REQUEST['feature_class'] ) ) { + JITM\Engine::dismiss( sanitize_text_field( wp_unslash( $_REQUEST['id'] ) ), sanitize_text_field( wp_unslash( $_REQUEST['feature_class'] ) ) ); + } + wp_die(); + } + + /** + * Syncs the sidebar collapsed state from Calypso Preferences. + */ + public function sync_sidebar_collapsed_state() { + $calypso_preferences = get_user_attribute( get_current_user_id(), 'calypso_preferences' ); + + $sidebar_collapsed = $calypso_preferences['sidebarCollapsed'] ?? false; + + // Read the current stored setting and convert it to boolean in order to be able to compare the values later. + $current_sidebar_collapsed_setting = 'f' === get_user_setting( 'mfold' ); + + // Only set the setting if the value differs, as `set_user_setting` always updates at least the timestamp + // which leads to unnecessary user meta cache purging on all wp-admin screen requests. + if ( $current_sidebar_collapsed_setting !== $sidebar_collapsed ) { + set_user_setting( 'mfold', $sidebar_collapsed ? 'f' : 'o' ); + } + } + + /** + * Removes unwanted submenu items. + * + * These submenus are added across wp-content and should be removed together with these function calls. + */ + public function remove_submenus() { + global $_registered_pages; + + remove_submenu_page( 'index.php', 'akismet-stats' ); + remove_submenu_page( 'index.php', 'my-comments' ); + remove_submenu_page( 'index.php', 'stats' ); + remove_submenu_page( 'index.php', 'subscriptions' ); + + /* @see https://github.com/Automattic/wp-calypso/issues/49210 */ + remove_submenu_page( 'index.php', 'my-blogs' ); + $_registered_pages['admin_page_my-blogs'] = true; // phpcs:ignore + + remove_submenu_page( 'paid-upgrades.php', 'premium-themes' ); + remove_submenu_page( 'paid-upgrades.php', 'domains' ); + remove_submenu_page( 'paid-upgrades.php', 'my-upgrades' ); + remove_submenu_page( 'paid-upgrades.php', 'billing-history' ); + + remove_submenu_page( 'themes.php', 'customize.php?autofocus[panel]=amp_panel&return=' . rawurlencode( admin_url() ) ); + + remove_submenu_page( 'users.php', 'wpcom-invite-users' ); // Wpcom_Invite_Users::action_admin_menu. + + remove_submenu_page( 'options-general.php', 'adcontrol' ); + + // Remove menu item but continue allowing access. + foreach ( array( 'openidserver', 'webhooks' ) as $page_slug ) { + remove_submenu_page( 'options-general.php', $page_slug ); + $_registered_pages[ 'admin_page_' . $page_slug ] = true; // phpcs:ignore + } + } +} diff --git a/projects/packages/masterbar/src/admin-menu/class-wpcom-email-subscription-checker.php b/projects/packages/masterbar/src/admin-menu/class-wpcom-email-subscription-checker.php new file mode 100644 index 0000000000000..e73cbc944088b --- /dev/null +++ b/projects/packages/masterbar/src/admin-menu/class-wpcom-email-subscription-checker.php @@ -0,0 +1,27 @@ + true, + 'dashicons-admin-collapse' => true, + 'dashicons-admin-comments' => true, + 'dashicons-admin-customizer' => true, + 'dashicons-admin-generic' => true, + 'dashicons-admin-home' => true, + 'dashicons-admin-links' => true, + 'dashicons-admin-media' => true, + 'dashicons-admin-multisite' => true, + 'dashicons-admin-network' => true, + 'dashicons-admin-page' => true, + 'dashicons-admin-plugins' => true, + 'dashicons-admin-post' => true, + 'dashicons-admin-settings' => true, + 'dashicons-admin-site-alt' => true, + 'dashicons-admin-site-alt2' => true, + 'dashicons-admin-site-alt3' => true, + 'dashicons-admin-site' => true, + 'dashicons-admin-tools' => true, + 'dashicons-admin-users' => true, + 'dashicons-airplane' => true, + 'dashicons-album' => true, + 'dashicons-align-center' => true, + 'dashicons-align-full-width' => true, + 'dashicons-align-left' => true, + 'dashicons-align-none' => true, + 'dashicons-align-pull-left' => true, + 'dashicons-align-pull-right' => true, + 'dashicons-align-right' => true, + 'dashicons-align-wide' => true, + 'dashicons-amazon' => true, + 'dashicons-analytics' => true, + 'dashicons-archive' => true, + 'dashicons-arrow-down-alt' => true, + 'dashicons-arrow-down-alt2' => true, + 'dashicons-arrow-down' => true, + 'dashicons-arrow-left-alt' => true, + 'dashicons-arrow-left-alt2' => true, + 'dashicons-arrow-left' => true, + 'dashicons-arrow-right-alt' => true, + 'dashicons-arrow-right-alt2' => true, + 'dashicons-arrow-right' => true, + 'dashicons-arrow-up-alt' => true, + 'dashicons-arrow-up-alt2' => true, + 'dashicons-arrow-up-duplicate' => true, + 'dashicons-arrow-up' => true, + 'dashicons-art' => true, + 'dashicons-awards' => true, + 'dashicons-backup' => true, + 'dashicons-bank' => true, + 'dashicons-beer' => true, + 'dashicons-bell' => true, + 'dashicons-block-default' => true, + 'dashicons-book-alt' => true, + 'dashicons-book' => true, + 'dashicons-buddicons-activity' => true, + 'dashicons-buddicons-bbpress-logo' => true, + 'dashicons-buddicons-buddypress-logo' => true, + 'dashicons-buddicons-community' => true, + 'dashicons-buddicons-forums' => true, + 'dashicons-buddicons-friends' => true, + 'dashicons-buddicons-groups' => true, + 'dashicons-buddicons-pm' => true, + 'dashicons-buddicons-replies' => true, + 'dashicons-buddicons-topics' => true, + 'dashicons-buddicons-tracking' => true, + 'dashicons-building' => true, + 'dashicons-businessman' => true, + 'dashicons-businessperson' => true, + 'dashicons-businesswoman' => true, + 'dashicons-button' => true, + 'dashicons-calculator' => true, + 'dashicons-calendar-alt' => true, + 'dashicons-calendar' => true, + 'dashicons-camera-alt' => true, + 'dashicons-camera' => true, + 'dashicons-car' => true, + 'dashicons-carrot' => true, + 'dashicons-cart' => true, + 'dashicons-category' => true, + 'dashicons-chart-area' => true, + 'dashicons-chart-bar' => true, + 'dashicons-chart-line' => true, + 'dashicons-chart-pie' => true, + 'dashicons-clipboard' => true, + 'dashicons-clock' => true, + 'dashicons-cloud-saved' => true, + 'dashicons-cloud-upload' => true, + 'dashicons-cloud' => true, + 'dashicons-code-standards' => true, + 'dashicons-coffee' => true, + 'dashicons-color-picker' => true, + 'dashicons-columns' => true, + 'dashicons-controls-back' => true, + 'dashicons-controls-forward' => true, + 'dashicons-controls-pause' => true, + 'dashicons-controls-play' => true, + 'dashicons-controls-repeat' => true, + 'dashicons-controls-skipback' => true, + 'dashicons-controls-skipforward' => true, + 'dashicons-controls-volumeoff' => true, + 'dashicons-controls-volumeon' => true, + 'dashicons-cover-image' => true, + 'dashicons-dashboard' => true, + 'dashicons-database-add' => true, + 'dashicons-database-export' => true, + 'dashicons-database-import' => true, + 'dashicons-database-remove' => true, + 'dashicons-database-view' => true, + 'dashicons-database' => true, + 'dashicons-desktop' => true, + 'dashicons-dismiss' => true, + 'dashicons-download' => true, + 'dashicons-drumstick' => true, + 'dashicons-edit-large' => true, + 'dashicons-edit-page' => true, + 'dashicons-edit' => true, + 'dashicons-editor-aligncenter' => true, + 'dashicons-editor-alignleft' => true, + 'dashicons-editor-alignright' => true, + 'dashicons-editor-bold' => true, + 'dashicons-editor-break' => true, + 'dashicons-editor-code-duplicate' => true, + 'dashicons-editor-code' => true, + 'dashicons-editor-contract' => true, + 'dashicons-editor-customchar' => true, + 'dashicons-editor-expand' => true, + 'dashicons-editor-help' => true, + 'dashicons-editor-indent' => true, + 'dashicons-editor-insertmore' => true, + 'dashicons-editor-italic' => true, + 'dashicons-editor-justify' => true, + 'dashicons-editor-kitchensink' => true, + 'dashicons-editor-ltr' => true, + 'dashicons-editor-ol-rtl' => true, + 'dashicons-editor-ol' => true, + 'dashicons-editor-outdent' => true, + 'dashicons-editor-paragraph' => true, + 'dashicons-editor-paste-text' => true, + 'dashicons-editor-paste-word' => true, + 'dashicons-editor-quote' => true, + 'dashicons-editor-removeformatting' => true, + 'dashicons-editor-rtl' => true, + 'dashicons-editor-spellcheck' => true, + 'dashicons-editor-strikethrough' => true, + 'dashicons-editor-table' => true, + 'dashicons-editor-textcolor' => true, + 'dashicons-editor-ul' => true, + 'dashicons-editor-underline' => true, + 'dashicons-editor-unlink' => true, + 'dashicons-editor-video' => true, + 'dashicons-ellipsis' => true, + 'dashicons-email-alt' => true, + 'dashicons-email-alt2' => true, + 'dashicons-email' => true, + 'dashicons-embed-audio' => true, + 'dashicons-embed-generic' => true, + 'dashicons-embed-photo' => true, + 'dashicons-embed-post' => true, + 'dashicons-embed-video' => true, + 'dashicons-excerpt-view' => true, + 'dashicons-exit' => true, + 'dashicons-external' => true, + 'dashicons-facebook-alt' => true, + 'dashicons-facebook' => true, + 'dashicons-feedback' => true, + 'dashicons-filter' => true, + 'dashicons-flag' => true, + 'dashicons-food' => true, + 'dashicons-format-aside' => true, + 'dashicons-format-audio' => true, + 'dashicons-format-chat' => true, + 'dashicons-format-gallery' => true, + 'dashicons-format-image' => true, + 'dashicons-format-quote' => true, + 'dashicons-format-status' => true, + 'dashicons-format-video' => true, + 'dashicons-forms' => true, + 'dashicons-fullscreen-alt' => true, + 'dashicons-fullscreen-exit-alt' => true, + 'dashicons-games' => true, + 'dashicons-google' => true, + 'dashicons-googleplus' => true, + 'dashicons-grid-view' => true, + 'dashicons-groups' => true, + 'dashicons-hammer' => true, + 'dashicons-heading' => true, + 'dashicons-heart' => true, + 'dashicons-hidden' => true, + 'dashicons-hourglass' => true, + 'dashicons-html' => true, + 'dashicons-id-alt' => true, + 'dashicons-id' => true, + 'dashicons-image-crop' => true, + 'dashicons-image-filter' => true, + 'dashicons-image-flip-horizontal' => true, + 'dashicons-image-flip-vertical' => true, + 'dashicons-image-rotate-left' => true, + 'dashicons-image-rotate-right' => true, + 'dashicons-image-rotate' => true, + 'dashicons-images-alt' => true, + 'dashicons-images-alt2' => true, + 'dashicons-index-card' => true, + 'dashicons-info-outline' => true, + 'dashicons-info' => true, + 'dashicons-insert-after' => true, + 'dashicons-insert-before' => true, + 'dashicons-insert' => true, + 'dashicons-instagram' => true, + 'dashicons-laptop' => true, + 'dashicons-layout' => true, + 'dashicons-leftright' => true, + 'dashicons-lightbulb' => true, + 'dashicons-linkedin' => true, + 'dashicons-list-view' => true, + 'dashicons-location-alt' => true, + 'dashicons-location' => true, + 'dashicons-lock-duplicate' => true, + 'dashicons-lock' => true, + 'dashicons-marker' => true, + 'dashicons-media-archive' => true, + 'dashicons-media-audio' => true, + 'dashicons-media-code' => true, + 'dashicons-media-default' => true, + 'dashicons-media-document' => true, + 'dashicons-media-interactive' => true, + 'dashicons-media-spreadsheet' => true, + 'dashicons-media-text' => true, + 'dashicons-media-video' => true, + 'dashicons-megaphone' => true, + 'dashicons-menu-alt' => true, + 'dashicons-menu-alt2' => true, + 'dashicons-menu-alt3' => true, + 'dashicons-menu' => true, + 'dashicons-microphone' => true, + 'dashicons-migrate' => true, + 'dashicons-minus' => true, + 'dashicons-money-alt' => true, + 'dashicons-money' => true, + 'dashicons-move' => true, + 'dashicons-nametag' => true, + 'dashicons-networking' => true, + 'dashicons-no-alt' => true, + 'dashicons-no' => true, + 'dashicons-open-folder' => true, + 'dashicons-palmtree' => true, + 'dashicons-paperclip' => true, + 'dashicons-pdf' => true, + 'dashicons-performance' => true, + 'dashicons-pets' => true, + 'dashicons-phone' => true, + 'dashicons-pinterest' => true, + 'dashicons-playlist-audio' => true, + 'dashicons-playlist-video' => true, + 'dashicons-plugins-checked' => true, + 'dashicons-plus-alt' => true, + 'dashicons-plus-alt2' => true, + 'dashicons-plus' => true, + 'dashicons-podio' => true, + 'dashicons-portfolio' => true, + 'dashicons-post-status' => true, + 'dashicons-pressthis' => true, + 'dashicons-printer' => true, + 'dashicons-privacy' => true, + 'dashicons-products' => true, + 'dashicons-randomize' => true, + 'dashicons-reddit' => true, + 'dashicons-redo' => true, + 'dashicons-remove' => true, + 'dashicons-rest-api' => true, + 'dashicons-rss' => true, + 'dashicons-saved' => true, + 'dashicons-schedule' => true, + 'dashicons-screenoptions' => true, + 'dashicons-search' => true, + 'dashicons-share-alt' => true, + 'dashicons-share-alt2' => true, + 'dashicons-share' => true, + 'dashicons-shield-alt' => true, + 'dashicons-shield' => true, + 'dashicons-shortcode' => true, + 'dashicons-slides' => true, + 'dashicons-smartphone' => true, + 'dashicons-smiley' => true, + 'dashicons-sort' => true, + 'dashicons-sos' => true, + 'dashicons-spotify' => true, + 'dashicons-star-empty' => true, + 'dashicons-star-filled' => true, + 'dashicons-star-half' => true, + 'dashicons-sticky' => true, + 'dashicons-store' => true, + 'dashicons-superhero-alt' => true, + 'dashicons-superhero' => true, + 'dashicons-table-col-after' => true, + 'dashicons-table-col-before' => true, + 'dashicons-table-col-delete' => true, + 'dashicons-table-row-after' => true, + 'dashicons-table-row-before' => true, + 'dashicons-table-row-delete' => true, + 'dashicons-tablet' => true, + 'dashicons-tag' => true, + 'dashicons-tagcloud' => true, + 'dashicons-testimonial' => true, + 'dashicons-text-page' => true, + 'dashicons-text' => true, + 'dashicons-thumbs-down' => true, + 'dashicons-thumbs-up' => true, + 'dashicons-tickets-alt' => true, + 'dashicons-tickets' => true, + 'dashicons-tide' => true, + 'dashicons-translation' => true, + 'dashicons-trash' => true, + 'dashicons-twitch' => true, + 'dashicons-twitter-alt' => true, + 'dashicons-twitter' => true, + 'dashicons-undo' => true, + 'dashicons-universal-access-alt' => true, + 'dashicons-universal-access' => true, + 'dashicons-unlock' => true, + 'dashicons-update-alt' => true, + 'dashicons-update' => true, + 'dashicons-upload' => true, + 'dashicons-vault' => true, + 'dashicons-video-alt' => true, + 'dashicons-video-alt2' => true, + 'dashicons-video-alt3' => true, + 'dashicons-visibility' => true, + 'dashicons-warning' => true, + 'dashicons-welcome-add-page' => true, + 'dashicons-welcome-comments' => true, + 'dashicons-welcome-learn-more' => true, + 'dashicons-welcome-view-site' => true, + 'dashicons-welcome-widgets-menus' => true, + 'dashicons-welcome-write-blog' => true, + 'dashicons-whatsapp' => true, + 'dashicons-wordpress-alt' => true, + 'dashicons-wordpress' => true, + 'dashicons-xing' => true, + 'dashicons-yes-alt' => true, + 'dashicons-yes' => true, + 'dashicons-youtube' => true, + 'dashicons-editor-distractionfree' => true, + 'dashicons-exerpt-view' => true, + 'dashicons-format-links' => true, + 'dashicons-format-standard' => true, + 'dashicons-post-trash' => true, + 'dashicons-share1' => true, + 'dashicons-welcome-edit-page' => true, +); diff --git a/projects/packages/masterbar/src/admin-menu/globe-icon.svg b/projects/packages/masterbar/src/admin-menu/globe-icon.svg new file mode 100644 index 0000000000000..03db8b4dd7a53 --- /dev/null +++ b/projects/packages/masterbar/src/admin-menu/globe-icon.svg @@ -0,0 +1 @@ +Globe \ No newline at end of file diff --git a/projects/packages/masterbar/src/admin-menu/load.php b/projects/packages/masterbar/src/admin-menu/load.php new file mode 100644 index 0000000000000..48c9d858aeb20 --- /dev/null +++ b/projects/packages/masterbar/src/admin-menu/load.php @@ -0,0 +1,171 @@ +is_active( 'sso' ) ) { + return false; + } + + // No nav customizations on WP Admin of Jetpack sites. + if ( is_a( $admin_menu_class, Jetpack_Admin_Menu::class, true ) && ! $is_api_request ) { + return false; + } + + return true; +} + +/** + * Hides the Customizer menu items when the block theme is active by removing the dotcom-specific actions. + * They are not needed for block themes. + * + * @see https://github.com/Automattic/jetpack/pull/36017 + */ +function hide_customizer_menu_on_block_theme() { + add_action( + 'init', + function () { + if ( wp_is_block_theme() && ! is_customize_preview() ) { + remove_action( 'customize_register', 'add_logotool_button', 20 ); + remove_action( 'customize_register', 'footercredits_register', 99 ); + remove_action( 'customize_register', 'wpcom_disable_customizer_site_icon', 20 ); + + if ( class_exists( '\Jetpack_Fonts' ) ) { + $jetpack_fonts_instance = \Jetpack_Fonts::get_instance(); + remove_action( 'customize_register', array( $jetpack_fonts_instance, 'register_controls' ) ); + remove_action( 'customize_register', array( $jetpack_fonts_instance, 'maybe_prepopulate_option' ), 0 ); + } + + remove_action( 'customize_register', array( 'Jetpack_Fonts_Typekit', 'maybe_override_for_advanced_mode' ), 20 ); + + remove_action( 'customize_register', 'Automattic\Jetpack\Masterbar\register_css_nudge_control' ); + + remove_action( 'customize_register', array( 'Jetpack_Custom_CSS_Enhancements', 'customize_register' ) ); + } + } + ); +} + +/** + * Gets the name of the class that customizes the admin menu. + * + * @return string Class name. + */ +function get_admin_menu_class() { + hide_customizer_menu_on_block_theme(); + + // WordPress.com Atomic sites. + if ( ( new Host() )->is_woa_site() ) { + + // DIFM Lite In Progress Atomic Sites. Uses the same menu used for domain-only sites. + // Ignore this check if we are in a support session. + // @phan-suppress-next-line PhanUndeclaredFunction -- This is temp, pending pf4qpu-nc-p2 + $is_difm_lite_in_progress = wpcomsh_is_site_sticker_active( 'difm-lite-in-progress' ); + $is_support_session = defined( 'WPCOM_SUPPORT_SESSION' ) && WPCOM_SUPPORT_SESSION; + if ( $is_difm_lite_in_progress && ! $is_support_session ) { + require_once __DIR__ . '/class-domain-only-admin-menu.php'; + return Domain_Only_Admin_Menu::class; + } + + require_once __DIR__ . '/class-atomic-admin-menu.php'; + return Atomic_Admin_Menu::class; + } + + // WordPress.com Simple sites. + if ( defined( 'IS_WPCOM' ) && IS_WPCOM ) { + $blog_id = get_current_blog_id(); + + // Domain-only sites. + $blog_options = get_blog_option( $blog_id, 'options' ); + $is_domain_only = ! empty( $blog_options['is_domain_only'] ); + if ( $is_domain_only ) { + require_once __DIR__ . '/class-domain-only-admin-menu.php'; + return Domain_Only_Admin_Menu::class; + } + + // DIFM Lite In Progress Sites. Uses the same menu used for domain-only sites. + // Ignore this check if we are in a support session. + $is_difm_lite_in_progress = has_blog_sticker( 'difm-lite-in-progress' ); + $is_support_session = defined( 'WPCOM_SUPPORT_SESSION' ) && WPCOM_SUPPORT_SESSION; + if ( $is_difm_lite_in_progress && ! $is_support_session ) { + require_once __DIR__ . '/class-domain-only-admin-menu.php'; + return Domain_Only_Admin_Menu::class; + } + + // P2 sites. + require_once WP_CONTENT_DIR . '/lib/wpforteams/functions.php'; + if ( \WPForTeams\is_wpforteams_site( $blog_id ) ) { + require_once __DIR__ . '/class-p2-admin-menu.php'; + return P2_Admin_Menu::class; + } + + // Rest of simple sites. + require_once __DIR__ . '/class-wpcom-admin-menu.php'; + return WPcom_Admin_Menu::class; + } + + // Jetpack sites. + require_once __DIR__ . '/class-jetpack-admin-menu.php'; + return Jetpack_Admin_Menu::class; +} + +/** + * Filters the name of the class that customizes the admin menu. It should extends the `Base_Admin_Menu` class. + * + * @module masterbar + * + * @since jetpack-9.6.0 + * + * @param string $admin_menu_class Class name. + */ +$admin_menu_class = apply_filters( 'jetpack_admin_menu_class', get_admin_menu_class() ); +if ( should_customize_nav( $admin_menu_class ) ) { + /** The admin menu singleton instance. @var Base_Admin_Menu $instance */ + $admin_menu_class::get_instance(); + + /** + * Trigger an event when the user uses the dashboard quick switcher. + * + * @param string $screen The current screen. + * @param string $view The view the user choosed to go to. + */ + function dashboard_quick_switcher_record_usage( $screen, $view ) { + require_once __DIR__ . '/class-dashboard-switcher-tracking.php'; + + $tracking = new Dashboard_Switcher_Tracking( + new Tracking( Dashboard_Switcher_Tracking::get_jetpack_tracking_product() ), + array( Dashboard_Switcher_Tracking::class, 'wpcom_tracks_record_event' ), + Dashboard_Switcher_Tracking::get_plan() + ); + + $tracking->record_switch_event( $screen, $view ); + } + + \add_action( 'jetpack_dashboard_switcher_changed_view', __NAMESPACE__ . '\dashboard_quick_switcher_record_usage', 10, 2 ); +} else { + \add_filter( 'jetpack_load_admin_menu_class', '__return_false' ); +} diff --git a/projects/packages/masterbar/src/admin-menu/menu-mappings.php b/projects/packages/masterbar/src/admin-menu/menu-mappings.php new file mode 100644 index 0000000000000..06795e340b713 --- /dev/null +++ b/projects/packages/masterbar/src/admin-menu/menu-mappings.php @@ -0,0 +1,32 @@ + 'https://wordpress.com/media/', + 'edit.php' => 'https://wordpress.com/posts/', + 'edit-comments.php' => 'https://wordpress.com/comments/', + 'import.php' => 'https://wordpress.com/import/', + 'edit.php?post_type=page' => 'https://wordpress.com/pages/', + 'edit.php?post_type=post' => 'https://wordpress.com/posts/', + 'users.php' => 'https://wordpress.com/people/team/', + 'options-general.php' => 'https://wordpress.com/settings/general/', + 'options-discussion.php' => 'https://wordpress.com/settings/discussion/', + 'options-reading.php' => 'https://wordpress.com/settings/reading/', + 'options-writing.php' => 'https://wordpress.com/settings/writing/', + 'themes.php' => 'https://wordpress.com/themes/', + 'edit-tags.php?taxonomy=category' => 'https://wordpress.com/settings/taxonomies/category/', + 'edit-tags.php?taxonomy=post_tag' => 'https://wordpress.com/settings/taxonomies/post_tag/', + 'edit.php?post_type=jetpack-portfolio' => 'https://wordpress.com/types/jetpack-portfolio/', + 'edit.php?post_type=jetpack-testimonial' => 'https://wordpress.com/types/jetpack-testimonial/', +); + +if ( defined( 'IS_WPCOM' ) && IS_WPCOM ) { + // WPCOM Specific mappings. + $common_mappings['export.php'] = 'https://wordpress.com/export/'; +} + +return $common_mappings; diff --git a/projects/packages/masterbar/src/class-main.php b/projects/packages/masterbar/src/class-main.php new file mode 100644 index 0000000000000..bf692aab67204 --- /dev/null +++ b/projects/packages/masterbar/src/class-main.php @@ -0,0 +1,64 @@ +is_woa_site() ) { + new Inline_Help(); + require_once __DIR__ . '/wp-posts-list/bootstrap.php'; + require_once __DIR__ . '/profile-edit/bootstrap.php'; + require_once __DIR__ . '/nudges/bootstrap.php'; + } + + /** + * Whether to load the admin menu functionality. + * + * @use add_filter( 'jetpack_load_admin_menu_class', '__return_true' ); + * + * @param bool $load_admin_menu_class Load Jetpack's custom admin menu functionality. Default to false. + */ + if ( ! $should_use_nav_redesign && apply_filters( 'jetpack_load_admin_menu_class', false ) ) { + require_once __DIR__ . '/admin-menu/load.php'; + } + + /** + * Fires after the Masterbar package is initialized. + * Used mainly to ensure the package is initialized once. + * + * @since $$next-version$$ + */ + do_action( 'jetpack_masterbar_init' ); + } +} diff --git a/projects/packages/masterbar/src/class-masterbar.php b/projects/packages/masterbar/src/class-masterbar.php deleted file mode 100644 index b222cba84527b..0000000000000 --- a/projects/packages/masterbar/src/class-masterbar.php +++ /dev/null @@ -1,16 +0,0 @@ -base ) && str_contains( $current_screen->base, '_page_wpseo_' ); + + if ( $is_framed || $is_yoast ) { + return; + } + // phpcs:enable WordPress.Security.NonceVerification.Recommended + + add_action( 'admin_footer', array( $this, 'add_fab_icon' ) ); + + add_action( 'admin_enqueue_scripts', array( $this, 'add_fab_styles' ) ); + } + + /** + * Outputs "FAB" icon markup and SVG. + * + * @return void|string the HTML markup for the FAB or early exit. + */ + public function add_fab_icon() { + + if ( wp_doing_ajax() ) { + return; + } + + $svg_allowed = array( + 'svg' => array( + 'id' => true, + 'class' => true, + 'aria-hidden' => true, + 'aria-labelledby' => true, + 'role' => true, + 'xmlns' => true, + 'width' => true, + 'height' => true, + 'viewbox' => true, // <= Must be lower case! + ), + 'g' => array( 'fill' => true ), + 'title' => array( 'title' => true ), + 'path' => array( + 'd' => true, + 'fill' => true, + ), + ); + + $gridicon_help = file_get_contents( __DIR__ . '/gridicon-help.svg', true ); + + // Add tracking data to link to be picked up by Calypso for GA and Tracks usage. + $tracking_href = add_query_arg( + array( + 'utm_source' => 'wp_admin', + 'utm_medium' => 'other', + 'utm_content' => 'jetpack_masterbar_inline_help_click', + 'flags' => 'a8c-analytics.on', + ), + 'https://wordpress.com/help' + ); + + load_template( + __DIR__ . '/inline-help-template.php', + true, + array( + 'href' => $tracking_href, + 'icon' => $gridicon_help, + 'svg_allowed' => $svg_allowed, + ) + ); + } + + /** + * Enqueues FAB CSS styles. + * + * @return void + */ + public function add_fab_styles() { + $assets_base_path = '../../dist/inline-help/'; + + Assets::register_script( + 'a8c-faux-inline-help', + $assets_base_path . 'inline-help.js', + __FILE__, + array( + 'enqueue' => true, + 'css_path' => $assets_base_path . 'inline-help.css', + ) + ); + } +} diff --git a/projects/packages/masterbar/src/inline-help/gridicon-help.svg b/projects/packages/masterbar/src/inline-help/gridicon-help.svg new file mode 100644 index 0000000000000..f85fe9e17f14d --- /dev/null +++ b/projects/packages/masterbar/src/inline-help/gridicon-help.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/projects/packages/masterbar/src/inline-help/inline-help-template.php b/projects/packages/masterbar/src/inline-help/inline-help-template.php new file mode 100644 index 0000000000000..d259f7063f951 --- /dev/null +++ b/projects/packages/masterbar/src/inline-help/inline-help-template.php @@ -0,0 +1,17 @@ + + +
+ + + +
diff --git a/projects/packages/masterbar/src/inline-help/inline-help.css b/projects/packages/masterbar/src/inline-help/inline-help.css new file mode 100644 index 0000000000000..f9971d47d27bf --- /dev/null +++ b/projects/packages/masterbar/src/inline-help/inline-help.css @@ -0,0 +1,74 @@ +.a8c-faux-inline-help { + position: fixed; + right: 24px; + bottom: 24px; + z-index: 9999; +} + +.a8c-faux-inline-help .a8c-faux-inline-help__button { + position: absolute; + right: 0; + bottom: 10px; /* push away from WordPress Core footer */ + line-height: 0; + padding: 1px; + border-radius: 100%; + background-color: #1d2327; /* IE11 fallback - Classic Dark */ + background-color: var( --color-primary ); + border: 1px solid #3c434a; /* IE11 fallback - Classic Dark */ + border: 1px solid var( --color-primary-dark ); + transition: all 0.2s ease-in-out; + overflow: visible; + width: 40px; + height: 40px; + box-sizing: border-box; /* .button component */ + + /* elevation() mixin styles */ + box-shadow: 0 4px 5px 0 rgba( 16, 21, 23, 0.14 ), + 0 1px 10px 0 rgba( 16, 21, 23, 0.12 ), + 0 2px 4px -1px rgba( 16, 21, 23, 0.2 ); /* IE11 fallback - Classic Dark */ + box-shadow: 0 4px 5px 0 rgba( var( --color-neutral-100-rgb ), 0.14 ), + 0 1px 10px 0 rgba( var( --color-neutral-100-rgb ), 0.12 ), + 0 2px 4px -1px rgba( var( --color-neutral-100-rgb ), 0.2 ); +} + +.a8c-faux-inline-help .a8c-faux-inline-help__button::before { + width: 28px; + height: 28px; + display: block; + position: absolute; + top: 5px; + left: 5px; + content: ''; + background: #ffffff; /* IE11 fallback - Classic Dark */ + background: var( --color-surface ); + border-radius: 100%; +} + +.a8c-faux-inline-help .a8c-faux-inline-help__button:focus { + background-color: #1d2327; /* IE11 fallback - Classic Dark */ + background-color: var( --color-primary ); + box-shadow: 0 0 0 2px #8c8f94; /* IE11 fallback - Classic Dark */ + box-shadow: 0 0 0 2px var( --color-primary-light ); +} + +.a8c-faux-inline-help .a8c-faux-inline-help__button .gridicon { + pointer-events: none; /* ensure SVG does not capture click on anchor */ + position: relative; + fill: #1d2327; /* IE11 fallback - Classic Dark */ + fill: var( --color-primary ); + margin: -3px 0 0 -3px; + top: 0; + height: 42px; + width: 42px; +} + +.a8c-faux-inline-help .a8c-faux-inline-help__button .gridicon>use:first-child, +.a8c-faux-inline-help .a8c-faux-inline-help__button .gridicon>g:first-child { + transform: none; +} + +.a8c-faux-inline-help .a8c-faux-inline-help__button:hover:not(.is-active) { + background: #1d2327; /* IE11 fallback - Classic Dark */ + background: var( --color-primary ); + transform: scale( 1.15 ); +} diff --git a/projects/packages/masterbar/src/inline-help/inline-help.js b/projects/packages/masterbar/src/inline-help/inline-help.js new file mode 100644 index 0000000000000..2b04364914b74 --- /dev/null +++ b/projects/packages/masterbar/src/inline-help/inline-help.js @@ -0,0 +1 @@ +import './inline-help.css'; diff --git a/projects/packages/masterbar/src/masterbar/class-masterbar.php b/projects/packages/masterbar/src/masterbar/class-masterbar.php new file mode 100644 index 0000000000000..5de5f1201b3c8 --- /dev/null +++ b/projects/packages/masterbar/src/masterbar/class-masterbar.php @@ -0,0 +1,1567 @@ +user_id = get_current_user_id(); + $connection_manager = new Connection_Manager( 'jetpack' ); + + if ( ! $connection_manager->is_user_connected( $this->user_id ) ) { + return; + } + + $this->user_data = $connection_manager->get_connected_user_data( $this->user_id ); + $this->user_login = $this->user_data['login'] ?? ''; + $this->user_email = $this->user_data['email'] ?? ''; + $this->display_name = $this->user_data['display_name'] ?? ''; + $this->user_site_count = $this->user_data['site_count'] ?? ''; + $this->is_rtl = isset( $this->user_data['text_direction'] ) && 'rtl' === $this->user_data['text_direction']; + $this->user_locale = $this->user_data['user_locale'] ?? ''; + $this->site_woa = ( new Host() )->is_woa_site(); + + // Store part of the connected user data as user options so it can be used + // by other files of the masterbar module without making another XMLRPC + // request. Although `get_connected_user_data` tries to save the data for + // future uses on a transient, the data is not guaranteed to be cached. + update_user_option( $this->user_id, 'jetpack_wpcom_is_rtl', $this->is_rtl ? '1' : '0' ); + if ( isset( $this->user_data['use_wp_admin_links'] ) ) { + update_user_option( $this->user_id, 'jetpack_admin_menu_link_destination', $this->user_data['use_wp_admin_links'] ? '1' : '0' ); + } + // If Atomic, store and install user locale. + if ( $this->site_woa && 'wp-admin' !== get_option( 'wpcom_admin_interface' ) ) { + $this->user_locale = $this->get_jetpack_locale( $this->user_locale ); + $this->install_locale( $this->user_locale ); + $this->unload_non_default_textdomains_on_wpcom_user_locale_switch( $this->user_locale ); + update_user_option( $this->user_id, 'locale', $this->user_locale, true ); + } + + add_action( 'admin_bar_init', array( $this, 'init' ) ); + + if ( ! empty( $this->user_data['ID'] ) ) { + // Post logout on the site, also log the user out of WordPress.com. + add_filter( 'logout_redirect', array( $this, 'maybe_logout_user_from_wpcom' ) ); + } + } + + /** + * Initialize our masterbar. + */ + public function init() { + $this->locale = $this->get_locale(); + + // Don't show the masterbar on WordPress mobile apps. + if ( User_Agent_Info::is_mobile_app() ) { + add_filter( 'show_admin_bar', '__return_false' ); + return; + } + + // Disable the Masterbar on AMP views. + if ( + class_exists( 'Jetpack_AMP_Support' ) + && Jetpack_AMP_Support::is_amp_available() + && Jetpack_AMP_Support::is_amp_request() + ) { + return; + } + + Assets::add_resource_hint( + array( + '//s0.wp.com', + '//0.gravatar.com', + '//1.gravatar.com', + '//2.gravatar.com', + ), + 'dns-prefetch' + ); + + // WordPress.com on Atomic only. + if ( $this->site_woa ) { + /* + * override user setting that hides masterbar from site's front. + * https://github.com/Automattic/jetpack/issues/7667 + */ + add_filter( 'show_admin_bar', '__return_true' ); + } + + // Used to build menu links that point directly to Calypso. + $this->primary_site_slug = ( new Status() )->get_site_suffix(); + + // Used for display purposes and for building WP Admin links. + $this->primary_site_url = str_replace( '::', '/', $this->primary_site_slug ); + + add_filter( 'admin_body_class', array( $this, 'admin_body_class' ) ); + + add_action( 'wp_before_admin_bar_render', array( $this, 'replace_core_masterbar' ), 99999 ); + + add_action( 'wp_enqueue_scripts', array( $this, 'add_styles_and_scripts' ) ); + add_action( 'admin_enqueue_scripts', array( $this, 'add_styles_and_scripts' ) ); + + add_action( 'wp_enqueue_scripts', array( $this, 'remove_core_styles' ) ); + add_action( 'admin_enqueue_scripts', array( $this, 'remove_core_styles' ) ); + + if ( ( new Modules() )->is_active( 'notes' ) && $this->is_rtl ) { + // Override Notification module to include RTL styles. + add_action( 'a8c_wpcom_masterbar_enqueue_rtl_notification_styles', '__return_true' ); + } + + // Hides and replaces the language dropdown for the current user, on WoA. + if ( $this->site_woa && + 'wp-admin' !== get_option( 'wpcom_admin_interface' ) && + defined( 'IS_PROFILE_PAGE' ) && IS_PROFILE_PAGE ) { + add_action( 'user_edit_form_tag', array( $this, 'hide_language_dropdown' ) ); + add_action( 'personal_options', array( $this, 'replace_language_dropdown' ), 9 ); + } + } + + /** + * Log out from WordPress.com when logging out of the local site. + * + * @param string $redirect_to The redirect destination URL. + */ + public function maybe_logout_user_from_wpcom( $redirect_to ) { + /** + * Whether we should sign out from wpcom too when signing out from the masterbar. + * + * @since jetpack-5.9.0 + * + * @param bool $masterbar_should_logout_from_wpcom True by default. + */ + $masterbar_should_logout_from_wpcom = apply_filters( 'jetpack_masterbar_should_logout_from_wpcom', true ); + if ( + // No need to check for a nonce here, it happens further up. + isset( $_GET['context'] ) // phpcs:ignore WordPress.Security.NonceVerification.Recommended + && 'masterbar' === $_GET['context'] // phpcs:ignore WordPress.Security.NonceVerification.Recommended + && $masterbar_should_logout_from_wpcom + ) { + /** + * Hook into the log out event happening from the Masterbar. + * + * @since jetpack-5.1.0 + * @since jetpack-7.9.0 Added the $wpcom_user_id parameter to the action. + * + * @module masterbar + * + * @param int $wpcom_user_id WordPress.com User ID. + */ + do_action( 'wp_masterbar_logout', $this->user_data['ID'] ); + } + + return $redirect_to; + } + + /** + * Adds CSS classes to admin body tag. + * + * @since jetpack-5.1 + * + * @param string $admin_body_classes CSS classes that will be added. + * + * @return string + */ + public function admin_body_class( $admin_body_classes ) { + + $classes = array( 'jetpack-masterbar', trim( $admin_body_classes ) ); + + if ( get_option( 'wpcom_admin_interface' ) === 'wp-admin' ) { + $classes[] = 'wpcom-admin-interface'; + } + + return implode( ' ', $classes ); + } + + /** + * Remove the default Admin Bar CSS. + */ + public function remove_core_styles() { + /* + * Notifications need the admin bar styles, + * so let's not remove them when the module is active. + * Also, don't remove the styles if the user has opted to use wp-admin. + */ + if ( ! ( new Modules() )->is_active( 'notes' ) && get_option( 'wpcom_admin_interface' ) !== 'wp-admin' ) { + wp_dequeue_style( 'admin-bar' ); + } + } + + /** + * Enqueue our own CSS and JS to display our custom admin bar. + */ + public function add_styles_and_scripts() { + $assets_base_path = '../../dist/masterbar/'; + + // WoA sites: If wpcom_admin_interface is set to wp-admin, load the wp-admin styles. + // These include only styles to enable the "My Sites" and "Reader" links that will be added. + if ( get_option( 'wpcom_admin_interface' ) === 'wp-admin' ) { + $css_file = $this->is_rtl ? 'masterbar-wp-admin-rtl.css' : 'masterbar-wp-admin.css'; + wp_enqueue_style( 'a8c-wpcom-masterbar-overrides', $this->wpcom_static_url( '/wp-content/mu-plugins/admin-bar/masterbar-overrides/' . $css_file ), array(), Main::PACKAGE_VERSION ); + return; + } + + if ( $this->is_rtl ) { + wp_enqueue_style( 'a8c-wpcom-masterbar-rtl', $this->wpcom_static_url( '/wp-content/mu-plugins/admin-bar/rtl/wpcom-admin-bar-rtl.css' ), array(), Main::PACKAGE_VERSION ); + wp_enqueue_style( 'a8c-wpcom-masterbar-overrides-rtl', $this->wpcom_static_url( '/wp-content/mu-plugins/admin-bar/masterbar-overrides/rtl/masterbar-rtl.css' ), array(), Main::PACKAGE_VERSION ); + } else { + wp_enqueue_style( 'a8c-wpcom-masterbar', $this->wpcom_static_url( '/wp-content/mu-plugins/admin-bar/wpcom-admin-bar.css' ), array(), Main::PACKAGE_VERSION ); + wp_enqueue_style( 'a8c-wpcom-masterbar-overrides', $this->wpcom_static_url( '/wp-content/mu-plugins/admin-bar/masterbar-overrides/masterbar.css' ), array(), Main::PACKAGE_VERSION ); + } + + // Local overrides. + Assets::register_script( + 'a8c_wpcom_css_override', + $assets_base_path . 'overrides.js', + __FILE__, + array( + 'enqueue' => true, + 'css_path' => $assets_base_path . 'overrides.css', + ) + ); + + if ( ! ( new Modules() )->is_active( 'notes' ) ) { + // Masterbar is relying on some icons from noticons.css. + wp_enqueue_style( 'noticons', $this->wpcom_static_url( '/i/noticons/noticons.css' ), array(), Main::PACKAGE_VERSION . '-' . gmdate( 'oW' ) ); + } + + wp_enqueue_script( + 'jetpack-accessible-focus', + Assets::get_file_url_for_environment( '_inc/build/accessible-focus.min.js', '_inc/accessible-focus.js' ), + array(), + Main::PACKAGE_VERSION, + false + ); + Assets::register_script( + 'a8c_wpcom_masterbar_tracks_events', + $assets_base_path . 'tracks-events.js', + __FILE__, + array( + 'enqueue' => true, + ) + ); + + wp_enqueue_script( + 'a8c_wpcom_masterbar_overrides', + $this->wpcom_static_url( '/wp-content/mu-plugins/admin-bar/masterbar-overrides/masterbar.js' ), + array( 'jquery' ), + Main::PACKAGE_VERSION, + false + ); + } + + /** + * Get base URL where our CSS and JS will come from. + * + * @param string $file File path for a static resource. + */ + private function wpcom_static_url( $file ) { + if ( ! empty( $this->sandbox_url ) ) { + // For testing undeployed changes to remotely enqueued scripts and styles. + return set_url_scheme( $this->sandbox_url . $file, 'https' ); + } + + $url = 'https://s0.wp.com' . $file; + + return set_url_scheme( $url, 'https' ); + } + + /** + * Remove the default admin bar items and replace it with our own admin bar. + */ + public function replace_core_masterbar() { + global $wp_admin_bar; + + if ( ! is_object( $wp_admin_bar ) ) { + return false; + } + + if ( get_option( 'wpcom_admin_interface' ) === 'wp-admin' ) { + $this->build_wp_admin_interface_bar( $wp_admin_bar ); + return; + } + + $this->clear_core_masterbar( $wp_admin_bar ); + $this->build_wpcom_masterbar( $wp_admin_bar ); + } + + /** + * This reorganizes the original wp admin bar for when an atomic site + * has the wpcom_admin_interface set to wp-admin. + * + * The wpcom_admin_interface = wp-admin setting indicates that the users wishes + * to NOT use the wpcom master bar. We do need to adjust a couple of things + * though. + * + * @param WP_Admin_Bar $bar The admin bar object. + * + * @return void + */ + protected function build_wp_admin_interface_bar( $bar ) { + + $nodes = array(); + + // First, lets gather all nodes and remove them. + foreach ( $bar->get_nodes() as $node ) { + $nodes[ $node->id ] = $node; + $bar->remove_node( $node->id ); + } + + // This disables a submenu from being placed under the My Sites button. + add_filter( 'jetpack_load_admin_menu_class', '__return_true' ); + + // Here we add the My sites and Reader buttons + $this->wpcom_adminbar_add_secondary_groups( $bar ); + $this->add_my_sites_submenu( $bar ); + $this->add_reader_submenu( $bar ); + + foreach ( $nodes as $id => $node ) { + + $bar->add_node( $node ); + // Add our custom node and change the title of the edit profile node. + if ( 'edit-profile' === $id ) { + $this->add_wpcom_profile_link( $bar ); + $bar->add_node( + array( + 'id' => 'edit-profile', + 'title' => __( 'Site Profile', 'jetpack-masterbar' ), + ) + ); + } + } + + // Add a menu item to the user menu + // Add a custom link to the user menu. + $this->add_wpcom_profile_link( $bar ); + + // Remove some things + $bar->remove_node( 'wp-logo' ); + } + + /** + * Add a link to the user` profile on WordPress.com + * + * @param WP_Admin_Bar $bar The admin bar object. + * + * @return void + */ + protected function add_wpcom_profile_link( $bar ) { + $custom_node = array( + 'parent' => 'user-actions', + 'id' => 'wpcom-profile-link', + 'title' => __( 'WordPress.com Profile', 'jetpack-masterbar' ), + 'href' => 'https://wordpress.com/me', + 'meta' => array( + 'title' => __( 'Go to your profile page on WordPress.com', 'jetpack-masterbar' ), // Optional, tooltip text. + ), + ); + + $bar->add_node( $custom_node ); + } + + /** + * Remove all existing toolbar entries from core Masterbar + * + * @param WP_Admin_Bar $wp_admin_bar Admin Bar instance. + */ + public function clear_core_masterbar( $wp_admin_bar ) { + foreach ( $wp_admin_bar->get_nodes() as $node ) { + $wp_admin_bar->remove_node( $node->id ); + } + } + + /** + * Add entries corresponding to WordPress.com Masterbar + * + * @param WP_Admin_Bar $wp_admin_bar Admin Bar instance. + */ + public function build_wpcom_masterbar( $wp_admin_bar ) { + // Menu groups. + $this->wpcom_adminbar_add_secondary_groups( $wp_admin_bar ); + + // Left part. + $this->add_my_sites_submenu( $wp_admin_bar ); + $this->add_reader_submenu( $wp_admin_bar ); + + // Right part. + if ( ( new Modules() )->is_active( 'notes' ) && ! \Jetpack_Notifications::is_block_editor() ) { + $this->add_notifications( $wp_admin_bar ); + } + + $this->add_me_submenu( $wp_admin_bar ); + $this->add_write_button( $wp_admin_bar ); + + // Recovery mode exit. + wp_admin_bar_recovery_mode_menu( $wp_admin_bar ); + + if ( class_exists( 'Automattic\Jetpack\Scan\Admin_Bar_Notice' ) ) { + $scan_admin_bar_notice = Admin_Bar_Notice::instance(); + $scan_admin_bar_notice->add_threats_to_toolbar( $wp_admin_bar ); + } + } + + /** + * Get WordPress.com current locale name. + */ + public function get_locale() { + $wpcom_locale = get_locale(); + + if ( ! class_exists( 'GP_Locales' ) ) { + // @phan-suppress-next-line PhanTypeMismatchArgumentNullableInternal -- See https://github.com/Automattic/jetpack/issues/2707#issuecomment-2036701663 + if ( defined( 'JETPACK__GLOTPRESS_LOCALES_PATH' ) && file_exists( JETPACK__GLOTPRESS_LOCALES_PATH ) ) { + require JETPACK__GLOTPRESS_LOCALES_PATH; + } + } + + if ( class_exists( 'GP_Locales' ) ) { + $wpcom_locale_object = GP_Locales::by_field( 'wp_locale', get_locale() ); + if ( $wpcom_locale_object instanceof GP_Locale ) { + $wpcom_locale = $wpcom_locale_object->slug; + } + } + + return $wpcom_locale; + } + + /** + * Get Jetpack locale name. + * + * @param string $slug Locale slug. + * @return string Jetpack locale. + */ + public function get_jetpack_locale( $slug = '' ) { + if ( ! class_exists( 'GP_Locales' ) ) { + // @phan-suppress-next-line PhanTypeMismatchArgumentNullableInternal -- See https://github.com/Automattic/jetpack/issues/2707#issuecomment-2036701663 + if ( defined( 'JETPACK__GLOTPRESS_LOCALES_PATH' ) && file_exists( JETPACK__GLOTPRESS_LOCALES_PATH ) ) { + require JETPACK__GLOTPRESS_LOCALES_PATH; + } + } + + if ( class_exists( 'GP_Locales' ) ) { + $jetpack_locale_object = GP_Locales::by_field( 'slug', $slug ); + if ( $jetpack_locale_object instanceof GP_Locale ) { + $jetpack_locale = $jetpack_locale_object->wp_locale ? $jetpack_locale_object->wp_locale : 'en_US'; + } + } + + if ( isset( $jetpack_locale ) ) { + return $jetpack_locale; + } + + return 'en_US'; + } + + /** + * Install locale if not yet available. + * + * @param string $locale The new locale slug. + */ + public function install_locale( $locale = '' ) { + if ( ! in_array( $locale, get_available_languages(), true ) + && ! empty( $locale ) && current_user_can( 'install_languages' ) ) { + + if ( ! function_exists( 'wp_download_language_pack' ) ) { + require_once ABSPATH . 'wp-admin/includes/translation-install.php'; + } + + if ( ! function_exists( 'request_filesystem_credentials' ) ) { + require_once ABSPATH . 'wp-admin/includes/file.php'; + } + + if ( wp_can_install_language_pack() ) { + wp_download_language_pack( $locale ); + load_default_textdomain( $locale ); + } + } + } + + /** + * Trigger reloading of all non-default textdomains if the user just changed + * their locale on WordPress.com. + * + * User locale changes on WordPress.com are detected and acted upon in the + * constructor of this class. However, at that point, some plugins and their + * translations have already been loaded (including Jetpack's). If we don't + * reload the translations, the user will see a mix of the old and new locale's + * translations until the next page load. + * + * The default textdomain is not affected by this because it's always reloaded + * after all plugins have been loaded, in wp-settings.php. + * + * @param string $wpcom_locale The user's detected WordPress.com locale. + */ + public function unload_non_default_textdomains_on_wpcom_user_locale_switch( $wpcom_locale ) { + $user_switched_locale = get_user_locale() !== $wpcom_locale; + if ( ! $user_switched_locale ) { + return; + } + + global $l10n; + $loaded_textdomains = array_keys( $l10n ); + $non_default_textdomains = array_diff( $loaded_textdomains, array( 'default' ) ); + foreach ( $non_default_textdomains as $textdomain ) { + // Using $reloadable = true makes sure the correct locale's + // translations are loaded just-in-time. + unload_textdomain( $textdomain, true ); + } + } + + /** + * Hide language dropdown on user edit form. + */ + public function hide_language_dropdown() { + add_filter( 'get_available_languages', '__return_empty_array' ); + } + + /** + * Replace language dropdown with link to WordPress.com. + */ + public function replace_language_dropdown() { + $language_row = printf( '' ); + $language_row .= printf( + '', + esc_html__( 'Language', 'jetpack-masterbar' ) + ); + $language_row .= printf( '' ); + $language_row .= printf( + '%2$s', + esc_url( 'https://wordpress.com/me/account' ), + esc_html__( 'Set your profile language on WordPress.com.', 'jetpack-masterbar' ) + ); + $language_row .= printf( '' ); + return $language_row; + } + + /** + * Add the Notifications menu item. + * + * @param WP_Admin_Bar $wp_admin_bar Admin Bar instance. + */ + public function add_notifications( $wp_admin_bar ) { + $wp_admin_bar->add_node( + array( + 'id' => 'notes', + 'title' => ' + ' . esc_html__( 'Notifications', 'jetpack-masterbar' ) . ' + ', + 'meta' => array( + 'html' => '', + 'class' => 'menupop mb-trackable', + ), + 'parent' => 'top-secondary', + 'href' => 'https://wordpress.com/notifications', + ) + ); + } + + /** + * Add the "Reader" menu item in the root default group. + * + * @param WP_Admin_Bar $wp_admin_bar Admin Bar instance. + */ + public function add_reader_submenu( $wp_admin_bar ) { + $wp_admin_bar->add_menu( + array( + 'parent' => 'root-default', + 'id' => 'newdash', + 'title' => esc_html__( 'Reader', 'jetpack-masterbar' ), + 'href' => 'https://wordpress.com/read', + 'meta' => array( + 'class' => 'mb-trackable', + ), + ) + ); + } + + /** + * Merge 2 menu items together into 2 link tags. + * + * @param array $primary Array of menu information. + * @param array $secondary Array of menu information. + */ + public function create_menu_item_pair( $primary, $secondary ) { + $primary_class = 'ab-item ab-primary mb-icon'; + $secondary_class = 'ab-secondary'; + + $primary_anchor = $this->create_menu_item_anchor( $primary_class, $primary['url'], $primary['label'], $primary['id'] ); + $secondary_anchor = $this->create_menu_item_anchor( $secondary_class, $secondary['url'], $secondary['label'], $secondary['id'] ); + + return $primary_anchor . $secondary_anchor; + } + + /** + * Create a link tag based on information about a menu item. + * + * @param string $class Menu item CSS class. + * @param string $url URL you go to when clicking on the menu item. + * @param string $label Menu item title. + * @param string $id Menu item slug. + */ + public function create_menu_item_anchor( $class, $url, $label, $id ) { + return '' . $label . ''; + } + + /** + * Add Secondary groups for submenu items. + * + * @param WP_Admin_Bar $wp_admin_bar Admin Bar instance. + */ + public function wpcom_adminbar_add_secondary_groups( $wp_admin_bar ) { + $wp_admin_bar->add_group( + array( + 'id' => 'root-default', + 'meta' => array( + 'class' => 'ab-top-menu', + ), + ) + ); + + $wp_admin_bar->add_group( + array( + 'parent' => 'blog', + 'id' => 'blog-secondary', + 'meta' => array( + 'class' => 'ab-sub-secondary', + ), + ) + ); + + $wp_admin_bar->add_group( + array( + 'id' => 'top-secondary', + 'meta' => array( + 'class' => 'ab-top-secondary', + ), + ) + ); + } + + /** + * Add User info menu item. + * + * @param WP_Admin_Bar $wp_admin_bar Admin Bar instance. + */ + public function add_me_submenu( $wp_admin_bar ) { + $user_id = get_current_user_id(); + if ( empty( $user_id ) ) { + return; + } + + $avatar = get_avatar( $this->user_email, 32, 'mm', '', array( 'force_display' => true ) ); + $class = empty( $avatar ) ? 'mb-trackable' : 'with-avatar mb-trackable'; + + // Add the 'Me' menu. + $wp_admin_bar->add_menu( + array( + 'id' => 'my-account', + 'parent' => 'top-secondary', + 'title' => $avatar . '' . esc_html__( 'Me', 'jetpack-masterbar' ) . '', + 'href' => 'https://wordpress.com/me', + 'meta' => array( + 'class' => $class, + ), + ) + ); + + /** This filter is documented in modules/masterbar.php */ + if ( apply_filters( 'jetpack_load_admin_menu_class', false ) ) { + return; + } + + $id = 'user-actions'; + $wp_admin_bar->add_group( + array( + 'parent' => 'my-account', + 'id' => $id, + ) + ); + + $logout_url = wp_logout_url(); + $logout_url = add_query_arg( 'context', 'masterbar', $logout_url ); + + $user_info = get_avatar( $this->user_email, 128, 'mm', '', array( 'force_display' => true ) ); + $user_info .= '' . $this->display_name . ''; + $user_info .= '@' . $this->user_login . ''; + + $user_info .= sprintf( + '
%s
', + $logout_url, + esc_html__( 'Sign Out', 'jetpack-masterbar' ) + ); + + $blog_id = Connection_Manager::get_site_id( true ); + + $args = array(); + if ( $blog_id ) { + $args['site'] = $blog_id; + } + + $wp_admin_bar->add_menu( + array( + 'parent' => $id, + 'id' => 'user-info', + 'title' => $user_info, + 'meta' => array( + 'class' => 'user-info user-info-item', + 'tabindex' => -1, + ), + ) + ); + + $wp_admin_bar->add_menu( + array( + 'parent' => $id, + 'id' => 'profile-header', + 'title' => esc_html__( 'Profile', 'jetpack-masterbar' ), + 'meta' => array( + 'class' => 'ab-submenu-header', + ), + ) + ); + + $wp_admin_bar->add_menu( + array( + 'parent' => $id, + 'id' => 'my-profile', + 'title' => esc_html__( 'My Profile', 'jetpack-masterbar' ), + 'href' => Redirect::get_url( 'calypso-me', $args ), + 'meta' => array( + 'class' => 'mb-icon', + ), + ) + ); + + $wp_admin_bar->add_menu( + array( + 'parent' => $id, + 'id' => 'account-settings', + 'title' => esc_html__( 'Account Settings', 'jetpack-masterbar' ), + 'href' => Redirect::get_url( 'calypso-me-account', $args ), + 'meta' => array( + 'class' => 'mb-icon', + ), + ) + ); + + $wp_admin_bar->add_menu( + array( + 'parent' => $id, + 'id' => 'billing', + 'title' => esc_html__( 'Manage Purchases', 'jetpack-masterbar' ), + 'href' => Redirect::get_url( 'calypso-me-purchases', $args ), + 'meta' => array( + 'class' => 'mb-icon', + ), + ) + ); + + $wp_admin_bar->add_menu( + array( + 'parent' => $id, + 'id' => 'security', + 'title' => esc_html__( 'Security', 'jetpack-masterbar' ), + 'href' => Redirect::get_url( 'calypso-me-security', $args ), + 'meta' => array( + 'class' => 'mb-icon', + ), + ) + ); + + $wp_admin_bar->add_menu( + array( + 'parent' => $id, + 'id' => 'notifications', + 'title' => esc_html__( 'Notifications', 'jetpack-masterbar' ), + 'href' => Redirect::get_url( 'calypso-me-notifications', $args ), + 'meta' => array( + 'class' => 'mb-icon', + ), + ) + ); + + $wp_admin_bar->add_menu( + array( + 'parent' => $id, + 'id' => 'special-header', + 'title' => esc_html_x( + 'Special', + 'Title for Me sub-menu that contains Get Apps, Next Steps, and Help options', + 'jetpack-masterbar' + ), + 'meta' => array( + 'class' => 'ab-submenu-header', + ), + ) + ); + + $wp_admin_bar->add_menu( + array( + 'parent' => $id, + 'id' => 'get-apps', + 'title' => esc_html__( 'Get Apps', 'jetpack-masterbar' ), + 'href' => Redirect::get_url( 'calypso-me-get-apps', $args ), + 'meta' => array( + 'class' => 'mb-icon user-info-item', + ), + ) + ); + + $help_link = Redirect::get_url( 'jetpack-support', $args ); + + if ( $this->site_woa ) { + $help_link = Redirect::get_url( 'calypso-help', $args ); + } + + $wp_admin_bar->add_menu( + array( + 'parent' => $id, + 'id' => 'help', + 'title' => esc_html__( 'Help', 'jetpack-masterbar' ), + 'href' => $help_link, + 'meta' => array( + 'class' => 'mb-icon user-info-item', + ), + ) + ); + } + + /** + * Add Write Menu item. + * + * @param WP_Admin_Bar $wp_admin_bar Admin Bar instance. + */ + public function add_write_button( $wp_admin_bar ) { + $current_user = wp_get_current_user(); + + $posting_blog_id = get_current_blog_id(); + if ( ! is_user_member_of_blog( get_current_user_id(), get_current_blog_id() ) ) { + $posting_blog_id = $current_user->primary_blog; + } + + $user_can_post = current_user_can_for_blog( $posting_blog_id, 'publish_posts' ); + + if ( ! $posting_blog_id || ! $user_can_post ) { + return; + } + + $wp_admin_bar->add_menu( + array( + 'parent' => 'top-secondary', + 'id' => 'ab-new-post', + 'href' => admin_url( 'post-new.php' ), + 'title' => '' . esc_html__( 'Write', 'jetpack-masterbar' ) . '', + 'meta' => array( + 'class' => 'mb-trackable', + ), + ) + ); + } + + /** + * Add the "My Site" menu item in the root default group. + * + * @param WP_Admin_Bar $wp_admin_bar Admin Bar instance. + */ + public function add_my_sites_submenu( $wp_admin_bar ) { + $current_user = wp_get_current_user(); + + $blog_name = get_bloginfo( 'name' ); + if ( empty( $blog_name ) ) { + $blog_name = $this->primary_site_slug; + } + + if ( mb_strlen( $blog_name ) > 20 ) { + $blog_name = mb_substr( html_entity_decode( $blog_name, ENT_QUOTES ), 0, 20 ) . '…'; + } + + $my_site_url = 'https://wordpress.com/sites/' . $this->primary_site_url; + if ( 'wp-admin' === get_option( 'wpcom_admin_interface' ) ) { + $my_site_url = 'https://wordpress.com/sites'; + } + + $wp_admin_bar->add_menu( + array( + 'parent' => 'root-default', + 'id' => 'blog', + 'href' => $my_site_url, + 'meta' => array( + 'class' => 'my-sites mb-trackable', + ), + ) + ); + + /** This filter is documented in modules/masterbar.php */ + if ( apply_filters( 'jetpack_load_admin_menu_class', false ) ) { + return; + } + + $blog_id = Connection_Manager::get_site_id( true ); + + $args = array(); + if ( $blog_id ) { + $args['site'] = $blog_id; + } + + if ( $this->user_site_count > 1 ) { + $wp_admin_bar->add_menu( + array( + 'parent' => 'blog', + 'id' => 'switch-site', + 'title' => esc_html__( 'Switch Site', 'jetpack-masterbar' ), + 'href' => Redirect::get_url( 'calypso-sites', $args ), + ) + ); + } else { + $wp_admin_bar->add_menu( + array( + 'parent' => 'blog', + 'id' => 'new-site', + 'title' => esc_html__( '+ Add New WordPress', 'jetpack-masterbar' ), + 'href' => Redirect::get_url( 'calypso-start', array_merge( $args, array( 'query' => 'ref=admin-bar-logged-in' ) ) ), + ) + ); + } + + if ( is_user_member_of_blog( $current_user->ID ) ) { + $blavatar = ''; + $class = 'current-site'; + + if ( has_site_icon() ) { + $src = get_site_icon_url(); + $blavatar = 'Current site avatar'; + $class = 'has-blavatar'; + } + + $blog_info = '
' . $blavatar . '
'; + $blog_info .= '' . esc_html( $blog_name ) . ''; + $blog_info .= '' . esc_html( $this->primary_site_url ) . ''; + + $wp_admin_bar->add_menu( + array( + 'parent' => 'blog', + 'id' => 'blog-info', + 'title' => $blog_info, + 'href' => esc_url( trailingslashit( $this->primary_site_url ) ), + 'meta' => array( + 'class' => $class, + ), + ) + ); + } + + // Site Preview. + if ( is_admin() ) { + $wp_admin_bar->add_menu( + array( + 'parent' => 'blog', + 'id' => 'site-view', + 'title' => __( 'View Site', 'jetpack-masterbar' ), + 'href' => home_url(), + 'meta' => array( + 'class' => 'mb-icon', + 'target' => '_blank', + ), + ) + ); + } + + $this->add_my_home_submenu_item( $wp_admin_bar ); + + // Stats. + if ( ( new Modules() )->is_active( 'stats' ) && current_user_can( 'view_stats' ) ) { + $wp_admin_bar->add_menu( + array( + 'parent' => 'blog', + 'id' => 'blog-stats', + 'title' => esc_html__( 'Stats', 'jetpack-masterbar' ), + 'href' => Redirect::get_url( 'calypso-stats', $args ), + 'meta' => array( + 'class' => 'mb-icon', + ), + ) + ); + } + + if ( current_user_can( 'manage_options' ) ) { + $wp_admin_bar->add_menu( + array( + 'parent' => 'blog', + 'id' => 'activity', + 'title' => esc_html__( 'Activity', 'jetpack-masterbar' ), + 'href' => Redirect::get_url( 'calypso-activity-log', $args ), + 'meta' => array( + 'class' => 'mb-icon', + ), + ) + ); + } + + // Add Calypso plans link and plan type indicator. + if ( is_user_member_of_blog( $current_user->ID ) && current_user_can( 'manage_options' ) ) { + $plans_url = Redirect::get_url( 'calypso-plans', $args ); + $label = esc_html__( 'Plan', 'jetpack-masterbar' ); + $plan = Jetpack_Plan::get(); + + $plan_title = $this->create_menu_item_pair( + array( + 'url' => $plans_url, + 'id' => 'wp-admin-bar-plan', + 'label' => $label, + ), + array( + 'url' => $plans_url, + 'id' => 'wp-admin-bar-plan-badge', + 'label' => ! empty( $plan['product_name_short'] ) ? $plan['product_name_short'] : esc_html__( 'Free', 'jetpack-masterbar' ), + ) + ); + + $wp_admin_bar->add_menu( + array( + 'parent' => 'blog', + 'id' => 'plan', + 'title' => $plan_title, + 'meta' => array( + 'class' => 'inline-action', + ), + ) + ); + } + + // Publish group. + $wp_admin_bar->add_group( + array( + 'parent' => 'blog', + 'id' => 'publish', + ) + ); + + // Publish header. + $wp_admin_bar->add_menu( + array( + 'parent' => 'publish', + 'id' => 'publish-header', + 'title' => esc_html_x( 'Manage', 'admin bar menu group label', 'jetpack-masterbar' ), + 'meta' => array( + 'class' => 'ab-submenu-header', + ), + ) + ); + + // Pages. + $pages_title = $this->create_menu_item_pair( + array( + 'url' => Redirect::get_url( 'calypso-edit-pages', $args ), + 'id' => 'wp-admin-bar-edit-page', + 'label' => esc_html__( 'Site Pages', 'jetpack-masterbar' ), + ), + array( + 'url' => Redirect::get_url( 'calypso-edit-page', $args ), + 'id' => 'wp-admin-bar-new-page-badge', + 'label' => esc_html_x( 'Add', 'admin bar menu new item label', 'jetpack-masterbar' ), + ) + ); + + if ( ! current_user_can( 'edit_pages' ) ) { + $pages_title = $this->create_menu_item_anchor( + 'ab-item ab-primary mb-icon', + Redirect::get_url( 'calypso-edit-pages', $args ), + esc_html__( 'Site Pages', 'jetpack-masterbar' ), + 'wp-admin-bar-edit-page' + ); + } + + $wp_admin_bar->add_menu( + array( + 'parent' => 'publish', + 'id' => 'new-page', + 'title' => $pages_title, + 'meta' => array( + 'class' => 'inline-action', + ), + ) + ); + + // Blog Posts. + $posts_title = $this->create_menu_item_pair( + array( + 'url' => Redirect::get_url( 'calypso-edit-posts', $args ), + 'id' => 'wp-admin-bar-edit-post', + 'label' => esc_html__( 'Blog Posts', 'jetpack-masterbar' ), + ), + array( + 'url' => Redirect::get_url( 'calypso-edit-post', $args ), + 'id' => 'wp-admin-bar-new-post-badge', + 'label' => esc_html_x( 'Add', 'admin bar menu new item label', 'jetpack-masterbar' ), + ) + ); + + if ( ! current_user_can( 'edit_posts' ) ) { + $posts_title = $this->create_menu_item_anchor( + 'ab-item ab-primary mb-icon', + Redirect::get_url( 'calypso-edit-posts', $args ), + esc_html__( 'Blog Posts', 'jetpack-masterbar' ), + 'wp-admin-bar-edit-post' + ); + } + + $wp_admin_bar->add_menu( + array( + 'parent' => 'publish', + 'id' => 'new-post', + 'title' => $posts_title, + 'meta' => array( + 'class' => 'inline-action mb-trackable', + ), + ) + ); + + // Comments. + if ( current_user_can( 'moderate_comments' ) ) { + $wp_admin_bar->add_menu( + array( + 'parent' => 'publish', + 'id' => 'comments', + 'title' => __( 'Comments', 'jetpack-masterbar' ), + 'href' => Redirect::get_url( 'calypso-comments', $args ), + 'meta' => array( + 'class' => 'mb-icon', + ), + ) + ); + } + + // Testimonials. + if ( ( new Modules() )->is_active( 'custom-content-types' ) && get_option( 'jetpack_testimonial' ) ) { + $testimonials_title = $this->create_menu_item_pair( + array( + 'url' => Redirect::get_url( 'calypso-list-jetpack-testimonial', $args ), + 'id' => 'wp-admin-bar-edit-testimonial', + 'label' => esc_html__( 'Testimonials', 'jetpack-masterbar' ), + ), + array( + 'url' => Redirect::get_url( 'calypso-edit-jetpack-testimonial', $args ), + 'id' => 'wp-admin-bar-new-testimonial', + 'label' => esc_html_x( 'Add', 'Button label for adding a new item via the toolbar menu', 'jetpack-masterbar' ), + ) + ); + + if ( ! current_user_can( 'edit_pages' ) ) { + $testimonials_title = $this->create_menu_item_anchor( + 'ab-item ab-primary mb-icon', + Redirect::get_url( 'calypso-list-jetpack-testimonial', $args ), + esc_html__( 'Testimonials', 'jetpack-masterbar' ), + 'wp-admin-bar-edit-testimonial' + ); + } + + $wp_admin_bar->add_menu( + array( + 'parent' => 'publish', + 'id' => 'new-jetpack-testimonial', + 'title' => $testimonials_title, + 'meta' => array( + 'class' => 'inline-action', + ), + ) + ); + } + + // Portfolio. + if ( ( new Modules() )->is_active( 'custom-content-types' ) && get_option( 'jetpack_portfolio' ) ) { + $portfolios_title = $this->create_menu_item_pair( + array( + 'url' => Redirect::get_url( 'calypso-list-jetpack-portfolio', $args ), + 'id' => 'wp-admin-bar-edit-portfolio', + 'label' => esc_html__( 'Portfolio', 'jetpack-masterbar' ), + ), + array( + 'url' => Redirect::get_url( 'calypso-edit-jetpack-portfolio', $args ), + 'id' => 'wp-admin-bar-new-portfolio', + 'label' => esc_html_x( 'Add', 'Button label for adding a new item via the toolbar menu', 'jetpack-masterbar' ), + ) + ); + + if ( ! current_user_can( 'edit_pages' ) ) { + $portfolios_title = $this->create_menu_item_anchor( + 'ab-item ab-primary mb-icon', + Redirect::get_url( 'calypso-list-jetpack-portfolio', $args ), + esc_html__( 'Portfolio', 'jetpack-masterbar' ), + 'wp-admin-bar-edit-portfolio' + ); + } + + $wp_admin_bar->add_menu( + array( + 'parent' => 'publish', + 'id' => 'new-jetpack-portfolio', + 'title' => $portfolios_title, + 'meta' => array( + 'class' => 'inline-action', + ), + ) + ); + } + + if ( current_user_can( 'edit_theme_options' ) ) { + // Look and Feel group. + $wp_admin_bar->add_group( + array( + 'parent' => 'blog', + 'id' => 'look-and-feel', + ) + ); + + // Look and Feel header. + $wp_admin_bar->add_menu( + array( + 'parent' => 'look-and-feel', + 'id' => 'look-and-feel-header', + 'title' => esc_html_x( 'Personalize', 'admin bar menu group label', 'jetpack-masterbar' ), + 'meta' => array( + 'class' => 'ab-submenu-header', + ), + ) + ); + + $request_uri = isset( $_SERVER['REQUEST_URI'] ) ? filter_var( wp_unslash( $_SERVER['REQUEST_URI'] ) ) : ''; + if ( is_admin() ) { + // In wp-admin the `return` query arg will return to that page after closing the Customizer. + $customizer_url = add_query_arg( + array( + 'return' => rawurlencode( site_url( $request_uri ) ), + ), + wp_customize_url() + ); + } else { + /* + * On the frontend the `url` query arg will load that page in the Customizer + * and also return to it after closing + * non-home URLs won't work unless we undo domain mapping + * since the Customizer preview is unmapped to always have HTTPS. + */ + $current_page = '//' . $this->primary_site_slug . $request_uri; + $customizer_url = add_query_arg( array( 'url' => rawurlencode( $current_page ) ), wp_customize_url() ); + } + + $theme_title = $this->create_menu_item_pair( + array( + 'url' => $customizer_url, + 'id' => 'wp-admin-bar-cmz', + 'label' => esc_html_x( 'Customize', 'admin bar customize item label', 'jetpack-masterbar' ), + ), + array( + 'url' => Redirect::get_url( 'calypso-themes', $args ), + 'id' => 'wp-admin-bar-themes', + 'label' => esc_html__( 'Themes', 'jetpack-masterbar' ), + ) + ); + $meta = array( + 'class' => 'mb-icon inline-action', + ); + $href = false; + + $wp_admin_bar->add_menu( + array( + 'parent' => 'look-and-feel', + 'id' => 'themes', + 'title' => $theme_title, + 'href' => $href, + 'meta' => $meta, + ) + ); + } + + if ( current_user_can( 'manage_options' ) ) { + // Configuration group. + $wp_admin_bar->add_group( + array( + 'parent' => 'blog', + 'id' => 'configuration', + ) + ); + + // Configuration header. + $wp_admin_bar->add_menu( + array( + 'parent' => 'configuration', + 'id' => 'configuration-header', + 'title' => esc_html_x( 'Configure', 'admin bar menu group label', 'jetpack-masterbar' ), + 'meta' => array( + 'class' => 'ab-submenu-header', + ), + ) + ); + + if ( ( new Modules() )->is_active( 'publicize' ) || ( new Modules() )->is_active( 'sharedaddy' ) ) { + $wp_admin_bar->add_menu( + array( + 'parent' => 'configuration', + 'id' => 'sharing', + 'title' => esc_html__( 'Sharing', 'jetpack-masterbar' ), + 'href' => Redirect::get_url( 'calypso-sharing', $args ), + 'meta' => array( + 'class' => 'mb-icon', + ), + ) + ); + } + + $people_title = $this->create_menu_item_pair( + array( + 'url' => Redirect::get_url( 'calypso-people-team', $args ), + 'id' => 'wp-admin-bar-people', + 'label' => esc_html__( 'People', 'jetpack-masterbar' ), + ), + array( + 'url' => admin_url( 'user-new.php' ), + 'id' => 'wp-admin-bar-people-add', + 'label' => esc_html_x( 'Add', 'admin bar people item label', 'jetpack-masterbar' ), + ) + ); + + $wp_admin_bar->add_menu( + array( + 'parent' => 'configuration', + 'id' => 'users-toolbar', + 'title' => $people_title, + 'href' => false, + 'meta' => array( + 'class' => 'inline-action', + ), + ) + ); + + $plugins_title = $this->create_menu_item_pair( + array( + 'url' => Redirect::get_url( 'calypso-plugins', $args ), + 'id' => 'wp-admin-bar-plugins', + 'label' => esc_html__( 'Plugins', 'jetpack-masterbar' ), + ), + array( + 'url' => Redirect::get_url( 'calypso-plugins-manage', $args ), + 'id' => 'wp-admin-bar-plugins-add', + 'label' => esc_html_x( 'Manage', 'Label for the button on the Masterbar to manage plugins', 'jetpack-masterbar' ), + ) + ); + + $wp_admin_bar->add_menu( + array( + 'parent' => 'configuration', + 'id' => 'plugins', + 'title' => $plugins_title, + 'href' => false, + 'meta' => array( + 'class' => 'inline-action', + ), + ) + ); + + if ( $this->site_woa ) { + $domain_title = $this->create_menu_item_pair( + array( + 'url' => Redirect::get_url( 'calypso-domains', $args ), + 'id' => 'wp-admin-bar-domains', + 'label' => esc_html__( 'Domains', 'jetpack-masterbar' ), + ), + array( + 'url' => Redirect::get_url( 'calypso-domains-add', $args ), + 'id' => 'wp-admin-bar-domains-add', + 'label' => esc_html_x( 'Add', 'Label for the button on the Masterbar to add a new domain', 'jetpack-masterbar' ), + ) + ); + $wp_admin_bar->add_menu( + array( + 'parent' => 'configuration', + 'id' => 'domains', + 'title' => $domain_title, + 'href' => false, + 'meta' => array( + 'class' => 'inline-action', + ), + ) + ); + } + + $wp_admin_bar->add_menu( + array( + 'parent' => 'configuration', + 'id' => 'blog-settings', + 'title' => esc_html__( 'Settings', 'jetpack-masterbar' ), + 'href' => Redirect::get_url( 'calypso-settings-general', $args ), + 'meta' => array( + 'class' => 'mb-icon', + ), + ) + ); + + if ( ! is_admin() ) { + $wp_admin_bar->add_menu( + array( + 'parent' => 'configuration', + 'id' => 'legacy-dashboard', + 'title' => esc_html__( 'Dashboard', 'jetpack-masterbar' ), + 'href' => admin_url(), + 'meta' => array( + 'class' => 'mb-icon', + ), + ) + ); + } + + // Restore dashboard menu toggle that is needed on mobile views. + if ( is_admin() ) { + $wp_admin_bar->add_menu( + array( + 'id' => 'menu-toggle', + 'title' => '' . esc_html__( 'Menu', 'jetpack-masterbar' ) . '', + 'href' => '#', + ) + ); + } + + /** + * Fires when menu items are added to the masterbar "My Sites" menu. + * + * @since jetpack-5.4.0 + */ + do_action( 'jetpack_masterbar' ); + } + } + + /** + * Adds "My Home" submenu item to sites that are eligible. + * + * @param WP_Admin_Bar $wp_admin_bar Admin Bar instance. + * @return void + */ + private function add_my_home_submenu_item( &$wp_admin_bar ) { + if ( ! current_user_can( 'manage_options' ) || ! $this->site_woa ) { + return; + } + + $wp_admin_bar->add_menu( + array( + 'parent' => 'blog', + 'id' => 'my-home', + 'title' => __( 'My Home', 'jetpack-masterbar' ), + 'href' => Redirect::get_url( 'calypso-home' ), + 'meta' => array( + 'class' => 'mb-icon', + ), + ) + ); + } +} diff --git a/projects/packages/masterbar/src/masterbar/overrides.css b/projects/packages/masterbar/src/masterbar/overrides.css new file mode 100644 index 0000000000000..33a3f883ac018 --- /dev/null +++ b/projects/packages/masterbar/src/masterbar/overrides.css @@ -0,0 +1,147 @@ +/* Remove min-height from menu elements that was causing them to render incorrectly */ +.my-sites li { + min-height: unset !important; +} + +/* Remove margin between icon and text since the my-sites menu does not have text */ +#wpadminbar .my-sites .ab-item:before { + margin-right: 0; +} + +/* Overwrite a core style which breaks the overflow for .my-sites in Safari */ +#wpadminbar li.menupop.my-sites { + overflow: visible; +} + +/* Add a focus style for menu items */ +.accessible-focus #wpadminbar li.menupop a.ab-item:focus, +.accessible-focus #wpadminbar li#wp-admin-bar-notes.menupop .ab-item:focus, +.accessible-focus #wpadminbar ul li#wp-admin-bar-ab-new-post a:focus { + -webkit-box-shadow: inset 2px 2px 0 #668eaa, + inset -2px -2px 0 #668eaa; + box-shadow: inset 2px 2px 0 #668eaa, + inset -2px -2px 0 #668eaa; +} + +/* Menu items in panels are inside `ab-empty-item` */ +.accessible-focus #wpadminbar li.menupop .ab-empty-item a.ab-item:focus, +.accessible-focus #wpadminbar li.menupop .ab-empty-item a.ab-secondary:focus, +.accessible-focus #wpadminbar li.menupop .ab-empty-item a.username:focus { + -webkit-box-shadow: inset 2px 2px 0 #2e4354, + inset -2px -2px 0 #2e4354; + box-shadow: inset 2px 2px 0 #2e4354, + inset -2px -2px 0 #2e4354; +} + +.accessible-focus #wpadminbar .quicklinks li#wp-admin-bar-my-account #wp-admin-bar-user-info .ab-sign-out:focus { + -webkit-box-shadow: inset 2px 2px 0 #2e4354, + inset -2px -2px 0 #2e4354 !important; + box-shadow: inset 2px 2px 0 #2e4354, + inset -2px -2px 0 #2e4354 !important; +} + +.accessible-focus #wpadminbar:not(.mobile) .ab-top-menu > li > .ab-item:focus { + background: transparent; +} + +/* Hide the panels initially */ +#wpadminbar li#wp-admin-bar-blog.menupop > .ab-sub-wrapper, /* My Sites */ +#wpadminbar li#wp-admin-bar-newdash.menupop > .ab-sub-wrapper, /* Reader */ +#wpadminbar li#wp-admin-bar-my-account.menupop > .ab-sub-wrapper, /* Me */ +#wpadminbar li#wp-admin-bar-notes.menupop > #wpnt-notes-panel2 { /* Notifications */ + display: block !important; +} + +/* Change notification icon the match the one on WP.com */ +#wp-admin-bar-notes .noticon-bell:before { + content: url("data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyNCAyNCIgd2lkdGg9IjI0IiBoZWlnaHQ9IjI0Ij48cmVjdCB4PSIwIiBmaWxsPSJub25lIiB3aWR0aD0iMjQiIGhlaWdodD0iMjQiLz48Zz48cGF0aCBmaWxsPSIjZmZmZmZmIiBkPSJNNi4xNCAxNC45N2wyLjgyOCAyLjgyN2MtLjM2Mi4zNjItLjg2Mi41ODYtMS40MTQuNTg2LTEuMTA1IDAtMi0uODk1LTItMiAwLS41NTIuMjI0LTEuMDUyLjU4Ni0xLjQxNHptOC44NjcgNS4zMjRMMTQuMyAyMSAzIDkuN2wuNzA2LS43MDcgMS4xMDIuMTU3Yy43NTQuMTA4IDEuNjktLjEyMiAyLjA3Ny0uNTFsMy44ODUtMy44ODRjMi4zNC0yLjM0IDYuMTM1LTIuMzQgOC40NzUgMHMyLjM0IDYuMTM1IDAgOC40NzVsLTMuODg1IDMuODg2Yy0uMzg4LjM4OC0uNjE4IDEuMzIzLS41MSAyLjA3N2wuMTU3IDEuMXoiLz48L2c+PC9zdmc+") !important; +} +#wp-admin-bar-notes.active .noticon-bell:before { + content: url("data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyNCAyNCIgd2lkdGg9IjI0IiBoZWlnaHQ9IjI0Ij48cmVjdCB4PSIwIiBmaWxsPSJub25lIiB3aWR0aD0iMjQiIGhlaWdodD0iMjQiLz48Zz48cGF0aCBmaWxsPSIjMjMyODJkIiBkPSJNNi4xNCAxNC45N2wyLjgyOCAyLjgyN2MtLjM2Mi4zNjItLjg2Mi41ODYtMS40MTQuNTg2LTEuMTA1IDAtMi0uODk1LTItMiAwLS41NTIuMjI0LTEuMDUyLjU4Ni0xLjQxNHptOC44NjcgNS4zMjRMMTQuMyAyMSAzIDkuN2wuNzA2LS43MDcgMS4xMDIuMTU3Yy43NTQuMTA4IDEuNjktLjEyMiAyLjA3Ny0uNTFsMy44ODUtMy44ODRjMi4zNC0yLjM0IDYuMTM1LTIuMzQgOC40NzUgMHMyLjM0IDYuMTM1IDAgOC40NzVsLTMuODg1IDMuODg2Yy0uMzg4LjM4OC0uNjE4IDEuMzIzLS41MSAyLjA3N2wuMTU3IDEuMXoiLz48L2c+PC9zdmc+") !important; +} + +/* Fit width of sign out button to content */ +#wpadminbar .quicklinks li#wp-admin-bar-my-account #wp-admin-bar-user-info .ab-sign-out { + display: inline-block; +} + +/* Move the admin menu toggle in Gutenberg - https://github.com/Automattic/jetpack/issues/12320 */ +.jetpack-masterbar.post-new-php.block-editor-page #wpadminbar #wp-admin-bar-ab-new-post { + display: none; +} + +.jetpack-masterbar.post-new-php.block-editor-page #wpadminbar #wp-admin-bar-menu-toggle { + top: -4px; + position: relative; +} + +.jetpack-masterbar.post-new-php.block-editor-page #wpadminbar #wp-admin-bar-menu-toggle .ab-icon:before { + color: #fff !important; + font-size: 28px; +} + +.jetpack-masterbar #wpadminbar #wp-admin-bar-recovery-mode { + background-color: #d63638; + color: #fff; + margin-right: 1em; +} +#wpadminbar #wp-admin-bar-jetpack-scan-notice { + margin-right: 1em; +} +@media screen and (max-width: 959px ) { + #wpadminbar #wp-admin-bar-jetpack-scan-notice { + width:32px; + } + #wpadminbar #wp-admin-bar-jetpack-scan-notice a { color: transparent!important;} +} + +@media screen and (max-width: 480px) { + .jetpack-masterbar.post-new-php.block-editor-page #wp-toolbar ul li { + flex: 1; + width: auto !important; + } + + .jetpack-masterbar.post-new-php.block-editor-page #wpadminbar ul#wp-admin-bar-root-default { + width: 60%; + } + + .jetpack-masterbar.post-new-php.block-editor-page #wpadminbar ul#wp-admin-bar-top-secondary { + width: 40%; + } + + .wp-admin.jetpack-masterbar.post-new-php.block-editor-page .wp-responsive-open #wpadminbar #wp-admin-bar-menu-toggle { + left: 0; + } +} + +@media screen and (max-width: 782px) { + .wp-admin.jetpack-masterbar.post-new-php.block-editor-page .wp-responsive-open #wpadminbar #wp-admin-bar-menu-toggle { + left: 0 !important; + } + + .jetpack-masterbar.post-new-php.block-editor-page #wp-toolbar, + .jetpack-masterbar.post-new-php.block-editor-page #wp-toolbar ul { + display: flex; + } + + .jetpack-masterbar.post-new-php.block-editor-page #wpadminbar ul#wp-admin-bar-root-default { + flex-grow: 1; + } + + .jetpack-masterbar.post-new-php.block-editor-page #wpadminbar li#wp-admin-bar-menu-toggle { + order: 1; + } + + .jetpack-masterbar.post-new-php.block-editor-page #wpadminbar li#wp-admin-bar-blog { + order: 2; + } + + .jetpack-masterbar.post-new-php.block-editor-page #wpadminbar li#wp-admin-bar-newdash { + order: 3; + } + + #wpadminbar #wp-admin-bar-jetpack-scan-notice, + .jetpack-masterbar #wpadminbar #wp-admin-bar-recovery-mode { + display: none; + } +} diff --git a/projects/packages/masterbar/src/masterbar/overrides.js b/projects/packages/masterbar/src/masterbar/overrides.js new file mode 100644 index 0000000000000..7b9468ffd486d --- /dev/null +++ b/projects/packages/masterbar/src/masterbar/overrides.js @@ -0,0 +1 @@ +import './overrides.css'; diff --git a/projects/packages/masterbar/src/masterbar/tracks-events.js b/projects/packages/masterbar/src/masterbar/tracks-events.js new file mode 100644 index 0000000000000..7d81a89244e9c --- /dev/null +++ b/projects/packages/masterbar/src/masterbar/tracks-events.js @@ -0,0 +1,242 @@ +/* eslint-disable jsdoc/require-returns,jsdoc/require-description,jsdoc/require-param-description, jsdoc/require-param-type */ + +( function () { + // eslint-disable-next-line strict + 'use strict'; + + const eventName = 'masterbar_click'; + + const linksTracksEvents = { + //top level items + 'wp-admin-bar-blog': 'my_sites', + 'wp-admin-bar-newdash': 'reader', + 'wp-admin-bar-ab-new-post': 'write_button', + 'wp-admin-bar-my-account': 'my_account', + 'wp-admin-bar-notes': 'notifications', + //my sites - top items + 'wp-admin-bar-switch-site': 'my_sites_switch_site', + 'wp-admin-bar-blog-info': 'my_sites_blog_info', + 'wp-admin-bar-site-view': 'my_sites_view_site', + 'wp-admin-bar-my-home': 'my_sites_my_home', + 'wp-admin-bar-blog-stats': 'my_sites_blog_stats', + 'wp-admin-bar-activity': 'my_sites_activity', + 'wp-admin-bar-plan': 'my_sites_plan', + 'wp-admin-bar-plan-badge': 'my_sites_plan_badge', + //my sites - manage + 'wp-admin-bar-edit-page': 'my_sites_manage_site_pages', + 'wp-admin-bar-new-page-badge': 'my_sites_manage_add_page', + 'wp-admin-bar-edit-post': 'my_sites_manage_blog_posts', + 'wp-admin-bar-new-post-badge': 'my_sites_manage_add_new_post', + 'wp-admin-bar-edit-attachment': 'my_sites_manage_media', + 'wp-admin-bar-new-attachment-badge': 'my_sites_manage_add_media', + 'wp-admin-bar-comments': 'my_sites_manage_comments', + 'wp-admin-bar-edit-testimonial': 'my_sites_manage_testimonials', + 'wp-admin-bar-new-testimonial': 'my_sites_manage_add_testimonial', + 'wp-admin-bar-edit-portfolio': 'my_sites_manage_portfolio', + 'wp-admin-bar-new-portfolio': 'my_sites_manage_add_portfolio', + //my sites - personalize + 'wp-admin-bar-themes': 'my_sites_personalize_themes', + 'wp-admin-bar-cmz': 'my_sites_personalize_themes_customize', + //my sites - configure + 'wp-admin-bar-sharing': 'my_sites_configure_sharing', + 'wp-admin-bar-people': 'my_sites_configure_people', + 'wp-admin-bar-people-add': 'my_sites_configure_people_add_button', + 'wp-admin-bar-plugins': 'my_sites_configure_plugins', + 'wp-admin-bar-plugins-add': 'my_sites_configure_manage_plugins', + 'wp-admin-bar-blog-settings': 'my_sites_configure_settings', + //reader + 'wp-admin-bar-followed-sites': 'reader_followed_sites', + 'wp-admin-bar-reader-followed-sites-manage': 'reader_manage_followed_sites', + 'wp-admin-bar-discover-discover': 'reader_discover', + 'wp-admin-bar-discover-search': 'reader_search', + 'wp-admin-bar-my-activity-my-likes': 'reader_my_likes', + //account + 'wp-admin-bar-user-info': 'my_account_user_name', + // account - profile + 'wp-admin-bar-my-profile': 'my_account_profile_my_profile', + 'wp-admin-bar-account-settings': 'my_account_profile_account_settings', + 'wp-admin-bar-billing': 'my_account_profile_manage_purchases', + 'wp-admin-bar-security': 'my_account_profile_security', + 'wp-admin-bar-notifications': 'my_account_profile_notifications', + //account - special + 'wp-admin-bar-get-apps': 'my_account_special_get_apps', + 'wp-admin-bar-next-steps': 'my_account_special_next_steps', + 'wp-admin-bar-help': 'my_account_special_help', + }; + + const notesTracksEvents = { + openSite: function ( data ) { + return { + clicked: 'masterbar_notifications_panel_site', + site_id: data.siteId, + }; + }, + openPost: function ( data ) { + return { + clicked: 'masterbar_notifications_panel_post', + site_id: data.siteId, + post_id: data.postId, + }; + }, + openComment: function ( data ) { + return { + clicked: 'masterbar_notifications_panel_comment', + site_id: data.siteId, + post_id: data.postId, + comment_id: data.commentId, + }; + }, + }; + + /** + * + * @param s + * @param defaultValue + */ + function parseJson( s, defaultValue ) { + try { + return JSON.parse( s ); + } catch ( e ) { + return defaultValue; + } + } + + // Element.prototype.matches as a standalone function, with old browser fallback + /** + * + * @param node + * @param selector + */ + function matches( node, selector ) { + if ( ! node ) { + return undefined; + } + + if ( ! Element.prototype.matches && ! Element.prototype.msMatchesSelector ) { + throw new Error( 'Unsupported browser' ); + } + + return Element.prototype.matches + ? node.matches( selector ) + : node.msMatchesSelector( selector ); + } + + // Element.prototype.closest as a standalone function, with old browser fallback + /** + * + * @param node + * @param selector + */ + function closest( node, selector ) { + if ( ! node ) { + return undefined; + } + + if ( Element.prototype.closest ) { + return node.closest( selector ); + } + + do { + if ( matches( node, selector ) ) { + return node; + } + + node = node.parentElement || node.parentNode; + } while ( node !== null && node.nodeType === 1 ); + + return null; + } + + /** + * + */ + function createTrackableLinkEventHandler() { + return function ( e ) { + if ( ! window.jpTracksAJAX || typeof window.jpTracksAJAX.record_ajax_event !== 'function' ) { + return; + } + + let target = e.target; + const parent = closest( target, 'li' ); + + if ( ! matches( target, 'a' ) ) { + target = closest( target, 'a' ); + } + + if ( ! parent || ! target ) { + return; + } + + const trackingId = target.getAttribute( 'ID' ) || parent.getAttribute( 'ID' ); + + if ( ! Object.prototype.hasOwnProperty.call( linksTracksEvents, trackingId ) ) { + return; + } + const eventProps = { clicked: linksTracksEvents[ trackingId ] }; + + if ( parent.classList.contains( 'menupop' ) ) { + window.jpTracksAJAX.record_ajax_event( eventName, 'click', eventProps ); + } else { + e.preventDefault(); + window.jpTracksAJAX + .record_ajax_event( eventName, 'click', eventProps ) + .always( function () { + window.location = target.getAttribute( 'href' ); + } ); + } + }; + } + + /** + * + */ + function init() { + const trackableLinkSelector = + '.mb-trackable .ab-item:not(div),' + + '#wp-admin-bar-notes .ab-item,' + + '#wp-admin-bar-user-info .ab-item,' + + '.mb-trackable .ab-secondary'; + + const trackableLinks = document.querySelectorAll( trackableLinkSelector ); + for ( let i = 0; i < trackableLinks.length; i++ ) { + const link = trackableLinks[ i ]; + const handler = createTrackableLinkEventHandler(); + + link.addEventListener( 'click', handler ); + link.addEventListener( 'touchstart', handler ); + } + } + + if ( document.readyState === 'loading' ) { + document.addEventListener( 'DOMContentLoaded', init ); + } else { + init(); + } + + // listen for postMessage events from the notifications iframe + window.addEventListener( + 'message', + function ( event ) { + if ( ! window.jpTracksAJAX || typeof window.jpTracksAJAX.record_ajax_event !== 'function' ) { + return; + } + + if ( event.origin !== 'https://widgets.wp.com' ) { + return; + } + + const data = typeof event.data === 'string' ? parseJson( event.data, {} ) : event.data; + if ( data.type !== 'notesIframeMessage' ) { + return; + } + + const eventData = notesTracksEvents[ data.action ]; + if ( ! eventData ) { + return; + } + + window.jpTracksAJAX.record_ajax_event( eventName, 'click', eventData( data ) ); + }, + false + ); +} )(); diff --git a/projects/packages/masterbar/src/nudges/additional-css/additional-css.css b/projects/packages/masterbar/src/nudges/additional-css/additional-css.css new file mode 100644 index 0000000000000..14fd9af9cbd26 --- /dev/null +++ b/projects/packages/masterbar/src/nudges/additional-css/additional-css.css @@ -0,0 +1,13 @@ +.customize-control-cssNudge .nudge-container { + width: 100%; + padding: 50px 0; +} + +.customize-control-cssNudge .nudge-container p { + text-align: center; +} + +.customize-control-cssNudge .nudge-container .button-container { + margin-top: 20px; + text-align: center; +} diff --git a/projects/packages/masterbar/src/nudges/additional-css/additional-css.js b/projects/packages/masterbar/src/nudges/additional-css/additional-css.js new file mode 100644 index 0000000000000..eb0ddc5d0b8b8 --- /dev/null +++ b/projects/packages/masterbar/src/nudges/additional-css/additional-css.js @@ -0,0 +1,61 @@ +import './additional-css.css'; + +( function ( $ ) { + // eslint-disable-next-line strict + 'use strict'; + + const cssNudge = { + init: function () { + this.clickifyNavigateToButtons(); + }, + + clickifyNavigateToButtons: function () { + const navButton = document.querySelector( '.navigate-to' ); + if ( ! navButton ) { + return; + } + + navButton.addEventListener( 'click', function () { + // Get destination. + const destination = this.getAttribute( 'data-navigate-to-page' ); + + if ( ! destination ) { + return; + } + + // Fire Tracks click event. + window._tkq = window._tkq || []; + window._tkq.push( [ + 'recordEvent', + 'calypso_upgrade_nudge_cta_click', + { + cta_name: 'customizer_css', + }, + ] ); + + // Navigate to a different page. + if ( + window.location.search.match( /calypso=true/ ) && + window.parent.location !== window.location + ) { + // Calypso. + window.top.postMessage( + JSON.stringify( { + calypso: true, + command: 'navigateTo', + destination: destination, + } ), + '*' + ); + } else { + // Non-Calypso. + window.location = 'https://wordpress.com' + destination; + } + } ); + }, + }; + + $( document ).ready( function () { + cssNudge.init(); + } ); +} )( jQuery ); diff --git a/projects/packages/masterbar/src/nudges/additional-css/class-atomic-additional-css-manager.php b/projects/packages/masterbar/src/nudges/additional-css/class-atomic-additional-css-manager.php new file mode 100644 index 0000000000000..2ff6b84f5f059 --- /dev/null +++ b/projects/packages/masterbar/src/nudges/additional-css/class-atomic-additional-css-manager.php @@ -0,0 +1,61 @@ +domain = $domain; + } + + /** + * Replace the Additional CSS section from Customiz¡er with an upgrade nudge. + * + * @param \WP_Customize_Manager $wp_customize_manager Core customize manager. + */ + public function register_nudge( \WP_Customize_Manager $wp_customize_manager ) { + $nudge_url = $this->get_nudge_url(); + $nudge_text = __( 'Purchase the Creator plan to
activate CSS customization', 'jetpack-masterbar' ); + + $nudge = new CSS_Customizer_Nudge( + $nudge_url, + $nudge_text + ); + + $wp_customize_manager->remove_control( 'custom_css' ); + $wp_customize_manager->remove_section( 'custom_css' ); + + $nudge->customize_register_nudge( $wp_customize_manager ); + } + + /** + * Get the Nudge URL. + * + * @return string + */ + private function get_nudge_url() { + return '/checkout/' . $this->domain . '/business'; + } +} diff --git a/projects/packages/masterbar/src/nudges/additional-css/class-css-customizer-nudge.php b/projects/packages/masterbar/src/nudges/additional-css/class-css-customizer-nudge.php new file mode 100644 index 0000000000000..ecd9ae94cb09d --- /dev/null +++ b/projects/packages/masterbar/src/nudges/additional-css/class-css-customizer-nudge.php @@ -0,0 +1,129 @@ +cta_url = $cta_url; + $this->nudge_copy = $nudge_copy; + $this->control_name = $control_name; + } + + /** + * Register the assets required for the CSS nudge page from the Customizer. + */ + public function customize_controls_enqueue_scripts_nudge() { + $assets_base_path = '../../../dist/nudges/additional-css/'; + + Assets::register_script( + 'additional-css-js', + $assets_base_path . 'additional-css.js', + __FILE__, + array( + 'enqueue' => true, + 'css_path' => $assets_base_path . 'additional-css.css', + ) + ); + } + + /** + * Register the CSS nudge in the Customizer. + * + * @param \WP_Customize_Manager $wp_customize The customize manager. + */ + public function customize_register_nudge( \WP_Customize_Manager $wp_customize ) { + // Show a nudge in place of the normal CSS section. + \add_action( 'customize_controls_enqueue_scripts', array( $this, 'customize_controls_enqueue_scripts_nudge' ) ); + + $wp_customize->add_setting( + $this->control_name . '[dummy_setting]', + array( + 'type' => $this->control_name . '_dummy_setting', + 'default' => '', + 'transport' => 'refresh', + ) + ); + + $wp_customize->add_section( $this->create_css_nudge_section( $wp_customize ) ); + + $wp_customize->add_control( $this->create_css_nudge_control( $wp_customize ) ); + } + + /** + * Create a nudge control object. + * + * @param \WP_Customize_Manager $wp_customize The Core Customize Manager. + * + * @return CSS_Nudge_Customize_Control + */ + public function create_css_nudge_control( \WP_Customize_Manager $wp_customize ) { + return new CSS_Nudge_Customize_Control( + $wp_customize, + $this->control_name . '_control', + array( + 'cta_url' => $this->cta_url, + 'nudge_copy' => $this->nudge_copy, + 'label' => __( 'Custom CSS', 'jetpack-masterbar' ), + 'section' => $this->control_name, + 'settings' => $this->control_name . '[dummy_setting]', + ) + ); + } + + /** + * Create the nudge section. + * + * @param \WP_Customize_Manager $wp_customize The core Customize Manager. + * + * @return \WP_Customize_Section + */ + public function create_css_nudge_section( \WP_Customize_Manager $wp_customize ) { + return new \WP_Customize_Section( + $wp_customize, + $this->control_name, + array( + 'title' => __( 'Additional CSS', 'jetpack-masterbar' ), + 'priority' => 200, + ) + ); + } +} diff --git a/projects/packages/masterbar/src/nudges/additional-css/class-css-nudge-customize-control.php b/projects/packages/masterbar/src/nudges/additional-css/class-css-nudge-customize-control.php new file mode 100644 index 0000000000000..f5d4acad169d8 --- /dev/null +++ b/projects/packages/masterbar/src/nudges/additional-css/class-css-nudge-customize-control.php @@ -0,0 +1,54 @@ +cta_url; + $nudge_copy = $this->nudge_copy; + $nudge_button_copy = __( 'Upgrade now', 'jetpack-masterbar' ); + + echo '
+

+ ' . wp_kses( $nudge_copy, array( 'br' => array() ) ) . ' +

+
+ +
+
'; + } +} diff --git a/projects/packages/masterbar/src/nudges/additional-css/class-wpcom-additional-css-manager.php b/projects/packages/masterbar/src/nudges/additional-css/class-wpcom-additional-css-manager.php new file mode 100644 index 0000000000000..69d4af9caae30 --- /dev/null +++ b/projects/packages/masterbar/src/nudges/additional-css/class-wpcom-additional-css-manager.php @@ -0,0 +1,59 @@ +domain = $domain; + } + + /** + * Register the Additional CSS nudge. + * + * @param \WP_Customize_Manager $wp_customize_manager The core customize manager. + */ + public function register_nudge( \WP_Customize_Manager $wp_customize_manager ) { + $nudge_url = $this->get_nudge_url(); + $nudge_text = __( 'Purchase the Explorer plan to
activate CSS customization', 'jetpack-masterbar' ); + + $nudge = new CSS_Customizer_Nudge( + $nudge_url, + $nudge_text, + 'jetpack_custom_css' + ); + + $nudge->customize_register_nudge( $wp_customize_manager ); + } + + /** + * Get the nudge URL in WPCOM. + * + * @return string + */ + private function get_nudge_url() { + return '/checkout/' . $this->domain . '/premium'; + } +} diff --git a/projects/packages/masterbar/src/nudges/bootstrap.php b/projects/packages/masterbar/src/nudges/bootstrap.php new file mode 100644 index 0000000000000..e0d8ee1fa72a0 --- /dev/null +++ b/projects/packages/masterbar/src/nudges/bootstrap.php @@ -0,0 +1,55 @@ +get_site_suffix(); + + if ( defined( 'IS_WPCOM' ) && IS_WPCOM ) { + $manager = new WPCOM_Additional_CSS_Manager( $domain ); + } elseif ( ( new Host() )->is_woa_site() ) { + $manager = new Atomic_Additional_CSS_Manager( $domain ); + } + + if ( ! isset( $manager ) ) { + return; + } + + $manager->register_nudge( $customize_manager ); +} + +/** + * Load the bootstrap on init action. + * + * We need to load on init because otherwise the filter will not be set to true in WPCOM (since the add_filter is set on init). + */ +function load_bootstrap_on_init() { + + /** + * Disable Additional CSS section from Customizer in WPCOM and Atomic and replace it with a nudge. + * + * @module masterbar + * + * @since jetpack-9.9.0 + * + * @param bool + */ + if ( \apply_filters( 'jetpack_customize_enable_additional_css_nudge', false ) ) { + \add_action( 'customize_register', __NAMESPACE__ . '\register_css_nudge_control' ); + } +} + +add_action( 'init', __NAMESPACE__ . '\load_bootstrap_on_init' ); diff --git a/projects/packages/masterbar/src/profile-edit/bootstrap.php b/projects/packages/masterbar/src/profile-edit/bootstrap.php new file mode 100644 index 0000000000000..11cf49b27a270 --- /dev/null +++ b/projects/packages/masterbar/src/profile-edit/bootstrap.php @@ -0,0 +1,23 @@ +connection_manager = $connection_manager; + + \add_filter( 'wp_pre_insert_user_data', array( $this, 'revert_user_data_on_wp_admin_profile_update' ), 10, 3 ); + \add_filter( 'insert_user_meta', array( $this, 'revert_user_meta_on_wp_admin_profile_change' ), 10, 3 ); + + /** + * Core sends two E-mail notifications that have to be disabled: + * - To the existing e-mail address + * - To the new email address + */ + \add_filter( 'send_email_change_email', array( $this, 'disable_send_email_change_email' ), 10, 3 ); + \add_action( 'personal_options_update', array( $this, 'disable_email_notification' ), 1, 1 ); + } + + /** + * Filter the built-in user profile fields. + * + * @param array $data { + * Values and keys for the user. + * + * @type string $user_login The user's login. Only included if $update == false + * @type string $user_pass The user's password. + * @type string $user_email The user's email. + * @type string $user_url The user's url. + * @type string $user_nicename The user's nice name. Defaults to a URL-safe version of user's login + * @type string $display_name The user's display name. + * @type string $user_registered MySQL timestamp describing the moment when the user registered. Defaults to + * the current UTC timestamp. + * } + * + * @param bool $update Whether the user is being updated rather than created. + * @param int|null $id ID of the user to be updated, or NULL if the user is being created. + * + * @return array + */ + public function revert_user_data_on_wp_admin_profile_update( $data, $update, $id ) { + + // bail if the id is null, meaning that this was triggered in the context of user create. + // bail if the user is not connected (e.g. non-WP.com users or disconnected users). + if ( ! $update || null === $id || ! $this->connection_manager->is_user_connected( $id ) ) { + return $data; + } + + /** + * Revert the data in the form submission with the data from the database. + */ + $user = \get_userdata( $id ); + + /** + * E-mail has a different flow for changing it's value. It stores it in an option until the user confirms it via e-mail. + * Based on this, it displays in the UI a section mentioning the e-mail pending change. + * We hide the entire section, but we should also clean it up just in case. + */ + \delete_user_meta( $id, '_new_email' ); + + $data['user_email'] = $user->user_email; + $data['user_url'] = $user->user_url; + $data['user_nicename'] = $user->user_nicename; + $data['display_name'] = $user->display_name; + + return $data; + } + + /** + * Revert the first_name, last_name and description since this is managed by WP.com. + * + * @param array $meta { + * Default meta values and keys for the user. + * + * @type string $nickname The user's nickname. Default is the user's username. + * @type string $first_name The user's first name. + * @type string $last_name The user's last name. + * @type string $description The user's description. + * @type string $rich_editing Whether to enable the rich-editor for the user. Default 'true'. + * @type string $syntax_highlighting Whether to enable the rich code editor for the user. Default 'true'. + * @type string $comment_shortcuts Whether to enable keyboard shortcuts for the user. Default 'false'. + * @type string $admin_color The color scheme for a user's admin screen. Default 'fresh'. + * @type int|bool $use_ssl Whether to force SSL on the user's admin area. 0|false if SSL + * is not forced. + * @type string $show_admin_bar_front Whether to show the admin bar on the front end for the user. + * Default 'true'. + * @type string $locale User's locale. Default empty. + * } + * @param \WP_User $user User object. + * @param bool $update Whether the user is being updated rather than created. + * + * @return array + */ + public function revert_user_meta_on_wp_admin_profile_change( $meta, $user, $update ) { + + // bail if not in update context. + if ( ! $update || ! $this->connection_manager->is_user_connected( $user->ID ) ) { + return $meta; + } + + /** + * Revert the data in the form submission with the data from the database. + */ + $database_user = \get_userdata( $user->ID ); + + $meta['first_name'] = $database_user->first_name; + $meta['last_name'] = $database_user->last_name; + $meta['description'] = $database_user->description; + $meta['nickname'] = $database_user->nickname; + + return $meta; + } + + /** + * Disable the e-mail notification. + * + * @param bool $send Whether to send or not the email. + * @param array $user User data. + */ + public function disable_send_email_change_email( $send, $user ) { + if ( ! isset( $user['ID'] ) || ! $this->connection_manager->is_user_connected( $user['ID'] ) ) { + return $send; + } + + return false; + } + + /** + * Disable notification on E-mail changes for Atomic WP-Admin Edit Profile. (for WP.com we use a different section for changing the E-mail). + * + * We need this because WP.org uses a custom flow for E-mail changes. + * + * @param int $user_id The id of the user that's updated. + */ + public function disable_email_notification( $user_id ) { + // Don't remove the notification for non-WP.com connected users. + if ( ! $this->connection_manager->is_user_connected( $user_id ) ) { + return; + } + + \remove_action( 'personal_options_update', 'send_confirmation_on_profile_email' ); + } +} diff --git a/projects/packages/masterbar/src/profile-edit/profile-edit.php b/projects/packages/masterbar/src/profile-edit/profile-edit.php new file mode 100644 index 0000000000000..85d1facc76199 --- /dev/null +++ b/projects/packages/masterbar/src/profile-edit/profile-edit.php @@ -0,0 +1,65 @@ +is_user_connected( $user->ID ) ) { + // If this is a local user, show the default UX. + return; + } + $wp_kses_rule = array( + 'a' => array( + 'href' => array(), + 'rel' => array(), + 'target' => array(), + ), + ); + // Since there is no hook for altering profile fields, we will use CSS and JS. + $name_info_wpcom_link_message = sprintf( + /* translators: 1 link */ + __( 'WordPress.com users can change their profile’s basic details ( i.e., First Name, Last Name, Display Name, About ) in WordPress.com Profile settings.', 'jetpack-masterbar' ), + 'https://wordpress.com/me' + ); + $contact_info_wpcom_link_message = sprintf( + /* translators: 1 link */ + __( 'WordPress.com users can change their profile’s email & website address in WordPress.com Account settings.', 'jetpack-masterbar' ), + 'https://wordpress.com/me/account' + ); + ?> + + posts_page_id = '' === $posts_page_id ? null : (int) $posts_page_id; + } + + /** + * Add in all hooks. + */ + public function init_actions() { + \add_filter( 'map_meta_cap', array( $this, 'disable_posts_page' ), 10, 4 ); + \add_filter( 'post_class', array( $this, 'add_posts_page_css_class' ), 10, 3 ); + \add_action( 'admin_print_footer_scripts-edit.php', array( $this, 'add_notification_icon' ) ); + \add_action( 'admin_enqueue_scripts', array( $this, 'enqueue_css' ) ); + } + + /** + * Creates instance. + * + * @return Posts_List_Page_Notification + */ + public static function init() { + if ( self::$instance === null ) { + self::$instance = new self( \get_option( 'page_for_posts' ), \get_option( 'show_on_front' ), \get_option( 'page_on_front' ) ); + } + + return self::$instance; + } + + /** + * Disable editing and deleting for the page that is configured as a Posts Page. + * + * @param array $caps Array of capabilities. + * @param string $cap The current capability. + * @param string $user_id The user id. + * @param array $args Argument array. + * @return array + */ + public function disable_posts_page( $caps, $cap, $user_id, $args ) { + if ( 'edit_post' !== $cap && 'delete_post' !== $cap ) { + return $caps; + } + + if ( isset( $args[0] ) && $this->posts_page_id === (int) $args[0] ) { + $caps[] = 'do_not_allow'; + } + + return $caps; + } + + /** + * Load the CSS for the WP Posts List + * + * We would probably need to move this elsewhere when new features are introduced to wp-posts-list. + */ + public function enqueue_css() { + $assets_base_path = '../../dist/wp-posts-list/'; + + Assets::register_script( + 'wp-posts-list', + $assets_base_path . 'wp-posts-list.js', + __FILE__, + array( + 'enqueue' => true, + 'css_path' => $assets_base_path . 'wp-posts-list.css', + ) + ); + } + + /** + * Adds a CSS class on the page configured as a Posts Page. + * + * @param array $classes A list of CSS classes. + * @param string $class A CSS class. + * @param string $post_id The current post id. + * @return array + */ + public function add_posts_page_css_class( $classes, $class, $post_id ) { + if ( $this->posts_page_id !== (int) $post_id ) { + return $classes; + } + + $this->is_page_in_list = true; + + $classes[] = 'posts-page'; + + return $classes; + } + + /** + * Add a info icon on the Posts Page letting the user know why they cannot delete and remove the page. + */ + public function add_notification_icon() { + // No need to add the JS since the site is not configured with a Posts Page or the current listview doesn't contain the page. + if ( null === $this->posts_page_id || ! $this->is_page_in_list ) { + return; + } + + $text_notice = __( 'The content of your latest posts page is automatically generated and cannot be edited.', 'jetpack-masterbar' ); + ?> + + array( + 'Dashboard', + 'read', + 'index.php', + '', + 'menu-top menu-top-first menu-icon-dashboard', + 'menu-dashboard', + 'dashicons-dashboard', + ), + 3 => array( + 'Jetpack', + 'jetpack_admin_page', + 'jetpack', + 'Jetpack', + 'menu-top toplevel_page_jetpack', + 'toplevel_page_jetpack', + 'div', + ), + 4 => array( + '', + 'read', + 'separator1', + '', + 'wp-menu-separator', + ), + 10 => array( + 'Media', + 'upload_files', + 'upload.php', + '', + 'menu-top menu-icon-media', + 'menu-media', + 'dashicons-admin-media', + ), + 15 => array( + 'Links', + 'manage_links', + 'edit-tags.php?taxonomy=link_category', + '', + 'menu-top menu-icon-links', + 'menu-links', + 'dashicons-admin-links', + ), + 25 => array( + 'Comments 3 Comments in moderation', + 'edit_posts', + 'edit-comments.php', + '', + 'menu-top menu-icon-comments', + 'menu-comments', + 'dashicons-admin-comments', + ), + 5 => array( + 'Posts', + 'edit_posts', + 'edit.php', + '', + 'menu-top menu-icon-post open-if-no-js', + 'menu-posts', + 'dashicons-admin-post', + ), + 20 => array( + 'Pages', + 'edit_pages', + 'edit.php?post_type=page', + '', + 'menu-top menu-icon-page', + 'menu-pages', + 'dashicons-admin-page', + ), + 30 => array( + 'Custom Test Types', + 'edit_posts', + 'edit.php?post_type=custom_test_type', + '', + 'menu-top menu-icon-page', + 'menu-custom_test_type', + 'dashicons-admin-post', + ), + 58 => array( + '', + 'read', + 'separator2', + '', + 'wp-menu-separator', + ), + 60 => array( + 'Appearance', + 'switch_themes', + 'themes.php', + '', + 'menu-top menu-icon-appearance', + 'menu-appearance', + 'dashicons-admin-appearance', + ), + 65 => array( + 'Plugins 4', + 'activate_plugins', + 'plugins.php', + '', + 'menu-top menu-icon-plugins', + 'menu-plugins', + 'dashicons-admin-plugins', + ), + 70 => array( + 'Users 0', + 'list_users', + 'users.php', + '', + 'menu-top menu-icon-users', + 'menu-users', + 'dashicons-admin-users', + ), + 75 => array( + 'Tools', + 'edit_posts', + 'tools.php', + '', + 'menu-top menu-icon-tools', + 'menu-tools', + 'dashicons-admin-tools', + ), + 80 => array( + 'Settings', + 'manage_options', + 'options-general.php', + '', + 'menu-top menu-icon-settings', + 'menu-settings', + 'dashicons-admin-settings', + ), + 100 => array( + 'Site Editor beta', + 'edit_theme_options', + 'gutenberg-edit-site', + 'Site Editor (beta)', + 'menu-top toplevel_page_gutenberg-edit-site', + 'toplevel_page_gutenberg-edit-site', + 'dashicons-layout', + ), + ); +} + +/** + * WPCom Menu fixture data. + * + * @return string[][] + */ +function get_wpcom_menu_fixture() { + $gutenberg_menus = array( + 101 => array( + 'Gutenberg', + 'edit_posts', + 'gutenberg', + 'Gutenberg', + 'menu-top toplevel_page_gutenberg', + 'toplevel_page_gutenberg', + 'dashicons-edit', + ), + ); + return get_menu_fixture() + $gutenberg_menus; +} + +/** + * Submenu fixture data. + * + * @return string[][][] + */ +function get_submenu_fixture() { + return array( + 'index.php' => array( + 0 => array( + 'Home', + 'read', + 'index.php', + ), + 10 => array( + 'Updates 4', + 'update_core', + 'update-core.php', + ), + ), + 'upload.php' => array( + 5 => array( + 'Library', + 'upload_files', + 'upload.php', + ), + 10 => array( + 'Add New', + 'upload_files', + 'media-new.php', + ), + ), + 'edit-comments.php' => array( + 0 => array( + 'All Comments', + 'edit_posts', + 'edit-comments.php', + ), + ), + 'edit.php' => array( + 5 => array( + 'All Posts', + 'edit_posts', + 'edit.php', + ), + 10 => array( + 'Add New', + 'edit_posts', + 'post-new.php', + ), + 15 => array( + 'Categories', + 'manage_categories', + 'edit-tags.php?taxonomy=category', + ), + 16 => array( + 'Tags', + 'manage_post_tags', + 'edit-tags.php?taxonomy=post_tag', + ), + ), + 'edit.php?post_type=page' => array( + 5 => array( + 'All Pages', + 'edit_pages', + 'edit.php?post_type=page', + ), + 10 => array( + 'Add New', + 'edit_pages', + 'post-new.php?post_type=page', + ), + ), + 'edit.php?post_type=custom_test_type' => array( + 5 => array( + 'All Custom Test Types', + 'edit_pages', + 'edit.php?post_type=custom_test_type', + ), + 10 => array( + 'Add New', + 'edit_pages', + 'post-new.php?post_type=custom_test_type', + ), + ), + 'themes.php' => array( + 5 => array( + 'Themes', + 'switch_themes', + 'themes.php', + ), + 6 => array( + 'Customize', + 'customize', + 'customize.php?return', + '', + 'hide-if-no-customize', + ), + 10 => array( + 'Menus', + 'edit_theme_options', + 'nav-menus.php', + ), + 11 => array( + 'Widgets', + 'edit_theme_options', + 'gutenberg-widgets', + 'Widgets', + ), + 13 => array( + 'Theme Editor', + 'edit_themes', + 'theme-editor.php', + 'Theme Editor', + ), + ), + 'plugins.php' => array( + 5 => array( + 'Installed Plugins', + 'activate_plugins', + 'plugins.php', + ), + 10 => array( + 'Add New', + 'install_plugins', + 'plugin-install.php', + ), + 15 => array( + 'Plugin Editor', + 'edit_plugins', + 'plugin-editor.php', + ), + ), + 'users.php' => array( + 5 => array( + 'All Users', + 'list_users', + 'users.php', + ), + 10 => array( + 'Add New', + 'create_users', + 'user-new.php', + ), + 15 => array( + 'Profile', + 'read', + 'profile.php', + ), + ), + 'tools.php' => array( + 5 => array( + 'Available Tools', + 'edit_posts', + 'tools.php', + ), + 10 => array( + 'Import', + 'import', + 'import.php', + ), + 15 => array( + 'Export', + 'export', + 'export.php', + ), + 20 => array( + 'Site Health', + 'view_site_health_checks', + 'site-health.php', + ), + 25 => array( + 'Export Personal Data', + 'export_others_personal_data', + 'export-personal-data.php', + ), + 30 => array( + 'Erase Personal Data', + 'erase_others_personal_data', + 'erase-personal-data.php', + ), + ), + 'options-general.php' => array( + 10 => array( + 'General', + 'manage_options', + 'options-general.php', + ), + 15 => array( + 'Writing', + 'manage_options', + 'options-writing.php', + ), + 20 => array( + 'Reading', + 'manage_options', + 'options-reading.php', + ), + 25 => array( + 'Discussion', + 'manage_options', + 'options-discussion.php', + ), + 30 => array( + 'Media', + 'manage_options', + 'options-media.php', + ), + 40 => array( + 'Permalinks', + 'manage_options', + 'options-permalink.php', + ), + 45 => array( + 'Privacy', + 'manage_privacy_options', + 'options-privacy.php', + ), + 46 => array( + 'Approve User', + 'promote_users', + 'wp-approve-user', + 'Approve User', + ), + 47 => array( + '', + 'manage_options', + 'sharing', + '', + ), + ), + 'edit-tags.php?taxonomy=link_category' => array( + 15 => array( + 'Link Categories', + 'manage_categories', + 'edit-tags.php?taxonomy=link_category', + ), + ), + + '' => array( + 0 => array( + '', + 'manage_options', + 'jetpack-debugger', + 'Debugging Center', + ), + 1 => array( + 'Settings', + 'jetpack_manage_modules', + 'jetpack_modules', + 'Jetpack Settings', + ), + 2 => array( + '', + 'jetpack_admin_page', + 'jetpack_about', + 'About Jetpack', + ), + ), + 'edit.php?post_type=feedback' => array( + 0 => array( + 'Feedback', + 'edit_pages', + 'edit.php?post_type=feedback', + '', + ), + 1 => array( + 'Export CSV', + 'export', + 'feedback-export', + 'Export feedback as CSV', + ), + ), + 'jetpack' => array( + 1 => array( + 'Dashboard', + 'jetpack_admin_page', + 'jetpack#/dashboard', + 'Dashboard', + ), + 2 => array( + 'Settings', + 'jetpack_admin_page', + 'jetpack#/settings', + 'Settings', + ), + ), + ); +} diff --git a/projects/packages/masterbar/tests/php/patchwork.json b/projects/packages/masterbar/tests/php/patchwork.json new file mode 100644 index 0000000000000..01825dcfe6220 --- /dev/null +++ b/projects/packages/masterbar/tests/php/patchwork.json @@ -0,0 +1,3 @@ +{ + "redefinable-internals": [ "exit", "die" ] +} diff --git a/projects/packages/masterbar/tests/php/test-class-admin-color-schemes.php b/projects/packages/masterbar/tests/php/test-class-admin-color-schemes.php new file mode 100644 index 0000000000000..d95a71a0d17ab --- /dev/null +++ b/projects/packages/masterbar/tests/php/test-class-admin-color-schemes.php @@ -0,0 +1,164 @@ +server = $wp_rest_server; + + static::$user_id = wp_insert_user( + array( + 'user_login' => 'test_editor', + 'user_pass' => '123', + 'role' => 'editor', + ) + ); + + new Admin_Color_Schemes(); + + do_action( 'rest_api_init' ); + } + + /** + * Returning the environment into its initial state. + * + * @after + */ + public function tear_down() { + WorDBless_Options::init()->clear_options(); + WorDBless_Users::init()->clear_all_users(); + } + + /** + * Tests the schema response for OPTIONS requests. + */ + public function test_schema_request() { + $request = new WP_REST_Request( Requests::OPTIONS, '/wp/v2/users/' . static::$user_id ); + $response = $this->server->dispatch( $request ); + $data = $response->get_data(); + + $schema = ( new WP_REST_Users_Controller() )->get_public_item_schema(); + + $this->assertEquals( $schema, $data['schema'] ); + $this->assertArrayHasKey( 'meta', $data['schema']['properties'] ); + $this->assertArrayHasKey( 'admin_color', $data['schema']['properties']['meta']['properties'] ); + } + + /** + * Tests retrieving the color scheme setting for a user. + */ + public function test_get_color_scheme() { + wp_set_current_user( static::$user_id ); + + $request = new WP_REST_Request( Requests::GET, '/wp/v2/users/' . static::$user_id ); + $response = $this->server->dispatch( $request ); + $data = $response->get_data(); + + $this->assertArrayHasKey( 'meta', $data ); + $this->assertArrayHasKey( 'admin_color', $data['meta'] ); + $this->assertSame( 'fresh', $data['meta']['admin_color'] ); + } + + /** + * Tests updating the color scheme setting for a user. + */ + public function test_update_color_scheme() { + wp_set_current_user( static::$user_id ); + + // Editor can update their own meta value. + $request = new WP_REST_Request( Requests::PUT, '/wp/v2/users/' . static::$user_id ); + $request->set_body_params( + array( + 'meta' => array( + 'admin_color' => 'classic', + ), + ) + ); + $response = $this->server->dispatch( $request ); + $data = $response->get_data(); + + $this->assertArrayHasKey( 'meta', $data ); + $this->assertArrayHasKey( 'admin_color', $data['meta'] ); + $this->assertSame( 'classic', $data['meta']['admin_color'] ); + } + + /** + * Tests updating the color scheme setting as editor for another user. + */ + public function test_update_color_scheme_will_fail_when_editor_updates_another_user() { + wp_set_current_user( static::$user_id ); + + $admin_user_id = wp_insert_user( + array( + 'user_login' => 'test_admin', + 'user_pass' => '123', + 'role' => 'administrator', + ) + ); + + // Editor can't update someone else's meta value. + $request = new WP_REST_Request( Requests::PUT, '/wp/v2/users/' . $admin_user_id ); + $request->set_body_params( + array( + 'meta' => array( + 'admin_color' => 'classic', + ), + ) + ); + $response = $this->server->dispatch( $request ); + $data = $response->get_data(); + + $this->assertSame( WP_Http::FORBIDDEN, $response->get_status() ); + $this->assertSame( 'rest_cannot_edit', $data['code'] ); + } + + public function test_enqueue_core_color_schemes_overrides() { + wp_set_current_user( static::$user_id ); + update_user_option( static::$user_id, 'admin_color', 'coffee' ); + set_current_screen( 'edit-post' ); + $this->assertFalse( wp_style_is( 'jetpack-core-color-schemes-overrides' ) ); + do_action( 'admin_enqueue_scripts' ); + $this->assertTrue( wp_style_is( 'jetpack-core-color-schemes-overrides' ) ); + } +} diff --git a/projects/packages/masterbar/tests/php/test-class-admin-menu.php b/projects/packages/masterbar/tests/php/test-class-admin-menu.php new file mode 100644 index 0000000000000..0c7c409f1bb26 --- /dev/null +++ b/projects/packages/masterbar/tests/php/test-class-admin-menu.php @@ -0,0 +1,573 @@ +get_site_suffix(); + static::$menu_data = get_menu_fixture(); + static::$submenu_data = get_submenu_fixture(); + + static::$user_id = wp_insert_user( + array( + 'user_login' => 'test_admin', + 'user_pass' => '123', + 'role' => 'administrator', + ) + ); + + wp_set_current_user( static::$user_id ); + + // Initialize in set_up so it registers hooks for every test. + static::$admin_menu = Admin_Menu::get_instance(); + $menu = static::$menu_data; + $submenu = static::$submenu_data; + } + + /** + * Returning the environment into its initial state. + * + * @after + */ + public function tear_down() { + WorDBless_Options::init()->clear_options(); + WorDBless_Users::init()->clear_all_users(); + } + + /** + * Test_Admin_Menu. + * + * @covers ::reregister_menu_items + */ + public function test_admin_menu_output() { + global $menu, $submenu; + + static::$admin_menu->reregister_menu_items(); + + $this->assertCount( 18, $menu, 'Admin menu should not have unexpected top menu items.' ); + + $this->assertEquals( static::$submenu_data[''], $submenu[''], 'Submenu items without parent should stay the same.' ); + } + + /** + * Tests get_preferred_view + * + * @covers ::get_preferred_view + */ + public function test_get_preferred_view() { + static::$admin_menu->set_preferred_view( 'users.php', 'unknown' ); + $this->assertSame( 'default', static::$admin_menu->get_preferred_view( 'users.php' ) ); + static::$admin_menu->set_preferred_view( 'options-general.php', 'unknown' ); + $this->assertSame( 'default', static::$admin_menu->get_preferred_view( 'options-general.php' ) ); + } + + /** + * Tests add_my_home_menu + * + * @covers ::add_my_home_menu + */ + public function test_add_my_home_menu() { + global $menu, $submenu; + + static::$admin_menu->add_my_home_menu(); + + // Has My Home submenu item when there are other submenu items. + $this->assertSame( 'https://wordpress.com/home/' . static::$domain, array_shift( $submenu['index.php'] )[2] ); + + // Reset data. + $menu = static::$menu_data; + $submenu = static::$submenu_data; + + // Has no ny Home submenu when there are no other submenus. + $submenu['index.php'] = array( + 0 => array( 'Home', 'read', 'index.php' ), + ); + + static::$admin_menu->add_my_home_menu(); + $this->assertSame( 'https://wordpress.com/home/' . static::$domain, $menu[2][2] ); + '@phan-var non-empty-array $submenu'; + $this->assertSame( Base_Admin_Menu::HIDE_CSS_CLASS, $submenu['index.php'][0][4] ); + } + + /** + * Tests add_stats_menu + * + * @covers ::add_stats_menu + */ + public function test_add_stats_menu() { + global $menu; + + static::$admin_menu->add_stats_menu(); + // Ignore position keys, since the key used for the Stats menu contains a pseudorandom number + // that we shouldn't hardcode. The only thing that matters is that the menu should be in the + // 3rd position regardless of the key. + // @see https://core.trac.wordpress.org/ticket/40927 + ksort( $menu ); + $menu_items = array_values( $menu ); + + $this->assertSame( 'https://wordpress.com/stats/day/' . static::$domain, $menu_items[2][2] ); + } + + /** + * Tests add_upgrades_menu + * + * @covers ::add_upgrades_menu + */ + public function test_add_upgrades_menu() { + global $submenu; + + static::$admin_menu->add_upgrades_menu( 'Test Plan' ); + $this->assertSame( 'Upgrades', $submenu['paid-upgrades.php'][0][0] ); + $this->assertSame( 'https://wordpress.com/plans/' . static::$domain, $submenu['paid-upgrades.php'][1][2] ); + $this->assertSame( 'https://wordpress.com/purchases/subscriptions/' . static::$domain, $submenu['paid-upgrades.php'][2][2] ); + } + + /** + * Tests add_posts_menu + * + * @covers ::add_posts_menu + */ + public function test_add_posts_menu() { + global $submenu; + + static::$admin_menu->add_posts_menu(); + $this->assertSame( 'https://wordpress.com/posts/' . static::$domain, $submenu['edit.php'][0][2] ); + $this->assertSame( 'https://wordpress.com/post/' . static::$domain, $submenu['edit.php'][2][2] ); + } + + /** + * Tests add_media_menu + * + * @covers ::add_media_menu + */ + public function test_add_media_menu() { + global $menu, $submenu; + + static::$admin_menu->add_media_menu(); + + $this->assertSame( 'https://wordpress.com/media/' . static::$domain, $menu[10][2] ); + $this->assertFalse( static::$admin_menu->has_visible_items( $submenu['upload.php'] ) ); + } + + /** + * Tests add_page_menu + * + * @covers ::add_page_menu + */ + public function test_add_page_menu() { + global $submenu; + + static::$admin_menu->add_page_menu(); + $this->assertSame( 'https://wordpress.com/pages/' . static::$domain, $submenu['edit.php?post_type=page'][0][2] ); + $this->assertSame( 'https://wordpress.com/page/' . static::$domain, $submenu['edit.php?post_type=page'][2][2] ); + } + + /** + * Tests add_custom_post_type_menu + * + * @covers ::add_custom_post_type_menu + */ + public function test_add_custom_post_type_menu() { + global $menu, $submenu; + + // Don't show post types that don't want to be shown. + get_post_type_object( 'revision' ); + static::$admin_menu->add_custom_post_type_menu( 'revision' ); + $last_item = array_pop( $menu ); + $this->assertNotSame( 'https://wordpress.com/types/revision/' . static::$domain, $last_item[2] ); + + register_post_type( + 'custom_test_type', + array( + 'label' => 'Custom Test Types', + 'show_ui' => true, + 'menu_position' => 2020, + ) + ); + + static::$admin_menu->add_custom_post_type_menu( 'custom_test_type' ); + + // Clean up. + unregister_post_type( 'custom_test_type' ); + $this->assertSame( 'https://wordpress.com/types/custom_test_type/' . static::$domain, $submenu['edit.php?post_type=custom_test_type'][0][2] ); + $this->assertSame( 'https://wordpress.com/edit/custom_test_type/' . static::$domain, $submenu['edit.php?post_type=custom_test_type'][2][2] ); + } + + /** + * Tests add_comments_menu + * + * @covers ::add_comments_menu + */ + public function test_add_comments_menu() { + global $menu, $submenu; + + static::$admin_menu->add_comments_menu(); + + $this->assertSame( 'https://wordpress.com/comments/all/' . static::$domain, $menu[25][2] ); + $this->assertFalse( self::$admin_menu->has_visible_items( $submenu['edit-comments.php'] ) ); + } + + /** + * Tests add_appearance_menu + * + * @covers ::add_appearance_menu + */ + public function test_add_appearance_menu() { + global $submenu; + + static::$admin_menu->add_appearance_menu(); + + $this->assertSame( 'https://wordpress.com/themes/' . static::$domain, array_shift( $submenu['themes.php'] )[2] ); + } + + /** + * Tests add_plugins_menu + * + * @covers ::add_plugins_menu + */ + public function test_add_plugins_menu() { + global $menu, $submenu; + + static::$admin_menu->add_plugins_menu(); + + $this->assertSame( 'https://wordpress.com/plugins/' . static::$domain, $menu[65][2] ); + $this->assertFalse( self::$admin_menu->has_visible_items( $submenu['plugins.php'] ) ); + } + + /** + * Tests add_users_menu + * + * @covers ::add_users_menu + */ + public function test_add_users_menu() { + global $menu, $submenu; + + // Current user can't list users. + $editor_id = wp_insert_user( + array( + 'user_login' => 'test_editor', + 'user_pass' => '123', + 'role' => 'editor', + ) + ); + wp_set_current_user( $editor_id ); + $menu = array( + 70 => array( + 'Profile', + 'read', + 'profile.php', + '', + 'menu-top menu-icon-users', + 'menu-users', + 'dashicons-admin-users', + ), + ); + $submenu = array( + 'profile.php' => array( + 0 => array( 'Profile', 'read', 'profile.php' ), + ), + ); + + static::$admin_menu->add_users_menu(); + + '@phan-var non-empty-array $submenu'; + $this->assertSame( 'https://wordpress.com/me', $submenu['profile.php'][0][2] ); + $this->assertSame( 'https://wordpress.com/me/account', $submenu['profile.php'][2][2] ); + + // Reset. + wp_set_current_user( static::$user_id ); + $menu = static::$menu_data; + $submenu = static::$submenu_data; + + // On multisite the administrator is not allowed to create users. + grant_super_admin( self::$user_id ); + $account_key = 5; + + static::$admin_menu->add_users_menu(); + + // On WP.com users can only invite other users, not create them (missing create_users cap). + if ( ! defined( 'IS_WPCOM' ) || ! IS_WPCOM ) { + $this->assertSame( 'https://wordpress.com/people/new/' . static::$domain, $submenu['users.php'][2][2] ); + $account_key = 6; + } + + $this->assertSame( 'https://wordpress.com/people/team/' . static::$domain, $submenu['users.php'][0][2] ); + $this->assertSame( 'https://wordpress.com/me', $submenu['users.php'][3][2] ); + $this->assertSame( 'https://wordpress.com/me/account', $submenu['users.php'][ $account_key ][2] ); + } + + /** + * Tests add_tools_menu + * + * @covers ::add_tools_menu + */ + public function test_add_tools_menu() { + global $submenu; + + static::$admin_menu->add_tools_menu(); + + $this->assertSame( 'https://wordpress.com/marketing/tools/' . static::$domain, $submenu['tools.php'][0][2] ); + $this->assertSame( 'https://wordpress.com/earn/' . static::$domain, $submenu['tools.php'][1][2] ); + $this->assertSame( 'https://wordpress.com/import/' . static::$domain, $submenu['tools.php'][4][2] ); + $this->assertSame( 'https://wordpress.com/export/' . static::$domain, $submenu['tools.php'][5][2] ); + } + + /** + * Tests add_options_menu + * + * @covers ::add_options_menu + */ + public function test_add_options_menu() { + global $submenu; + + static::$admin_menu->add_options_menu(); + + $this->assertSame( 'https://wordpress.com/settings/general/' . static::$domain, $submenu['options-general.php'][0][2] ); + } + + /** + * Tests add_jetpack_menu + * § + * + * @covers ::add_jetpack_menu + */ + public function test_add_jetpack_menu() { + global $submenu; + + static::$admin_menu->add_jetpack_menu(); + + $this->assertSame( 'https://wordpress.com/activity-log/' . static::$domain, $submenu['jetpack'][3][2] ); + $this->assertSame( 'https://wordpress.com/backup/' . static::$domain, $submenu['jetpack'][4][2] ); + } + + /** + * Check if the hidden menus are at the end of the submenu. + */ + public function test_if_the_hidden_menus_are_at_the_end_of_submenu() { + global $submenu; + + $submenu = array( + 'options-general.php' => array( + array( '', 'read', 'test-slug', '', '' ), + array( '', 'read', 'test-slug', '', Base_Admin_Menu::HIDE_CSS_CLASS ), + array( '', 'read', 'test-slug', '', '' ), + array( '', 'read', 'test-slug', '' ), + array( '', 'read', 'test-slug', '', Base_Admin_Menu::HIDE_CSS_CLASS ), + array( '', 'read', 'test-slug', '', '' ), + ), + ); + + '@phan-var non-empty-array $submenu'; + + static::$admin_menu->sort_hidden_submenus(); + $this->assertNotEquals( Base_Admin_Menu::HIDE_CSS_CLASS, $submenu['options-general.php'][0][4] ); + $this->assertNotEquals( Base_Admin_Menu::HIDE_CSS_CLASS, $submenu['options-general.php'][2][4] ); + + $this->assertEquals( array( '', 'read', 'test-slug', '' ), $submenu['options-general.php'][3] ); + + $this->assertNotEquals( Base_Admin_Menu::HIDE_CSS_CLASS, $submenu['options-general.php'][5][4] ); + + $this->assertEquals( Base_Admin_Menu::HIDE_CSS_CLASS, $submenu['options-general.php'][6][4] ); + $this->assertEquals( Base_Admin_Menu::HIDE_CSS_CLASS, $submenu['options-general.php'][7][4] ); + + $submenu = self::$submenu_data; + } + + /** + * Check if the parent menu is hidden when the submenus are hidden. + * + * @dataProvider hide_menu_based_on_submenu_provider + * + * @param array $menu_items The mock menu array. + * @param array $submenu_items The mock submenu array. + * @param array $expected The expected result. + */ + public function test_if_it_hides_menu_based_on_submenu( $menu_items, $submenu_items, $expected ) { + global $submenu, $menu; + + $menu = $menu_items; + $submenu = $submenu_items; + + static::$admin_menu->hide_parent_of_hidden_submenus(); + + $this->assertEquals( $expected, $menu[0] ); + + // reset the menu arrays. + $menu = self::$menu_data; + $submenu = self::$submenu_data; + } + + /** + * The data provider for test_if_it_hides_menu_based_on_submenu. + * + * @return array + */ + public function hide_menu_based_on_submenu_provider() { + return array( + array( + array( + array( '', 'non-existing-capability', 'test-slug', '', '' ), + ), + array( + 'test-slug' => array( + array( + 'test', + '', + '', + '', + Base_Admin_Menu::HIDE_CSS_CLASS, + ), + ), + ), + array( '', 'non-existing-capability', 'test-slug', '', Base_Admin_Menu::HIDE_CSS_CLASS ), + ), + array( + array( + array( '', 'read', 'test-slug', '', '' ), + ), + array( + 'test-slug' => array( + array( + 'test', + '', + 'test-slug', + '', + Base_Admin_Menu::HIDE_CSS_CLASS, + ), + ), + ), + array( '', 'read', 'test-slug', '', Base_Admin_Menu::HIDE_CSS_CLASS ), + ), + ); + } + + /** + * Tests test_add_woocommerce_installation_menu + * + * @covers ::add_woocommerce_installation_menu + */ + public function test_add_woocommerce_installation_menu() { + global $menu; + + $woo_icon = 'data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAxMDI0IDEwMjQiPjxwYXRoIGZpbGw9IiNhMmFhYjIiIGQ9Ik02MTIuMTkyIDQyNi4zMzZjMC02Ljg5Ni0zLjEzNi01MS42LTI4LTUxLjYtMzcuMzYgMC00Ni43MDQgNzIuMjU2LTQ2LjcwNCA4Mi42MjQgMCAzLjQwOCAzLjE1MiA1OC40OTYgMjguMDMyIDU4LjQ5NiAzNC4xOTItLjAzMiA0Ni42NzItNzIuMjg4IDQ2LjY3Mi04OS41MnptMjAyLjE5MiAwYzAtNi44OTYtMy4xNTItNTEuNi0yOC4wMzItNTEuNi0zNy4yOCAwLTQ2LjYwOCA3Mi4yNTYtNDYuNjA4IDgyLjYyNCAwIDMuNDA4IDMuMDcyIDU4LjQ5NiAyNy45NTIgNTguNDk2IDM0LjE5Mi0uMDMyIDQ2LjY4OC03Mi4yODggNDYuNjg4LTg5LjUyek0xNDEuMjk2Ljc2OGMtNjguMjI0IDAtMTIzLjUwNCA1NS40ODgtMTIzLjUwNCAxMjMuOTJ2NjUwLjcyYzAgNjguNDMyIDU1LjI5NiAxMjMuOTIgMTIzLjUwNCAxMjMuOTJoMzM5LjgwOGwxMjMuNTA0IDEyMy45MzZWODk5LjMyOGgyNzguMDQ4YzY4LjIyNCAwIDEyMy41Mi01NS40NzIgMTIzLjUyLTEyMy45MnYtNjUwLjcyYzAtNjguNDMyLTU1LjI5Ni0xMjMuOTItMTIzLjUyLTEyMy45MmgtNzQxLjM2em01MjYuODY0IDQyMi4xNmMwIDU1LjA4OC0zMS4wODggMTU0Ljg4LTEwMi42NCAxNTQuODgtNi4yMDggMC0xOC40OTYtMy42MTYtMjUuNDI0LTYuMDE2LTMyLjUxMi0xMS4xNjgtNTAuMTkyLTQ5LjY5Ni01Mi4zNTItNjYuMjU2IDAgMC0zLjA3Mi0xNy43OTItMy4wNzItNDAuNzUyIDAtMjIuOTkyIDMuMDcyLTQ1LjMyOCAzLjA3Mi00NS4zMjggMTUuNTUyLTc1LjcyOCA0My41NTItMTA2LjczNiA5Ni40NDgtMTA2LjczNiA1OS4wNzItLjAzMiA4My45NjggNTguNTI4IDgzLjk2OCAxMTAuMjA4ek00ODYuNDk2IDMwMi40YzAgMy4zOTItNDMuNTUyIDE0MS4xNjgtNDMuNTUyIDIxMy40MjR2NzUuNzEyYy0yLjU5MiAxMi4wOC00LjE2IDI0LjE0NC0yMS44MjQgMjQuMTQ0LTQ2LjYwOCAwLTg4Ljg4LTE1MS40NzItOTIuMDE2LTE2MS44NC02LjIwOCA2Ljg5Ni02Mi4yNCAxNjEuODQtOTYuNDQ4IDE2MS44NC0yNC44NjQgMC00My41NTItMTEzLjY0OC00Ni42MDgtMTIzLjkzNkMxNzYuNzA0IDQzNi42NzIgMTYwIDMzNC4yMjQgMTYwIDMyNy4zMjhjMC0yMC42NzIgMS4xNTItMzguNzM2IDI2LjA0OC0zOC43MzYgNi4yMDggMCAyMS42IDYuMDY0IDIzLjcxMiAxNy4xNjggMTEuNjQ4IDYyLjAzMiAxNi42ODggMTIwLjUxMiAyOS4xNjggMTg1Ljk2OCAxLjg1NiAyLjkyOCAxLjUwNCA3LjAwOCA0LjU2IDEwLjQzMiAzLjE1Mi0xMC4yODggNjYuOTI4LTE2OC43ODQgOTQuOTYtMTY4Ljc4NCAyMi41NDQgMCAzMC40IDQ0LjU5MiAzMy41MzYgNjEuODI0IDYuMjA4IDIwLjY1NiAxMy4wODggNTUuMjE2IDIyLjQxNiA4Mi43NTIgMC0xMy43NzYgMTIuNDgtMjAzLjEyIDY1LjM5Mi0yMDMuMTIgMTguNTkyLjAzMiAyNi43MDQgNi45MjggMjYuNzA0IDI3LjU2OHpNODcwLjMyIDQyMi45MjhjMCA1NS4wODgtMzEuMDg4IDE1NC44OC0xMDIuNjQgMTU0Ljg4LTYuMTkyIDAtMTguNDQ4LTMuNjE2LTI1LjQyNC02LjAxNi0zMi40MzItMTEuMTY4LTUwLjE3Ni00OS42OTYtNTIuMjg4LTY2LjI1NiAwIDAtMy44ODgtMTcuOTItMy44ODgtNDAuODk2czMuODg4LTQ1LjE4NCAzLjg4OC00NS4xODRjMTUuNTUyLTc1LjcyOCA0My40ODgtMTA2LjczNiA5Ni4zODQtMTA2LjczNiA1OS4xMDQtLjAzMiA4My45NjggNTguNTI4IDgzLjk2OCAxMTAuMjA4eiIvPjwvc3ZnPg=='; + + // By default, the WooCommerce installation menu item should NOT be displayed. + static::$admin_menu->add_woocommerce_installation_menu(); + + $this->assertArrayNotHasKey( 54, $menu ); + $this->assertArrayNotHasKey( 55, $menu ); + + // Reset the menu array. + $menu = self::$menu_data; + + // If the filter returns true, the WooCommerce installation menu should be displayed. + add_filter( 'jetpack_show_wpcom_woocommerce_installation_menu', '__return_true' ); + static::$admin_menu->add_woocommerce_installation_menu(); + + $this->assertArrayHasKey( 54, $menu ); + $this->assertArrayHasKey( 55, $menu ); + + $this->assertMatchesRegularExpression( '/^separator-custom-.*/', $menu['54'][2] ); + + $this->assertSame( 'WooCommerce', $menu[55][0] ); + $this->assertSame( 'activate_plugins', $menu[55][1] ); + $this->assertSame( 'https://wordpress.com/woocommerce-installation/' . static::$domain, $menu[55][2] ); + $this->assertSame( 'WooCommerce', $menu[55][3] ); + $this->assertSame( $woo_icon, $menu[55][6] ); + + remove_all_filters( 'jetpack_show_wpcom_woocommerce_installation_menu' ); + + // Reset the menu array. + $menu = self::$menu_data; + + // If the filter returns false, the WooCommerce installation menu should NOT be displayed. + add_filter( 'jetpack_show_wpcom_woocommerce_installation_menu', '__return_false' ); + static::$admin_menu->add_woocommerce_installation_menu(); + + $this->assertArrayNotHasKey( 54, $menu ); + $this->assertArrayNotHasKey( 55, $menu ); + + remove_all_filters( 'jetpack_show_wpcom_woocommerce_installation_menu' ); + + // Reset the menu array. + $menu = self::$menu_data; + + // Test filter signature with $current_plan passed in. + add_filter( + 'jetpack_show_wpcom_woocommerce_installation_menu', + function ( $should_show, $current_plan ) { + return $current_plan['test']; + }, + 10, + 2 + ); + static::$admin_menu->add_woocommerce_installation_menu( array( 'test' => true ) ); + + $this->assertArrayHasKey( 54, $menu ); + $this->assertArrayHasKey( 55, $menu ); + + remove_all_filters( 'jetpack_show_wpcom_woocommerce_installation_menu' ); + } +} diff --git a/projects/packages/masterbar/tests/php/test-class-atomic-additional-css-manager.php b/projects/packages/masterbar/tests/php/test-class-atomic-additional-css-manager.php new file mode 100644 index 0000000000000..77ed082c04bb3 --- /dev/null +++ b/projects/packages/masterbar/tests/php/test-class-atomic-additional-css-manager.php @@ -0,0 +1,66 @@ +wp_customize = new \WP_Customize_Manager(); + } + + /** + * Returning the environment into its initial state. + * + * @after + */ + public function tear_down() { + WorDBless_Options::init()->clear_options(); + WorDBless_Users::init()->clear_all_users(); + } + + /** + * Check if the nudge contains the proper url and message copy. + */ + public function test_it_generates_proper_url_and_nudge() { + $manager = new Atomic_Additional_CSS_Manager( 'foo.com' ); + + $manager->register_nudge( $this->wp_customize ); + + $this->assertEquals( + '/checkout/foo.com/business', + $this->wp_customize->controls()['custom_css_control']->cta_url + ); + + $this->assertEquals( + 'Purchase the Creator plan to
activate CSS customization', + $this->wp_customize->controls()['custom_css_control']->nudge_copy + ); + } +} diff --git a/projects/packages/masterbar/tests/php/test-class-atomic-admin-menu.php b/projects/packages/masterbar/tests/php/test-class-atomic-admin-menu.php new file mode 100644 index 0000000000000..e34d0426bfa07 --- /dev/null +++ b/projects/packages/masterbar/tests/php/test-class-atomic-admin-menu.php @@ -0,0 +1,400 @@ +get_site_suffix(); + static::$menu_data = get_menu_fixture(); + static::$submenu_data = get_submenu_fixture(); + + static::$user_id = wp_insert_user( + array( + 'user_login' => 'test_admin', + 'user_pass' => '123', + 'role' => 'administrator', + ) + ); + + wp_set_current_user( static::$user_id ); + + // Initialize in set_up so it registers hooks for every test. + static::$admin_menu = Atomic_Admin_Menu::get_instance(); + $menu = static::$menu_data; + $submenu = static::$submenu_data; + } + + /** + * Returning the environment into its initial state. + * + * @after + */ + public function tear_down() { + WorDBless_Options::init()->clear_options(); + WorDBless_Users::init()->clear_all_users(); + } + + /** + * Tests add_browse_sites_link. + * + * @covers ::add_browse_sites_link + */ + public function test_add_browse_sites_link() { + global $menu; + + // No output when executed in single site mode. + static::$admin_menu->add_browse_sites_link(); + $this->assertArrayNotHasKey( 0, $menu ); + } + + /** + * Tests add_browse_sites_link. + * + * @covers ::add_browse_sites_link + */ + public function test_add_browse_sites_link_multisite() { + if ( ! is_multisite() ) { + $this->markTestSkipped( 'Only used on multisite' ); + } + + global $menu; + + // No output when user has just one site. + static::$admin_menu->add_browse_sites_link(); + $this->assertArrayNotHasKey( 0, $menu ); + + // Give user a second site. + update_user_option( static::$user_id, 'wpcom_site_count', 2 ); + + static::$admin_menu->add_browse_sites_link(); + + $browse_sites_menu_item = array( + 'Browse sites', + 'read', + 'https://wordpress.com/sites', + 'site-switcher', + 'menu-top toplevel_page_https://wordpress.com/sites', + 'toplevel_page_https://wordpress.com/sites', + 'dashicons-arrow-left-alt2', + ); + $this->assertSame( $menu[0], $browse_sites_menu_item ); + + delete_user_option( static::$user_id, 'wpcom_site_count' ); + } + + /** + * Tests add_new_site_link. + * + * @covers ::add_new_site_link + */ + public function test_add_new_site_link() { + global $menu; + + // Set jetpack user data. + update_user_option( static::$user_id, 'wpcom_site_count', 1 ); + + static::$admin_menu->add_new_site_link(); + + $new_site_menu_item = array( + 'Add New Site', + 'read', + 'https://wordpress.com/start?ref=calypso-sidebar', + 'Add New Site', + 'menu-top toplevel_page_https://wordpress.com/start?ref=calypso-sidebar', + 'toplevel_page_https://wordpress.com/start?ref=calypso-sidebar', + 'dashicons-plus-alt', + ); + $this->assertSame( array_pop( $menu ), $new_site_menu_item ); + + delete_user_option( static::$user_id, 'wpcom_site_count' ); + } + + /** + * Tests add_site_card_menu + * + * @covers ::add_site_card_menu + */ + public function test_add_site_card_menu() { + global $menu; + + static::$admin_menu->add_site_card_menu(); + + $home_url = home_url(); + $site_card_menu_item = array( + // phpcs:ignore Squiz.Strings.DoubleQuoteUsage.NotRequired + ' +
+
' . get_option( 'blogname' ) . '
+
' . static::$domain . "
\n\t\n
", + 'read', + $home_url, + 'site-card', + 'menu-top toplevel_page_' . $home_url, + 'toplevel_page_' . $home_url, + plugins_url( 'src/admin-menu/globe-icon.svg', dirname( __DIR__ ) ), + ); + + $this->assertEquals( $site_card_menu_item, $menu[1] ); + } + + /** + * Tests set_site_card_menu_class + * + * @covers ::set_site_card_menu_class + */ + public function test_set_site_card_menu_class() { + global $menu; + + static::$admin_menu->add_site_card_menu(); + + $menu = static::$admin_menu->set_site_card_menu_class( $menu ); + $this->assertStringNotContainsString( 'has-site-icon', $menu[1][4] ); + + // Atomic fallback site icon counts as no site icon. + add_filter( 'get_site_icon_url', array( $this, 'wpcomsh_site_icon_url' ) ); + $menu = static::$admin_menu->set_site_card_menu_class( $menu ); + remove_filter( 'get_site_icon_url', array( $this, 'wpcomsh_site_icon_url' ) ); + $this->assertStringNotContainsString( 'has-site-icon', $menu[1][4] ); + + // Custom site icon triggers CSS class. + add_filter( 'get_site_icon_url', array( $this, 'custom_site_icon_url' ) ); + $menu = static::$admin_menu->set_site_card_menu_class( $menu ); + remove_filter( 'get_site_icon_url', array( $this, 'custom_site_icon_url' ) ); + $this->assertStringContainsString( 'has-site-icon', $menu[1][4] ); + } + + /** + * Shim wpcomsh fallback site icon. + * + * @return string + */ + public function wpcomsh_site_icon_url() { + return 'https://s0.wp.com/i/webclip.png'; + } + + /** + * Custom site icon. + * + * @return string + */ + public function custom_site_icon_url() { + return 'https://s0.wp.com/i/jetpack.png'; + } + + /** + * Tests get_preferred_view + * + * @covers ::get_preferred_view + */ + public function test_get_preferred_view() { + $this->assertSame( 'classic', static::$admin_menu->get_preferred_view( 'export.php' ) ); + } + + /** + * Tests add_upgrades_menu + * + * @covers ::add_upgrades_menu + */ + public function test_add_upgrades_menu() { + global $submenu; + + static::$admin_menu->add_upgrades_menu(); + + $this->assertSame( 'https://wordpress.com/plans/' . static::$domain, $submenu['paid-upgrades.php'][1][2] ); + $this->assertSame( 'https://wordpress.com/domains/manage/' . static::$domain, $submenu['paid-upgrades.php'][2][2] ); + + /** This filter is already documented in modules/masterbar/admin-menu/class-atomic-admin-menu.php */ + if ( apply_filters( 'jetpack_show_wpcom_upgrades_email_menu', false ) ) { + $this->assertSame( 'https://wordpress.com/email/' . static::$domain, $submenu['paid-upgrades.php'][3][2] ); + $this->assertSame( 'https://wordpress.com/purchases/subscriptions/' . static::$domain, $submenu['paid-upgrades.php'][4][2] ); + } else { + $this->assertSame( 'https://wordpress.com/purchases/subscriptions/' . static::$domain, $submenu['paid-upgrades.php'][3][2] ); + } + } + + /** + * Tests add_my_mailboxes_menu + * + * @covers ::add_my_mailboxes_menu + */ + public function test_add_my_mailboxes_menu() { + global $menu; + + static::$admin_menu->add_my_mailboxes_menu(); + + $this->assertSame( 'https://wordpress.com/mailboxes/' . static::$domain, $menu['4.64424'][2] ); + } + + /** + * Tests add_options_menu + * + * @covers ::add_options_menu + */ + public function test_add_options_menu() { + global $submenu; + + static::$admin_menu->add_options_menu(); + $this->assertSame( 'https://wordpress.com/hosting-config/' . static::$domain, $submenu['options-general.php'][11][2] ); + } + + /** + * Tests add_users_menu + * + * @covers ::add_users_menu + */ + public function test_add_users_menu() { + global $submenu; + + static::$admin_menu->add_users_menu(); + $this->assertSame( 'https://wordpress.com/people/team/' . static::$domain, $submenu['users.php'][0][2] ); + $this->assertSame( 'user-new.php', $submenu['users.php'][2][2] ); + $this->assertSame( 'https://wordpress.com/subscribers/' . static::$domain, $submenu['users.php'][4][2] ); + $this->assertSame( 'https://wordpress.com/me', $submenu['users.php'][5][2] ); + $this->assertSame( 'https://wordpress.com/me/account', $submenu['users.php'][6][2] ); + } + + /** + * Tests remove_gutenberg_menu + * + * @covers ::remove_gutenberg_menu + */ + public function test_remove_gutenberg_menu() { + global $menu; + static::$admin_menu->remove_gutenberg_menu(); + + // Gutenberg plugin menu should not be visible. + $this->assertArrayNotHasKey( 101, $menu ); + } + + /** + * Tests add_plugins_menu + * + * @covers ::add_plugins_menu + */ + public function test_add_plugins_menu() { + global $submenu; + '@phan-var non-empty-array $submenu'; + $this->assertSame( 'plugin-install.php', $submenu['plugins.php'][10][2] ); + + if ( ! is_multisite() ) { + static::$admin_menu->add_plugins_menu(); + + // Make sure that initial menu item is hidden. + $this->assertSame( 'hide-if-js', $submenu['plugins.php'][1][4] ); + // Make sure that the new menu item is inserted. + $this->assertSame( 'https://wordpress.com/plugins/' . static::$domain, $submenu['plugins.php'][0][2] ); + // Make sure that Installed Plugins menu item is still in place. + $this->assertSame( 'plugins.php', $submenu['plugins.php'][2][2] ); + } + } + + /** + * Tests add_tools_menu + * + * @covers ::add_tools_menu + */ + public function test_add_site_monitoring_menu() { + global $submenu; + + static::$admin_menu->add_tools_menu(); + $menu_position = 7; + + $this->assertSame( 'https://wordpress.com/site-monitoring/' . static::$domain, $submenu['tools.php'][ $menu_position ][2] ); + } + + /** + * Tests add_github_deployments_menu + * + * @covers ::add_tools_menu + */ + public function test_add_github_deployments_menu() { + global $submenu; + + static::$admin_menu->add_tools_menu(); + $links = wp_list_pluck( array_values( $submenu['tools.php'] ), 2 ); + + $this->assertContains( 'https://wordpress.com/github-deployments/' . static::$domain, $links ); + } + + /** + * Tests add_jetpack_scan_menu + * + * @covers ::add_jetpack_menu + */ + public function test_add_jetpack_scan_submenu() { + global $submenu; + + static::$admin_menu->add_jetpack_menu(); + $links = wp_list_pluck( array_values( $submenu['jetpack'] ), 2 ); + + $this->assertContains( 'https://wordpress.com/scan/history/' . static::$domain, $links ); + } +} diff --git a/projects/packages/masterbar/tests/php/test-class-base-admin-menu.php b/projects/packages/masterbar/tests/php/test-class-base-admin-menu.php new file mode 100644 index 0000000000000..9f910a2e576e5 --- /dev/null +++ b/projects/packages/masterbar/tests/php/test-class-base-admin-menu.php @@ -0,0 +1,226 @@ + 'test_admin', + 'user_pass' => '123', + 'role' => 'administrator', + ) + ); + + wp_set_current_user( static::$user_id ); + + $admin_menu = $this->get_concrete_menu_admin(); + + // Initialize in setUp so it registers hooks for every test. + static::$admin_menu = $admin_menu::get_instance(); + $menu = static::$menu_data; + $submenu = static::$submenu_data; + } + + /** + * Returning the environment into its initial state. + * + * @after + */ + public function tear_down() { + wp_deregister_script( 'jetpack-admin-menu' ); + wp_deregister_style( 'jetpack-admin-menu' ); + wp_deregister_script( 'jetpack-admin-nav-unification' ); + wp_deregister_style( 'jetpack-admin-nav-unification' ); + WorDBless_Options::init()->clear_options(); + WorDBless_Users::init()->clear_all_users(); + } + + /** + * Test get_instance. + */ + public function test_get_instance() { + + $admin_menu = $this->get_concrete_menu_admin(); + + $instance = $admin_menu::get_instance(); + + $this->assertInstanceOf( Base_Admin_Menu::class, $instance ); + $this->assertSame( $instance, static::$admin_menu ); + + $this->assertSame( 99998, has_action( 'admin_menu', array( $instance, 'reregister_menu_items' ) ) ); + $this->assertSame( 11, has_action( 'admin_enqueue_scripts', array( $instance, 'enqueue_scripts' ) ) ); + } + + /** + * Tests add_admin_menu_separator + */ + public function test_add_admin_menu_separator() { + global $menu; + + // Start with a clean slate. + $temp_menu = $menu; + $menu = array(); + + static::$admin_menu->add_admin_menu_separator( 15 ); + static::$admin_menu->add_admin_menu_separator( 10, 'manage_options' ); + '@phan-var non-empty-array $menu'; + $this->assertSame( 'manage_options', $menu[10][1] ); + $this->assertStringContainsString( 'separator-custom-', $menu[10][2] ); + $this->assertSame( 'read', $menu[15][1] ); + $this->assertStringContainsString( 'separator-custom-', $menu[15][2] ); + + // Restore filtered $menu. + $menu = $temp_menu; + } + + /** + * Tests preferred_view + */ + public function test_preferred_view() { + $this->assertSame( 'default', static::$admin_menu->get_preferred_view( 'test.php' ) ); + $this->assertSame( 'unknown', static::$admin_menu->get_preferred_view( 'test.php', false ) ); + + update_user_option( get_current_user_id(), 'jetpack_admin_menu_link_destination', true ); + $this->assertSame( 'classic', static::$admin_menu->get_preferred_view( 'test.php' ) ); + delete_user_option( get_current_user_id(), 'jetpack_admin_menu_link_destination' ); + + static::$admin_menu->set_preferred_view( 'test.php', 'classic' ); + $this->assertSame( 'classic', static::$admin_menu->get_preferred_view( 'test.php' ) ); + + static::$admin_menu->set_preferred_view( 'test.php', 'default' ); + $this->assertSame( 'default', static::$admin_menu->get_preferred_view( 'test.php', false ) ); + } + + /** + * Tests preferred_view + */ + public function test_handle_preferred_view() { + // @see p9dueE-3LL-p2#comment-6669 + if ( defined( 'IS_WPCOM' ) && IS_WPCOM ) { + $this->markTestSkipped( 'Does not work on WP.com as handle_preferred_view() performs a redirect and then terminates the execution.' ); + } + + global $pagenow; + $pagenow = 'test.php'; + $_GET['preferred-view'] = 'classic'; + + $this->expectException( ExitException::class ); + + static::$admin_menu->handle_preferred_view(); + + $this->assertSame( 'classic', static::$admin_menu->get_preferred_view( 'test.php' ) ); + } + + /** + * Tests enqueue_scripts when the user has indicated they want to use the wp-admin interface. + */ + public function test_enqueue_scripts_use_wp_admin_interface() { + update_option( 'wpcom_admin_interface', 'wp-admin' ); + set_current_screen( 'edit-post' ); + + do_action( 'admin_enqueue_scripts' ); + $this->assertTrue( wp_script_is( 'jetpack-admin-menu' ) ); + $this->assertTrue( wp_style_is( 'jetpack-admin-menu' ) ); + $this->assertFalse( wp_script_is( 'jetpack-admin-nav-unification' ) ); + $this->assertFalse( wp_style_is( 'jetpack-admin-nav-unification' ) ); + } + + /** + * Tests enqueue_scripts + */ + public function test_enqueue_scripts() { + set_current_screen( 'edit-post' ); + + do_action( 'admin_enqueue_scripts' ); + $this->assertTrue( wp_script_is( 'jetpack-admin-menu' ) ); + $this->assertTrue( wp_style_is( 'jetpack-admin-menu' ) ); + $this->assertTrue( wp_script_is( 'jetpack-admin-nav-unification' ) ); + $this->assertTrue( wp_style_is( 'jetpack-admin-nav-unification' ) ); + } + + /** + * Tests enqueue_scripts with right-to-left text direction. + */ + public function test_enqueue_scripts_rtl() { + Functions\expect( 'is_rtl' ) + ->andReturn( true ); + + wp_set_current_user( static::$user_id ); + set_current_screen( 'edit-post' ); + + do_action( 'admin_enqueue_scripts' ); + + $styles = wp_styles(); + $admin_menu_styles = $styles->registered['jetpack-admin-menu']; + $nav_unific_styles = $styles->registered['jetpack-admin-nav-unification']; + + $this->assertStringContainsString( 'rtl.css', $admin_menu_styles->src ); + $this->assertStringContainsString( 'rtl.css', $nav_unific_styles->src ); + } + + /** + * Get an object of Base_Admin_Menu + * + * @return Base_Admin_Menu + */ + private function get_concrete_menu_admin() { + return $this->getMockBuilder( Base_Admin_Menu::class )->disableOriginalConstructor()->getMockForAbstractClass(); + } +} diff --git a/projects/packages/masterbar/tests/php/test-class-css-customizer-nudge.php b/projects/packages/masterbar/tests/php/test-class-css-customizer-nudge.php new file mode 100644 index 0000000000000..28d894598c408 --- /dev/null +++ b/projects/packages/masterbar/tests/php/test-class-css-customizer-nudge.php @@ -0,0 +1,92 @@ +wp_customize = new \WP_Customize_Manager(); + register_css_nudge_control( $this->wp_customize ); + } + + /** + * Check if the assets are registered. + */ + public function test_it_enqueues_the_assets() { + $nudge = new CSS_Customizer_Nudge( 'url', 'message' ); + + $nudge->customize_register_nudge( $this->wp_customize ); + + $this->assertEquals( + 10, + has_action( + 'customize_controls_enqueue_scripts', + array( + $nudge, + 'customize_controls_enqueue_scripts_nudge', + ) + ) + ); + } + + /** + * Tests customize_controls_enqueue_scripts_nudge + */ + public function test_customize_controls_enqueue_scripts_nudge() { + $nudge = new CSS_Customizer_Nudge( 'url', 'message' ); + $nudge->customize_controls_enqueue_scripts_nudge(); + + $this->assertTrue( wp_script_is( 'additional-css-js' ) ); + $this->assertTrue( wp_style_is( 'additional-css-js' ) ); + } + + /** + * Check if it creates the css nudge control. + */ + public function test_if_it_creates_a_css_nudge_control() { + $nudge = new CSS_Customizer_Nudge( 'url', 'message' ); + + $nudge->customize_register_nudge( $this->wp_customize ); + + $this->assertArrayHasKey( 'custom_css_control', $this->wp_customize->controls() ); + $this->assertArrayHasKey( 'custom_css', $this->wp_customize->sections() ); + } + + /** + * Check if the url and message are passed correctly to the custom control object. + */ + public function test_if_the_url_and_message_are_passed_correctly() { + $nudge = new CSS_Customizer_Nudge( 'url', 'message' ); + + $nudge->customize_register_nudge( $this->wp_customize ); + + $this->assertEquals( 'url', $this->wp_customize->controls()['custom_css_control']->cta_url ); + $this->assertEquals( 'message', $this->wp_customize->controls()['custom_css_control']->nudge_copy ); + } +} diff --git a/projects/packages/masterbar/tests/php/test-class-css-nudge-customize-control.php b/projects/packages/masterbar/tests/php/test-class-css-nudge-customize-control.php new file mode 100644 index 0000000000000..c62d9109022a2 --- /dev/null +++ b/projects/packages/masterbar/tests/php/test-class-css-nudge-customize-control.php @@ -0,0 +1,54 @@ + 'https://wordpress.com', + 'nudge_copy' => 'foo', + ) + ); + + $this->assertEquals( 'https://wordpress.com', $control->cta_url ); + $this->assertEquals( 'foo', $control->nudge_copy ); + ob_start(); + $control->render_content(); + $content = ob_get_contents(); + ob_end_clean(); + $expected_output = '
+

+ foo +

+
+ +
+
'; + $this->assertEquals( $expected_output, $content ); + } +} diff --git a/projects/packages/masterbar/tests/php/test-class-dashboard-switcher-tracking.php b/projects/packages/masterbar/tests/php/test-class-dashboard-switcher-tracking.php new file mode 100644 index 0000000000000..b066d01e3987e --- /dev/null +++ b/projects/packages/masterbar/tests/php/test-class-dashboard-switcher-tracking.php @@ -0,0 +1,117 @@ + 'test_admin', + 'user_pass' => '123', + 'role' => 'administrator', + ) + ); + + wp_set_current_user( static::$user_id ); + } + + /** + * Returning the environment into its initial state. + * + * @after + */ + public function tear_down() { + WorDBless_Options::init()->clear_options(); + WorDBless_Users::init()->clear_all_users(); + } + + /** + * Check if an event is triggered for Jetpack. + */ + public function test_it_creates_event_for_jetpack() { + $tracking = $this->createMock( Tracking::class ); + + $event_properties = array( + 'current_page' => 'foo', + 'destination' => 'bar', + 'plan' => 'business', + ); + + if ( defined( 'IS_WPCOM' ) && IS_WPCOM ) { + $event_properties['blog_id'] = get_current_blog_id(); + } else { + // record_user_event only gets called outside WP.com. + $tracking->expects( $this->once() )->method( 'record_user_event' )->with( + Dashboard_Switcher_Tracking::JETPACK_EVENT_NAME, + $event_properties + ); + } + + $wpcom_tracks = function ( $properties ) use ( $event_properties ) { + $this->assertEquals( $event_properties, $properties ); + }; + + $dashboard_tracking = new Dashboard_Switcher_Tracking( $tracking, $wpcom_tracks, 'business' ); + + $dashboard_tracking->record_switch_event( 'foo', 'bar' ); + } + + /** + * Check if an event is triggered for WPCOM. + */ + public function test_it_creates_event_for_wpcom() { + if ( ! static::$is_wpcom ) { + $this->markTestSkipped( 'Only used on WP.com.' ); + } + + $event_properties = array( + 'current_page' => 'foo', + 'destination' => 'bar', + 'plan' => 'business', + 'blog_id' => 1, + ); + + $tracking = $this->createMock( Tracking::class ); + + $wpcom_tracks = function ( $properties ) use ( $event_properties ) { + $this->assertEquals( $event_properties, $properties ); + }; + + $dashboard_tracking = new Dashboard_Switcher_Tracking( $tracking, $wpcom_tracks, 'business' ); + $dashboard_tracking->record_switch_event( 'foo', 'bar' ); + } +} diff --git a/projects/packages/masterbar/tests/php/test-class-domain-only-admin-menu.php b/projects/packages/masterbar/tests/php/test-class-domain-only-admin-menu.php new file mode 100644 index 0000000000000..6cd2cac27dcb1 --- /dev/null +++ b/projects/packages/masterbar/tests/php/test-class-domain-only-admin-menu.php @@ -0,0 +1,137 @@ +get_site_suffix(); + static::$menu_data = get_menu_fixture(); + static::$submenu_data = get_submenu_fixture(); + + static::$user_id = wp_insert_user( + array( + 'user_login' => 'test_admin', + 'user_pass' => '123', + 'role' => 'administrator', + ) + ); + + wp_set_current_user( static::$user_id ); + + // Initialize in setUp so it registers hooks for every test. + static::$admin_menu = Domain_Only_Admin_Menu::get_instance(); + $menu = static::$menu_data; + $submenu = static::$submenu_data; + } + + /** + * Returning the environment into its initial state. + * + * @after + */ + public function tear_down() { + WorDBless_Options::init()->clear_options(); + WorDBless_Users::init()->clear_all_users(); + } + + /** + * Tests reregister_menu_items when email subscriptions don't exist. + */ + public function test_reregister_menu_items_without_email_subscriptions() { + global $menu; + + // @phan-suppress-next-line PhanDeprecatedFunction -- Needed for PHP 7.0 and 7.1 CI tests. We can replace with onlyMethods once WP 6.7 comes out. + $mock_email_checker = $this->getMockBuilder( WPCOM_Email_Subscription_Checker::class )->setMethods( array( 'has_email' ) )->getMock(); + $mock_email_checker->method( 'has_email' )->willReturn( false ); // always returns false + + static::$admin_menu->set_email_subscription_checker( $mock_email_checker ); + static::$admin_menu->reregister_menu_items(); + + $this->assertCount( 3, $menu ); + + $this->assertEquals( 'https://wordpress.com/domains/manage/' . static::$domain . '/edit/' . static::$domain, $menu[0][2] ); + $this->assertEquals( 'https://wordpress.com/purchases/subscriptions/' . static::$domain, $menu[1][2] ); + $this->assertEquals( 'https://wordpress.com/mailboxes/' . static::$domain, $menu[2][2] ); + } + + /** + * Tests reregister_menu_items with email subscriptions . + */ + public function test_reregister_menu_items_with_email_subscriptions() { + global $menu; + + // @phan-suppress-next-line PhanDeprecatedFunction -- Needed for PHP 7.0 and 7.1 CI tests. We can replace with onlyMethods once WP 6.7 comes out. + $mock_email_checker = $this->getMockBuilder( WPCOM_Email_Subscription_Checker::class )->setMethods( array( 'has_email' ) )->getMock(); + $mock_email_checker->method( 'has_email' )->willReturn( true ); // always returns true + + static::$admin_menu->set_email_subscription_checker( $mock_email_checker ); + static::$admin_menu->reregister_menu_items(); + + $this->assertCount( 4, $menu ); + + $this->assertEquals( 'https://wordpress.com/domains/manage/' . static::$domain . '/edit/' . static::$domain, $menu[0][2] ); + $this->assertEquals( 'https://wordpress.com/email/' . static::$domain . '/manage/' . static::$domain, $menu[1][2] ); + $this->assertEquals( 'https://wordpress.com/purchases/subscriptions/' . static::$domain, $menu[2][2] ); + $this->assertEquals( 'https://wordpress.com/mailboxes/' . static::$domain, $menu[3][2] ); + } +} diff --git a/projects/packages/masterbar/tests/php/test-class-jetpack-admin-menu.php b/projects/packages/masterbar/tests/php/test-class-jetpack-admin-menu.php new file mode 100644 index 0000000000000..1ae3be3b4abbb --- /dev/null +++ b/projects/packages/masterbar/tests/php/test-class-jetpack-admin-menu.php @@ -0,0 +1,229 @@ +get_site_suffix(); + static::$menu_data = array(); + static::$submenu_data = array(); + + static::$user_id = wp_insert_user( + array( + 'user_login' => 'test_admin', + 'user_pass' => '123', + 'role' => 'administrator', + ) + ); + + wp_set_current_user( static::$user_id ); + + // Initialize in setUp so it registers hooks for every test. + static::$admin_menu = Jetpack_Admin_Menu::get_instance(); + $menu = static::$menu_data; + $submenu = static::$submenu_data; + } + + /** + * Returning the environment into its initial state. + * + * @after + */ + public function tear_down() { + WorDBless_Options::init()->clear_options(); + WorDBless_Users::init()->clear_all_users(); + } + + /** + * Tests add_jetpack_menu + * + * @covers ::add_jetpack_menu + */ + public function test_add_jetpack_menu() { + global $submenu; + + static::$admin_menu->add_jetpack_menu(); + $this->assertSame( 'https://wordpress.com/scan/' . static::$domain, $submenu['jetpack'][2][2] ); + } + + /** + * Tests add_tools_menu + * + * @covers ::add_tools_menu + */ + public function test_add_tools_menu() { + global $submenu; + + static::$admin_menu->add_tools_menu(); + + // Check Import/Export menu always links to WP Admin. + $this->assertSame( 'export.php', array_pop( $submenu['tools.php'] )[2] ); + $this->assertSame( 'import.php', array_pop( $submenu['tools.php'] )[2] ); + + $this->assertSame( 'https://wordpress.com/earn/' . static::$domain, array_pop( $submenu['tools.php'] )[2] ); + $this->assertSame( 'https://wordpress.com/marketing/tools/' . static::$domain, array_pop( $submenu['tools.php'] )[2] ); + } + + /** + * Tests add_wp_admin_menu + * + * @covers ::add_wp_admin_menu + */ + public function test_add_wp_admin_menu() { + global $menu; + + static::$admin_menu->add_wp_admin_menu(); + + $this->assertSame( 'index.php', array_pop( $menu )[2] ); + } + + /** + * Tests add_appearance_menu + * + * @covers ::add_appearance_menu + */ + public function test_add_appearance_menu() { + global $submenu; + + static::$admin_menu->add_appearance_menu(); + + // Check Customize menu always links to WP Admin. + $key = 'https://wordpress.com/themes/' . static::$domain; + + $this->assertSame( 'customize.php', array_pop( $submenu[ $key ] )[2] ); + } + + /** + * Tests add_posts_menu + * + * @covers ::add_posts_menu + */ + public function test_add_posts_menu() { + global $menu; + + static::$admin_menu->add_posts_menu(); + + $this->assertSame( 'https://wordpress.com/posts/' . static::$domain, array_shift( $menu )[2] ); + } + + /** + * Tests add_page_menu + * + * @covers ::add_page_menu + */ + public function test_add_page_menu() { + global $menu; + + static::$admin_menu->add_page_menu(); + + $this->assertSame( 'https://wordpress.com/pages/' . static::$domain, array_shift( $menu )[2] ); + } + + /** + * Tests add_users_menu + * + * @covers ::add_users_menu + */ + public function test_add_users_menu() { + global $menu; + + static::$admin_menu->add_users_menu(); + + $this->assertSame( 'https://wordpress.com/people/team/' . static::$domain, array_shift( $menu )[2] ); + } + + /** + * Tests add_users_menu + * + * @covers ::add_feedback_menu + */ + public function add_feedback_menu() { + global $menu; + + static::$admin_menu->add_feedback_menu(); + + $this->assertSame( 'edit.php?post_type=feedback', array_shift( $menu )[2] ); + } + + /** + * Tests add_plugins_menu + * + * @covers ::add_plugins_menu + */ + public function test_add_plugins_menu() { + global $menu; + + static::$admin_menu->add_plugins_menu(); + + // Check Plugins menu always links to Calypso. + + $this->assertSame( 'https://wordpress.com/plugins/' . static::$domain, array_shift( $menu )[2] ); + } +} diff --git a/projects/packages/masterbar/tests/php/test-class-main.php b/projects/packages/masterbar/tests/php/test-class-main.php new file mode 100644 index 0000000000000..f1f687951c948 --- /dev/null +++ b/projects/packages/masterbar/tests/php/test-class-main.php @@ -0,0 +1,51 @@ +assertSame( 1, did_action( 'jetpack_masterbar_init' ) ); + } + + public function test_init_will_return_early_if_called_twice() { + Main::init(); + // @phan-suppress-next-line PhanPluginDuplicateAdjacentStatement -- This is done on purpose to ensure the second call will return early. + Main::init(); + $this->assertSame( 1, did_action( 'jetpack_masterbar_init' ) ); + } + + public function test_init_woa() { + Cache::set( 'is_woa_site', true ); + Main::init(); + $this->assertSame( 1, did_action( 'jetpack_masterbar_init' ) ); + } + + public function test_init_with_jetpack_load_admin_menu_class_filter() { + add_filter( 'jetpack_load_admin_menu_class', '__return_true' ); + Main::init(); + $this->assertSame( 1, did_action( 'jetpack_masterbar_init' ) ); + } +} diff --git a/projects/packages/masterbar/tests/php/test-class-masterbar.php b/projects/packages/masterbar/tests/php/test-class-masterbar.php new file mode 100644 index 0000000000000..c3da52d17d61a --- /dev/null +++ b/projects/packages/masterbar/tests/php/test-class-masterbar.php @@ -0,0 +1,150 @@ +l10n_backup = $l10n; + + static::$user_id = wp_insert_user( + array( + 'user_login' => 'test_admin', + 'user_pass' => '123', + 'role' => 'administrator', + ) + ); + + wp_set_current_user( static::$user_id ); + + $plugin_dir = dirname( __DIR__, 4 ) . '/'; + Jetpack_Constants::set_constant( 'JETPACK__PLUGIN_FILE', $plugin_dir . 'jetpack.php' ); + } + + /** + * Returning the environment into its initial state. + * + * @after + */ + public function tear_down() { + // Restore the original global. + global $l10n; + $l10n = $this->l10n_backup; + + Jetpack_Constants::clear_constants(); + WorDBless_Options::init()->clear_options(); + WorDBless_Users::init()->clear_all_users(); + } + + /** + * Tests unload_non_default_textdomains_on_wpcom_user_locale_switch + * + * @param string $stored_user_locale The user's locale as stored on the site. + * @param string $detected_wpcom_locale The user's WordPress.com locale as detected by the Masterbar. + * + * @dataProvider wpcom_user_locale_switch_data_provider + * @covers ::unload_non_default_textdomains_on_wpcom_user_locale_switch + */ + public function test_unload_non_default_textdomains_on_wpcom_user_locale_switch( + $stored_user_locale, + $detected_wpcom_locale + ) { + // Pretend some textdomains have been loaded. + global $l10n; + $l10n = array( + 'default' => 'MO file', + 'jetpack' => 'MO file', + 'jetpack-boost' => 'MO file', + ); + + // Make get_user_locale() return $stored_user_locale. + wp_get_current_user()->locale = $stored_user_locale; + + // @phan-suppress-next-line PhanDeprecatedFunction -- Needed for PHP 7.0 and 7.1 CI tests. We can replace with addMethods once WP 6.7 comes out. + $masterbar = $this->getMockBuilder( Masterbar::class ) + ->disableOriginalConstructor() + ->setMethodsExcept( array( 'unload_non_default_textdomains_on_wpcom_user_locale_switch' ) ) + ->getMock(); + // @phan-suppress-next-line PhanUndeclaredMethod -- This will be resolved when we start using addMethods above. + $masterbar->unload_non_default_textdomains_on_wpcom_user_locale_switch( $detected_wpcom_locale ); + + // Check the result. + $user_switched_locale = $stored_user_locale !== $detected_wpcom_locale; + if ( $user_switched_locale ) { + // All non-default textdomains should have been unloaded. + $this->assertEquals( array( 'default' ), array_keys( $l10n ) ); + } else { + // No textdomains should have been unloaded. + $this->assertEquals( array( 'default', 'jetpack', 'jetpack-boost' ), array_keys( $l10n ) ); + } + } + + public function test_add_styles_and_scripts() { + // @phan-suppress-next-line PhanDeprecatedFunction -- Needed for PHP 7.0 and 7.1 CI tests. We can replace with addMethods once WP 6.7 comes out. + $masterbar = $this->getMockBuilder( Masterbar::class ) + ->disableOriginalConstructor() + ->setMethodsExcept( array( 'add_styles_and_scripts' ) ) + ->getMock(); + // @phan-suppress-next-line PhanUndeclaredMethod -- This will be resolved when we start using addMethods above. + $masterbar->add_styles_and_scripts(); + + $this->assertTrue( wp_style_is( 'a8c-wpcom-masterbar' ) ); + $this->assertTrue( wp_style_is( 'a8c-wpcom-masterbar-overrides' ) ); + $this->assertTrue( wp_script_is( 'a8c_wpcom_css_override' ) ); + $this->assertTrue( wp_script_is( 'jetpack-accessible-focus' ) ); + $this->assertTrue( wp_script_is( 'a8c_wpcom_masterbar_tracks_events' ) ); + $this->assertTrue( wp_script_is( 'a8c_wpcom_masterbar_overrides' ) ); + } + + /** + * Data provider for test_unload_non_default_textdomains_on_wpcom_user_locale_switch. + * + * @return array With format [stored_user_locale, detected_wpcom_locale]. + */ + public function wpcom_user_locale_switch_data_provider() { + return array( + // Simulate the user changing their locale on WordPress.com. + array( 'fr_FR', 'en_US' ), + array( 'en_US', 'fr_FR' ), + array( 'nl_NL', 'fr_FR' ), + + // No locale change. + array( 'nl_NL', 'nl_NL' ), + array( 'fr_FR', 'fr_FR' ), + ); + } +} diff --git a/projects/packages/masterbar/tests/php/test-class-posts-list-page-notification.php b/projects/packages/masterbar/tests/php/test-class-posts-list-page-notification.php new file mode 100644 index 0000000000000..83f856ea6ee9f --- /dev/null +++ b/projects/packages/masterbar/tests/php/test-class-posts-list-page-notification.php @@ -0,0 +1,93 @@ +assertSame( 10, has_action( 'init', array( $instance, 'init_actions' ) ) ); + + $instance->init_actions(); + + $this->assertSame( 10, has_action( 'map_meta_cap', array( $instance, 'disable_posts_page' ) ) ); + $this->assertSame( 10, has_action( 'post_class', array( $instance, 'add_posts_page_css_class' ) ) ); + $this->assertSame( 10, has_action( 'admin_print_footer_scripts-edit.php', array( $instance, 'add_notification_icon' ) ) ); + } + + /** + * Check if it appends the CSS class. + */ + public function test_it_appends_css_class() { + $instance = new Posts_List_Page_Notification( '5', 'page', '4' ); + + $classes = $instance->add_posts_page_css_class( array(), 'fox', '5' ); + $this->assertEquals( array( 'posts-page' ), $classes ); + + $classes = $instance->add_posts_page_css_class( array( 'bar' ), 'fox', '5' ); + $this->assertEquals( array( 'bar', 'posts-page' ), $classes ); + + $classes = $instance->add_posts_page_css_class( array( 'bar' ), 'fox', '6' ); + $this->assertEquals( array( 'bar' ), $classes ); + } + + /** + * Check if do_not_allow capability is added on Posts Page. + */ + public function test_it_disables_posts_page() { + $instance = new Posts_List_Page_Notification( '5', 'page', '' ); + + $this->assertEquals( array( 'do_not_allow' ), $instance->disable_posts_page( array(), 'edit_post', '6', array( 0 => 5 ) ) ); + $this->assertEquals( array( 'do_not_allow' ), $instance->disable_posts_page( array(), 'delete_post', '6', array( 0 => 5 ) ) ); + + $this->assertEquals( array(), $instance->disable_posts_page( array(), 'edit_post', '6', array( 0 => 6 ) ) ); + $this->assertEquals( array(), $instance->disable_posts_page( array(), 'delete_post', '6', array( 0 => 6 ) ) ); + } + + /** + * Check that the hooks are not loaded when the show_on_front option is not "page". + */ + public function test_it_is_not_loaded_when_show_on_front_option_is_not_page() { + $instance = new Posts_List_Page_Notification( '5', 'posts', '1' ); + + $this->assertFalse( has_action( 'init', array( $instance, 'init_actions' ) ) ); + } + + /** + * Check that the hooks are not loaded when the posts page id and the home page id are the same. + * + * Although in the WP-Admin interface, when the same page is selected in both dropdowns the posts page dropdown is reset, + * internally WordPress will still store the page id in "page_for_posts" site_option. + */ + public function test_it_is_not_loaded_when_posts_page_id_and_home_page_id_are_the_same() { + $instance = new Posts_List_Page_Notification( '5', 'page', '5' ); + $this->assertFalse( has_action( 'init', array( $instance, 'init_actions' ) ) ); + } + + /** + * Tests enqueue_css + */ + public function test_enqueue_css() { + set_current_screen( 'edit-post' ); + + do_action( 'admin_enqueue_scripts' ); + $this->assertTrue( wp_script_is( 'wp-posts-list' ) ); + $this->assertTrue( wp_style_is( 'wp-posts-list' ) ); + } +} diff --git a/projects/packages/masterbar/tests/php/test-class-wpcom-additional-css-manager.php b/projects/packages/masterbar/tests/php/test-class-wpcom-additional-css-manager.php new file mode 100644 index 0000000000000..45cc00d52ca9b --- /dev/null +++ b/projects/packages/masterbar/tests/php/test-class-wpcom-additional-css-manager.php @@ -0,0 +1,55 @@ +wp_customize = new \WP_Customize_Manager(); + } + + /** + * Check if the manager constructs the proper url and copy message. + */ + public function test_it_generates_proper_url_and_nudge() { + $manager = new WPCOM_Additional_CSS_Manager( 'foo.com' ); + + $manager->register_nudge( $this->wp_customize ); + $this->assertEquals( + '/checkout/foo.com/premium', + $this->wp_customize->controls()['jetpack_custom_css_control']->cta_url + ); + $this->assertEquals( + 'Purchase the Explorer plan to
activate CSS customization', + $this->wp_customize->controls()['jetpack_custom_css_control']->nudge_copy + ); + } +} diff --git a/projects/packages/masterbar/tests/php/test-class-wpcom-admin-menu.php b/projects/packages/masterbar/tests/php/test-class-wpcom-admin-menu.php new file mode 100644 index 0000000000000..d4f5555ec26da --- /dev/null +++ b/projects/packages/masterbar/tests/php/test-class-wpcom-admin-menu.php @@ -0,0 +1,270 @@ +get_site_suffix(); + static::$menu_data = get_wpcom_menu_fixture(); + static::$submenu_data = get_submenu_fixture(); + + static::$user_id = wp_insert_user( + array( + 'user_login' => 'test_admin', + 'user_pass' => '123', + 'role' => 'administrator', + ) + ); + + wp_set_current_user( static::$user_id ); + + // @phan-suppress-next-line PhanDeprecatedFunction -- Needed for PHP 7.0 and 7.1 CI tests. We can replace with onlyMethods once WP 6.7 comes out. + $admin_menu = $this->getMockBuilder( WPcom_Admin_Menu::class ) + ->disableOriginalConstructor() + ->setMethods( array( 'should_link_to_wp_admin' ) ) + ->getMock(); + $admin_menu->method( 'should_link_to_wp_admin' )->willReturn( false ); + + // Initialize in setUp so it registers hooks for every test. + static::$admin_menu = $admin_menu::get_instance(); + + $menu = static::$menu_data; + $submenu = static::$submenu_data; + } + + /** + * Returning the environment into its initial state. + * + * @after + */ + public function tear_down() { + WorDBless_Options::init()->clear_options(); + WorDBless_Users::init()->clear_all_users(); + } + + /** + * Tests get_preferred_view + */ + public function test_get_preferred_view() { + static::$admin_menu->set_preferred_view( 'themes.php', 'unknown' ); + $this->assertSame( 'default', static::$admin_menu->get_preferred_view( 'themes.php' ) ); + static::$admin_menu->set_preferred_view( 'plugins.php', 'classic' ); + $this->assertSame( 'default', static::$admin_menu->get_preferred_view( 'plugins.php' ) ); + } + + /** + * Tests add_new_site_link. + */ + public function test_add_new_site_link() { + global $menu; + + static::$admin_menu->add_new_site_link(); + + $new_site_menu_item = array( + 'Add New Site', + 'read', + 'https://wordpress.com/start?ref=calypso-sidebar', + 'Add New Site', + 'menu-top toplevel_page_https://wordpress.com/start?ref=calypso-sidebar', + 'toplevel_page_https://wordpress.com/start?ref=calypso-sidebar', + 'dashicons-plus-alt', + ); + $this->assertSame( array_pop( $menu ), $new_site_menu_item ); + } + + /** + * Tests add_site_card_menu + */ + public function test_add_site_card_menu() { + global $menu; + + if ( ! static::$is_wpcom ) { + $this->markTestSkipped( 'Only used on WP.com.' ); + } + + static::$admin_menu->add_site_card_menu(); + + $home_url = home_url(); + $site_card_menu_item = array( + // phpcs:ignore Squiz.Strings.DoubleQuoteUsage.NotRequired + ' +
+
' . get_option( 'blogname' ) . '
+
' . static::$domain . '
+ +
', + 'read', + $home_url, + 'site-card', + 'menu-top toplevel_page_' . $home_url, + 'toplevel_page_' . $home_url, + plugins_url( 'src/admin-menu/globe-icon.svg', dirname( __DIR__ ) ), + ); + + $this->assertEquals( $menu[1], $site_card_menu_item ); + } + + /** + * Tests set_site_card_menu_class + */ + public function test_set_site_card_menu_class() { + global $menu; + + if ( ! static::$is_wpcom ) { + $this->markTestSkipped( 'Only used on WP.com.' ); + } + + static::$admin_menu->add_site_card_menu(); + + $menu = static::$admin_menu->set_site_card_menu_class( $menu ); + $this->assertStringNotContainsString( 'has-site-icon', $menu[1][4] ); + + // Custom site icon triggers CSS class. + add_filter( 'get_site_icon_url', array( $this, 'custom_site_icon_url' ) ); + $menu = static::$admin_menu->set_site_card_menu_class( $menu ); + remove_filter( 'get_site_icon_url', array( $this, 'custom_site_icon_url' ) ); + $this->assertStringContainsString( 'has-site-icon', $menu[1][4] ); + } + + /** + * Custom site icon. + * + * @return string + */ + public function custom_site_icon_url() { + return 'https://s0.wp.com/i/jetpack.png'; + } + + /** + * Tests add_upgrades_menu + */ + public function test_add_upgrades_menu() { + global $submenu; + + static::$admin_menu->add_upgrades_menu(); + + $this->assertSame( 'https://wordpress.com/plans/' . static::$domain, $submenu['paid-upgrades.php'][1][2] ); + $this->assertSame( 'https://wordpress.com/domains/manage/' . static::$domain, $submenu['paid-upgrades.php'][2][2] ); + + /** This filter is already documented in modules/masterbar/admin-menu/class-atomic-admin-menu.php */ + if ( apply_filters( 'jetpack_show_wpcom_upgrades_email_menu', false ) ) { + $this->assertSame( 'https://wordpress.com/email/' . static::$domain, $submenu['paid-upgrades.php'][3][2] ); + $this->assertSame( 'https://wordpress.com/purchases/subscriptions/' . static::$domain, $submenu['paid-upgrades.php'][4][2] ); + } else { + $this->assertSame( 'https://wordpress.com/purchases/subscriptions/' . static::$domain, $submenu['paid-upgrades.php'][3][2] ); + } + } + + /** + * Tests add_my_mailboxes_menu + */ + public function test_add_my_mailboxes_menu() { + global $menu; + + static::$admin_menu->add_my_mailboxes_menu(); + + $this->assertSame( 'https://wordpress.com/mailboxes/' . static::$domain, $menu['4.64424'][2] ); + } + + /** + * Tests add_users_menu + */ + public function test_add_users_menu() { + global $submenu; + + // Check that menu always links to Calypso when no preferred view has been set. + static::$admin_menu->set_preferred_view( 'users.php', 'unknown' ); + static::$admin_menu->add_users_menu(); + $this->assertSame( 'https://wordpress.com/people/team/' . static::$domain, array_shift( $submenu['users.php'] )[2] ); + $this->assertSame( 'https://wordpress.com/subscribers/' . static::$domain, $submenu['users.php'][2][2] ); + } + + /** + * Tests add_options_menu + */ + public function test_add_options_menu() { + global $submenu; + + static::$admin_menu->add_options_menu(); + + $this->assertSame( 'https://wordpress.com/hosting-config/' . static::$domain, $submenu['options-general.php'][10][2] ); + } + + /** + * Tests remove_gutenberg_menu + */ + public function test_remove_gutenberg_menu() { + global $menu; + static::$admin_menu->remove_gutenberg_menu(); + + // Gutenberg plugin menu should not be visible. + $this->assertArrayNotHasKey( 101, $menu ); + } +} diff --git a/projects/packages/masterbar/tests/php/test-class-wpcom-user-profile-fields-revert.php b/projects/packages/masterbar/tests/php/test-class-wpcom-user-profile-fields-revert.php new file mode 100644 index 0000000000000..099e1450b86e3 --- /dev/null +++ b/projects/packages/masterbar/tests/php/test-class-wpcom-user-profile-fields-revert.php @@ -0,0 +1,126 @@ +l10n_backup = $l10n; + + static::$user_id = wp_insert_user( + array( + 'user_login' => 'test_admin', + 'role' => 'administrator', + 'user_pass' => '123', + 'display_name' => 'old_value', + 'description' => 'old_description', + 'first_name' => 'old_first_name', + 'last_name' => 'old_last_name', + ) + ); + + wp_set_current_user( static::$user_id ); + } + + /** + * Returning the environment into its initial state. + * + * @after + */ + public function tear_down() { + // Restore the original global. + global $l10n; + $l10n = $this->l10n_backup; + + WorDBless_Options::init()->clear_options(); + WorDBless_Users::init()->clear_all_users(); + } + + /** + * Check if the revert ignores not connected users. + */ + public function test_if_it_skips_not_connected_users() { + $connection_manager = $this->createMock( Connection_Manager::class ); + $connection_manager->method( 'is_user_connected' )->willReturn( false ); + $service = new WPCOM_User_Profile_Fields_Revert( $connection_manager ); + + $new_data = array( 'display_name' => 'new_value' ); + $data = $service->revert_user_data_on_wp_admin_profile_update( $new_data, true, self::$user_id ); + + $this->assertEquals( 'new_value', $data['display_name'] ); + } + + /** + * Check if the implementation prevents updating the display_name. + */ + public function test_revert_display_name() { + $connection_manager = $this->createMock( Connection_Manager::class ); + $connection_manager->method( 'is_user_connected' )->willReturn( true ); + $service = new WPCOM_User_Profile_Fields_Revert( $connection_manager ); + + $new_data = array( 'display_name' => 'new_value' ); + $data = $service->revert_user_data_on_wp_admin_profile_update( $new_data, true, self::$user_id ); + + $this->assertEquals( 'old_value', $data['display_name'] ); + } + + /** + * Check if the revert works for first_name, last_name and description fields. + */ + public function test_revert_user_fields() { + $connection_manager = $this->createMock( Connection_Manager::class ); + $connection_manager->method( 'is_user_connected' )->willReturn( true ); + $service = new WPCOM_User_Profile_Fields_Revert( $connection_manager ); + + $new_data = array( + 'description' => 'new_description', + 'first_name' => 'new_firstname', + 'last_name' => 'new_lastname', + ); + + $data = $service->revert_user_meta_on_wp_admin_profile_change( + $new_data, + get_userdata( self::$user_id ), + true + ); + + $this->assertEquals( 'old_description', $data['description'] ); + $this->assertEquals( 'old_first_name', $data['first_name'] ); + $this->assertEquals( 'old_last_name', $data['last_name'] ); + } +} diff --git a/projects/packages/masterbar/tools/webpack.config.js b/projects/packages/masterbar/tools/webpack.config.js new file mode 100644 index 0000000000000..c7ee9f965b2a2 --- /dev/null +++ b/projects/packages/masterbar/tools/webpack.config.js @@ -0,0 +1,112 @@ +const path = require( 'path' ); +const jetpackWebpackConfig = require( '@automattic/jetpack-webpack-config/webpack' ); +const RemoveAssetWebpackPlugin = require( '@automattic/remove-asset-webpack-plugin' ); +const glob = require( 'glob' ); + +const sharedWebpackConfig = { + mode: jetpackWebpackConfig.mode, + devtool: jetpackWebpackConfig.devtool, + output: { + ...jetpackWebpackConfig.output, + path: path.join( __dirname, '../dist' ), + }, + optimization: { + ...jetpackWebpackConfig.optimization, + }, + resolve: { + ...jetpackWebpackConfig.resolve, + alias: { + ...jetpackWebpackConfig.resolve.alias, + fs: false, + }, + }, + node: {}, + externals: { + ...jetpackWebpackConfig.externals, + jetpackConfig: JSON.stringify( { + consumer_slug: 'jetpack-masterbar', + } ), + }, + module: { + strictExportPresence: true, + rules: [ + // Transpile JavaScript + jetpackWebpackConfig.TranspileRule( { + exclude: /node_modules\//, + } ), + + // Handle CSS. + jetpackWebpackConfig.CssRule( { + extensions: [ 'css', 'sass', 'scss' ], + extraLoaders: [ + { + loader: 'postcss-loader', + options: { + postcssOptions: { plugins: [ require( 'autoprefixer' ) ] }, + }, + }, + { + loader: 'sass-loader', + options: { + sassOptions: { + // The minifier will minify if necessary. + outputStyle: 'expanded', + }, + }, + }, + ], + } ), + + // Leave fonts and images in place. + { + test: /\.(eot|ttf|woff|png|svg)$/i, + type: 'asset/resource', + generator: { + emit: false, + filename: '[file]', + }, + }, + ], + }, +}; + +const masterbarCssEntriesForAdminColorSchemes = {}; +// prettier-ignore +for ( const file of glob + .sync( 'src/admin-color-schemes/colors/**/*.scss' ) + .filter( n => ! path.basename( n ).startsWith( '_' ) ) +) { + masterbarCssEntriesForAdminColorSchemes[ file.substring( 4, file.length - 5 ) ] = './' + file; +} + +const masterBarJsFiles = {}; +for ( const file of glob + .sync( 'src/**/*.js' ) + .filter( name => ! name.endsWith( '.min.js' ) && name.indexOf( '/test/' ) < 0 ) ) { + masterBarJsFiles[ file.substring( 4, file.length - 3 ) ] = './' + file; +} + +module.exports = [ + { + ...sharedWebpackConfig, + entry: masterbarCssEntriesForAdminColorSchemes, + plugins: [ + ...jetpackWebpackConfig.StandardPlugins( { + DependencyExtractionPlugin: false, + I18nLoaderPlugin: false, + I18nCheckPlugin: false, + MiniCssWithRtlPlugin: false, + WebpackRtlPlugin: false, + } ), + // Delete the dummy JS files Webpack would otherwise create. + new RemoveAssetWebpackPlugin( { + assets: /\.js(\.map)?$/, + } ), + ], + }, + { + ...sharedWebpackConfig, + entry: masterBarJsFiles, + plugins: [ ...jetpackWebpackConfig.StandardPlugins() ], + }, +];