Skip to content

Commit

Permalink
Admin: ensure Jetpack settings can be accessed in Offline mode. (#19815)
Browse files Browse the repository at this point in the history
  • Loading branch information
jeherve authored May 13, 2021
1 parent c907698 commit b796976
Show file tree
Hide file tree
Showing 5 changed files with 233 additions and 66 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
/**
* External dependencies
*/
import { connect } from 'react-redux';
import React, { useCallback } from 'react';
import { __ } from '@wordpress/i18n';

/**
* Internal dependencies
*/
import analytics from 'lib/analytics';
import Button from 'components/button';
import ButtonGroup from 'components/button-group';
import {
getSiteConnectionStatus,
hasConnectedOwner as hasConnectedOwnerSelector,
isCurrentUserLinked as isCurrentUserLinkedSelector,
isSiteRegistered,
} from 'state/connection';
import { userCanEditPosts, userCanManageOptions } from 'state/initial-state';
import { getActiveModules } from 'state/modules';

const HeaderNavComponent = props => {
const {
activeModules,
canEditPosts,
canManageOptions,
hasConnectedOwner,
isCurrentUserLinked,
isSiteConnected,
location = { pathname: '' },
siteConnectionStatus,
} = props;

const { pathname } = location;

const isDashboardView =
[ '/', '/dashboard', '/my-plan', '/plans' ].includes( pathname ) ||
pathname.includes( '/recommendations' );
const isStatic = '' === pathname;

const onTrackDashClick = useCallback( () => {
analytics.tracks.recordJetpackClick( {
target: 'masthead',
path: 'nav_dashboard',
} );
}, [] );

const onTrackSettingsClick = useCallback( () => {
analytics.tracks.recordJetpackClick( {
target: 'masthead',
path: 'nav_settings',
} );
}, [] );

if ( isStatic ) {
return null;
}

if ( ! siteConnectionStatus ) {
return null;
}

/**
* Determine whether a user can access the Jetpack Settings page.
*
* Rules are:
* - We're not on the /setup page route
* - user is allowed to see the Jetpack Admin
* - site is connected or in offline mode
* - non-admins only need access to the settings when there are modules they can manage.
*/
if ( pathname.startsWith( '/setup' ) ) {
return null;
}

if ( ! canEditPosts ) {
return null;
}

if ( siteConnectionStatus !== 'offline' && ! isSiteConnected ) {
return null;
}

if ( siteConnectionStatus !== 'offline' && ! canManageOptions ) {
if ( ! hasConnectedOwner ) {
return null;
}

if ( ! isCurrentUserLinked ) {
return null;
}

// Are there any modules accessible by non-admins active?
const activeNonAdminModules = activeModules.some( module =>
[ 'post-by-email', 'publicize' ].includes( module )
);

if ( ! activeNonAdminModules ) {
return null;
}
}

return (
<div className="jp-masthead__nav">
<ButtonGroup>
<Button
compact={ true }
href="#/dashboard"
primary={ isDashboardView && ! isStatic }
onClick={ onTrackDashClick }
>
{ __( 'Dashboard', 'jetpack' ) }
</Button>
<Button
compact={ true }
href="#/settings"
primary={ ! isDashboardView && ! isStatic }
onClick={ onTrackSettingsClick }
>
{ __( 'Settings', 'jetpack' ) }
</Button>
</ButtonGroup>
</div>
);
};

const HeaderNav = connect( state => {
return {
canEditPosts: userCanEditPosts( state ),
canManageOptions: userCanManageOptions( state ),
hasConnectedOwner: hasConnectedOwnerSelector( state ),
isCurrentUserLinked: isCurrentUserLinkedSelector( state ),
isSiteConnected: isSiteRegistered( state ),
activeModules: getActiveModules( state ),
siteConnectionStatus: getSiteConnectionStatus( state ),
};
} )( HeaderNavComponent );

export { HeaderNav };
70 changes: 9 additions & 61 deletions projects/plugins/jetpack/_inc/client/components/masthead/index.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,42 +3,20 @@
*/
import React from 'react';
import { connect } from 'react-redux';
import { includes } from 'lodash';
import { __ } from '@wordpress/i18n';

/**
* Internal dependencies
*/
import analytics from 'lib/analytics';
import Button from 'components/button';
import ButtonGroup from 'components/button-group';
import { HeaderNav } from './header-nav';
import {
getSiteConnectionStatus,
getSandboxDomain,
fetchSiteConnectionTest,
} from 'state/connection';
import { getCurrentVersion, userCanEditPosts } from 'state/initial-state';
import JetpackLogo from '../jetpack-logo';

export class Masthead extends React.Component {
static defaultProps = {
location: { pathname: '' },
};

trackDashClick = () => {
analytics.tracks.recordJetpackClick( {
target: 'masthead',
path: 'nav_dashboard',
} );
};

trackSettingsClick = () => {
analytics.tracks.recordJetpackClick( {
target: 'masthead',
path: 'nav_settings',
} );
};

trackLogoClick = () => {
analytics.tracks.recordJetpackClick( {
target: 'masthead',
Expand All @@ -51,28 +29,23 @@ export class Masthead extends React.Component {
};

render() {
const offlineNotice =
this.props.siteConnectionStatus === 'offline' ? <code>Offline Mode</code> : '',
sandboxedBadge = this.props.sandboxDomain ? (
const { sandboxDomain, siteConnectionStatus } = this.props;

const offlineNotice = siteConnectionStatus === 'offline' ? <code>Offline Mode</code> : '',
sandboxedBadge = sandboxDomain ? (
<code
id="sandbox-domain-badge"
onClick={ this.testConnection }
onKeyDown={ this.testConnection }
role="button"
tabIndex={ 0 }
title={ `Sandboxing via ${ this.props.sandboxDomain }. Click to test connection.` }
title={ `Sandboxing via ${ sandboxDomain }. Click to test connection.` }
>
API Sandboxed
</code>
) : (
''
),
isDashboardView =
includes( [ '/', '/dashboard', '/my-plan', '/plans' ], this.props.location.pathname ) ||
this.props.location.pathname.includes( '/recommendations' ),
isStatic = '' === this.props.location.pathname;

const hideNav = this.props.location.pathname.startsWith( '/setup' );
);

return (
<div className="jp-masthead">
Expand All @@ -84,30 +57,7 @@ export class Masthead extends React.Component {
{ offlineNotice }
{ sandboxedBadge }
</div>
{ this.props.userCanEditPosts && ! hideNav && (
<div className="jp-masthead__nav">
{ ! isStatic && this.props.siteConnectionStatus && (
<ButtonGroup>
<Button
compact={ true }
href="#/dashboard"
primary={ isDashboardView && ! isStatic }
onClick={ this.trackDashClick }
>
{ __( 'Dashboard', 'jetpack' ) }
</Button>
<Button
compact={ true }
href="#/settings"
primary={ ! isDashboardView && ! isStatic }
onClick={ this.trackSettingsClick }
>
{ __( 'Settings', 'jetpack' ) }
</Button>
</ButtonGroup>
) }
</div>
) }
<HeaderNav location={ this.props.location } />
</div>
</div>
);
Expand All @@ -117,10 +67,8 @@ export class Masthead extends React.Component {
export default connect(
state => {
return {
siteConnectionStatus: getSiteConnectionStatus( state ),
sandboxDomain: getSandboxDomain( state ),
currentVersion: getCurrentVersion( state ),
userCanEditPosts: userCanEditPosts( state ),
siteConnectionStatus: getSiteConnectionStatus( state ),
};
},
dispatch => {
Expand Down
12 changes: 12 additions & 0 deletions projects/plugins/jetpack/_inc/client/state/modules/reducer.js
Original file line number Diff line number Diff line change
Expand Up @@ -195,6 +195,18 @@ export function getModules( state ) {
return state.jetpack.modules.items;
}

/**
* Returns an array of module slugs for all active modules on the site.
*
* @param {object} state - Global state tree
* @returns {Array} Array of module slugs.
*/
export function getActiveModules( state ) {
return Object.keys( state.jetpack.modules.items ).filter(
module_slug => state.jetpack.modules.items[ module_slug ].activated
);
}

/**
* Returns a module object by its name as present in the state
* @param {Object} state Global state tree
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -62,16 +62,79 @@ function jetpack_add_dashboard_sub_nav_item() {
}

/**
* If user is allowed to see the Jetpack Admin, add Settings sub-link.
* Determine whether a user can access the Jetpack Settings page.
*
* Rules are:
* - user is allowed to see the Jetpack Admin
* - site is connected or in offline mode
* - non-admins only need access to the settings when there are modules they can manage.
*
* @return bool $can_access_settings Can the user access settings.
*/
private function can_access_settings() {
$connection = new Connection_Manager( 'jetpack' );
$status = new Status();

// User must have the necessary permissions to see the Jetpack settings pages.
if ( ! current_user_can( 'edit_posts' ) ) {
return false;
}

// In offline mode, allow access to admins.
if ( $status->is_offline_mode() && current_user_can( 'manage_options' ) ) {
return true;
}

// If not in offline mode but site is not connected, bail.
if ( ! Jetpack::is_connection_ready() ) {
return false;
}

/*
* Additional checks for non-admins.
*/
if ( ! current_user_can( 'manage_options' ) ) {
// If the site isn't connected at all, bail.
if ( ! $connection->has_connected_owner() ) {
return false;
}

/*
* If they haven't connected their own account yet,
* they have no use for the settings page.
* They will not be able to manage any settings.
*/
if ( ! $connection->is_user_connected() ) {
return false;
}

/*
* Non-admins only have access to settings
* for the following modules:
* - Publicize
* - Post By Email
* If those modules are not available, bail.
*/
if (
! Jetpack::is_module_active( 'post-by-email' )
&& ! Jetpack::is_module_active( 'publicize' )
) {
return false;
}
}

// fallback.
return true;
}

/**
* Jetpack Settings sub-link.
*
* @since 4.3.0
* @since 9.7.0 If Connection does not have an owner, restrict it to admins
*/
function jetpack_add_settings_sub_nav_item() {
if ( ! ( new Connection_Manager( 'jetpack' ) )->has_connected_owner() && ! current_user_can( 'jetpack_connect' ) ) {
return;
}
if ( ( ( new Status() )->is_offline_mode() || Jetpack::is_connection_ready() ) && current_user_can( 'edit_posts' ) ) {
if ( $this->can_access_settings() ) {
add_submenu_page( 'jetpack', __( 'Settings', 'jetpack' ), __( 'Settings', 'jetpack' ), 'jetpack_admin_page', 'jetpack#/settings', '__return_null' );
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
Significance: patch
Type: bugfix

Dashboard: ensure that the Jetpack settings page can be accessed when using Jetpack's Offline mode.

0 comments on commit b796976

Please sign in to comment.