diff --git a/client/lib/site/computed-attributes.js b/client/lib/site/computed-attributes.js index 7c420b322d697..a1fea741cab8c 100644 --- a/client/lib/site/computed-attributes.js +++ b/client/lib/site/computed-attributes.js @@ -49,11 +49,5 @@ export default function( site ) { isHttps( attributes.options.unmapped_url ) ); - //TODO:(ehg) Replace instances with canCurrentUser selector when my-sites/sidebar is connected - attributes.is_customizable = !! ( - site.capabilities && - site.capabilities.edit_theme_options - ); - return attributes; } diff --git a/client/lib/site/index.js b/client/lib/site/index.js index 9b79fcd9fcea4..7afc4945ecb67 100644 --- a/client/lib/site/index.js +++ b/client/lib/site/index.js @@ -218,8 +218,4 @@ Site.prototype.isUpgradeable = function() { return this.capabilities && this.capabilities.manage_options; }; -Site.prototype.isCustomizable = function() { - return !! ( this.capabilities && this.capabilities.edit_theme_options ); -}; - module.exports = Site; diff --git a/client/state/selectors/index.js b/client/state/selectors/index.js index 3926e8ce14f4b..26452e9005870 100644 --- a/client/state/selectors/index.js +++ b/client/state/selectors/index.js @@ -139,6 +139,7 @@ export isSendingBillingReceiptEmail from './is-sending-billing-receipt-email'; export isSharingButtonsSaveSuccessful from './is-sharing-buttons-save-successful'; export isSiteAutomatedTransfer from './is-site-automated-transfer'; export isSiteBlocked from './is-site-blocked'; +export isSiteCustomizable from './is-site-customizable'; export isSiteOnFreePlan from './is-site-on-free-plan'; export isSiteSupportingImageEditor from './is-site-supporting-image-editor'; export isSiteUpgradeable from './is-site-upgradeable'; diff --git a/client/state/selectors/is-site-customizable.js b/client/state/selectors/is-site-customizable.js new file mode 100644 index 0000000000000..524f266913e45 --- /dev/null +++ b/client/state/selectors/is-site-customizable.js @@ -0,0 +1,24 @@ +/** + * Internal dependencies + */ +import { canCurrentUser } from 'state/selectors'; +import { getCurrentUserId } from 'state/current-user/selectors'; +import { getRawSite } from 'state/sites/selectors'; + +/** + * Returns true if the site can be customized by the user, false if the + * site cannot be customized, or null if customizing ability cannot be + * determined. + * + * @param {Object} state Global state tree + * @param {Number} siteId Site ID + * @return {?Boolean} Whether site is customizable + */ +export default function isSiteCustomizable( state, siteId ) { + // Cannot determine site customizing ability if there is no current user + if ( ! getCurrentUserId( state ) || ! getRawSite( state, siteId ) ) { + return null; + } + + return canCurrentUser( state, siteId, 'edit_theme_options' ); +} diff --git a/client/state/selectors/test/get-visible-sites.js b/client/state/selectors/test/get-visible-sites.js index 7b3da84759372..f1c4805401a65 100644 --- a/client/state/selectors/test/get-visible-sites.js +++ b/client/state/selectors/test/get-visible-sites.js @@ -55,7 +55,6 @@ describe( 'getVisibleSites()', () => { domain: 'example.com', slug: 'example.com', hasConflict: false, - is_customizable: false, is_previewable: false, options: { default_post_format: 'standard', diff --git a/client/state/selectors/test/is-site-customizable.js b/client/state/selectors/test/is-site-customizable.js new file mode 100644 index 0000000000000..e139a768724a5 --- /dev/null +++ b/client/state/selectors/test/is-site-customizable.js @@ -0,0 +1,58 @@ +/** + * External dependencies + */ +import { expect } from 'chai'; + +/** + * Internal dependencies + */ +import { isSiteCustomizable } from '../'; + +describe( 'isSiteCustomizable()', () => { + it( 'should return null if the capability is not set for the current user', () => { + const isCustomizable = isSiteCustomizable( { + sites: { + items: { + 77203199: { + ID: 77203199, + URL: 'https://example.com' + } + } + }, + currentUser: { + id: 12345678, + capabilities: { + 77203199: {} + } + } + }, 77203199 ); + + expect( isCustomizable ).to.be.null; + } ); + + it( 'should return true is the corresponding user capability is true for this site', () => { + const isCustomizable = isSiteCustomizable( { + sites: { + items: { + 77203199: { + ID: 77203199, + URL: 'http://example.com', + options: { + unmapped_url: 'http://example.com' + } + } + } + }, + currentUser: { + id: 12345678, + capabilities: { + 77203199: { + edit_theme_options: true + } + } + } + }, 77203199 ); + + expect( isCustomizable ).to.be.true; + } ); +} ); diff --git a/client/state/sites/selectors.js b/client/state/sites/selectors.js index 2e0c25ac761c5..4d6dc929f022c 100644 --- a/client/state/sites/selectors.js +++ b/client/state/sites/selectors.js @@ -30,7 +30,6 @@ import { isHttps, withoutHttp, addQueryArgs, urlToSlug } from 'lib/url'; import createSelector from 'lib/create-selector'; import { fromApi as seoTitleFromApi } from 'components/seo/meta-title-editor/mappings'; import versionCompare from 'lib/version-compare'; -import getComputedAttributes from 'lib/site/computed-attributes'; import { getCustomizerFocus } from 'my-sites/customize/panels'; /** @@ -63,18 +62,40 @@ export const getSite = createSelector( return { ...site, - ...getComputedAttributes( site ), ...getJetpackComputedAttributes( state, siteId ), hasConflict: isSiteConflicting( state, siteId ), title: getSiteTitle( state, siteId ), slug: getSiteSlug( state, siteId ), domain: getSiteDomain( state, siteId ), - is_previewable: isSitePreviewable( state, siteId ) + is_previewable: isSitePreviewable( state, siteId ), + options: computeSiteOptions( state, siteId ), }; }, ( state ) => state.sites.items ); +export function computeSiteOptions( state, siteId ) { + const site = getRawSite( state, siteId ); + if ( ! site ) { + return null; + } + + const isWpcomMappedDomain = getSiteOption( state, siteId, 'is_mapped_domain' ) && ! isJetpackSite( state, siteId ); + const wpcomUrl = withoutHttp( getSiteOption( state, siteId, 'unmapped_url' ) ); + + // The 'standard' post format is saved as an option of '0' + let defaultPostFormat = getSiteOption( state, siteId, 'default_post_format' ); + if ( ! defaultPostFormat || defaultPostFormat === '0' ) { + defaultPostFormat = 'standard'; + } + + return { + ...site.options, + ...isWpcomMappedDomain && { wpcom_url: wpcomUrl }, + default_post_format: defaultPostFormat + }; +} + export function getJetpackComputedAttributes( state, siteId ) { if ( ! isJetpackSite( state, siteId ) ) { return {}; diff --git a/client/state/sites/test/selectors.js b/client/state/sites/test/selectors.js index 7df2e4645a329..1708445f01b4b 100644 --- a/client/state/sites/test/selectors.js +++ b/client/state/sites/test/selectors.js @@ -11,6 +11,7 @@ import config from 'config'; import { useSandbox } from 'test/helpers/use-sinon'; import { getSite, + computeSiteOptions, getSiteCollisions, isSiteConflicting, isSingleUserSite, @@ -104,7 +105,6 @@ describe( 'selectors', () => { domain: 'example.com', slug: 'example.com', hasConflict: false, - is_customizable: false, is_previewable: true, options: { default_post_format: 'standard', @@ -114,6 +114,64 @@ describe( 'selectors', () => { } ); } ); + describe( '#computeSiteOptions()', () => { + it( 'should return null if the site is not known', () => { + const siteOptions = computeSiteOptions( { + sites: { + items: {} + } + }, 2916284 ); + + expect( siteOptions ).to.be.null; + } ); + + it( 'should return a the site options along with the computed option wpcom_url', () => { + const siteOptions = computeSiteOptions( { + sites: { + items: { + 2916284: { + ID: 2916284, + URL: 'https://example.com', + options: { + unmapped_url: 'https://example.wordpress.com', + is_mapped_domain: true + } + } + } + } + }, 2916284 ); + + expect( siteOptions ).to.eql( { + default_post_format: 'standard', + unmapped_url: 'https://example.wordpress.com', + is_mapped_domain: true, + wpcom_url: 'example.wordpress.com' + } ); + } ); + + it( 'should fix `default_post_format` if it is equal to \'0\'', () => { + const siteOptions = computeSiteOptions( { + sites: { + items: { + 2916284: { + ID: 2916284, + URL: 'https://example.com', + options: { + default_post_format: '0', + unmapped_url: 'https://example.wordpress.com' + } + } + } + } + }, 2916284 ); + + expect( siteOptions ).to.eql( { + default_post_format: 'standard', + unmapped_url: 'https://example.wordpress.com' + } ); + } ); + } ); + describe( '#getSiteCollisions', () => { it( 'should not consider distinct URLs as conflicting', () => { const collisions = getSiteCollisions( { diff --git a/client/state/ui/guided-tours/contexts.js b/client/state/ui/guided-tours/contexts.js index 68d203ae8df2b..e5856eaf40ad2 100644 --- a/client/state/ui/guided-tours/contexts.js +++ b/client/state/ui/guided-tours/contexts.js @@ -12,7 +12,7 @@ import { } from 'state/ui/selectors'; import { getLastAction } from 'state/ui/action-log/selectors'; import { getCurrentUser } from 'state/current-user/selectors'; -import { canCurrentUser } from 'state/selectors'; +import { canCurrentUser, isSiteCustomizable } from 'state/selectors'; import { hasDefaultSiteTitle, isCurrentPlanPaid, @@ -122,7 +122,7 @@ export const isSelectedSitePreviewable = state => * @return {Boolean} True if user can run customizer, false otherwise. */ export const isSelectedSiteCustomizable = state => - getSelectedSite( state ) && getSelectedSite( state ).is_customizable; + isSiteCustomizable( state, getSelectedSiteId( state ) ); /** * Returns a selector that tests whether an A/B test is in a given variant. diff --git a/client/state/ui/test/selectors.js b/client/state/ui/test/selectors.js index 2150db7b6bb09..eb1e4ccdf35e3 100644 --- a/client/state/ui/test/selectors.js +++ b/client/state/ui/test/selectors.js @@ -48,7 +48,6 @@ describe( 'selectors', () => { URL: 'https://example.com', domain: 'example.com', hasConflict: false, - is_customizable: false, is_previewable: false, options: { default_post_format: 'standard',