diff --git a/client/modules/core/helpers/apps.js b/client/modules/core/helpers/apps.js
index 94f34af1c73..54da9f6d3f4 100644
--- a/client/modules/core/helpers/apps.js
+++ b/client/modules/core/helpers/apps.js
@@ -4,6 +4,7 @@ import { Meteor } from "meteor/meteor";
import { Roles } from "meteor/alanning:roles";
import { Reaction } from "/client/api";
import { Packages, Shops } from "/lib/collections";
+import { Registry } from "/lib/collections/schemas/registry";
/**
@@ -152,7 +153,24 @@ export function Apps(optionHash) {
return false;
}
- return _.isMatch(item, itemFilter);
+ const filterKeys = Object.keys(itemFilter);
+ // Loop through all keys in the itemFilter
+ // each filter item should match exactly with the property in the registry or
+ // should be included in the array if that property is an array
+ return filterKeys.every((property) => {
+ // Check to see if the schema for this property is an array
+ // if so, we want to make sure that this item is included in the array
+ if (Array.isArray(Registry._schema[property].type())) {
+ // Check to see if the registry entry is an array.
+ // Legacy registry entries could exist that use a string even when the schema requires an array.
+ if (Array.isArray(item[property])) {
+ return item[property].includes(itemFilter[property]);
+ }
+ }
+
+ // If it's not an array, the filter should match exactly
+ return item[property] === itemFilter[property];
+ });
});
for (const registry of matchingRegistry) {
diff --git a/client/modules/core/main.js b/client/modules/core/main.js
index cc8b8c83366..31e21d60b90 100644
--- a/client/modules/core/main.js
+++ b/client/modules/core/main.js
@@ -47,46 +47,42 @@ export default {
Tracker.autorun(() => {
let shop;
if (this.Subscriptions.PrimaryShop.ready()) {
- // if we've already set the primaryShopId, carry on.
- // otherwise we need to define it.
- if (!this.primaryShopId) {
- // There should only ever be one "primary" shop
- shop = Shops.findOne({
- shopType: "primary"
- });
-
- if (shop) {
- this.primaryShopId = shop._id;
- this.primaryShopName = shop.name;
-
- // We'll initialize locale and currency for the primary shop unless
- // marketplace settings exist and merchantLocale is set to true
- if (this.marketplace.merchantLocale !== true) {
- // initialize local client Countries collection
- if (!Countries.findOne()) {
- createCountryCollection(shop.locales.countries);
- }
+ // There should only ever be one "primary" shop
+ shop = Shops.findOne({
+ shopType: "primary"
+ });
+
+ if (shop) {
+ this.primaryShopId = shop._id;
+ this.primaryShopName = shop.name;
+
+ // We'll initialize locale and currency for the primary shop unless
+ // marketplace settings exist and merchantLocale is set to true
+ if (this.marketplace.merchantLocale !== true) {
+ // initialize local client Countries collection
+ if (!Countries.findOne()) {
+ createCountryCollection(shop.locales.countries);
+ }
- const locale = this.Locale.get() || {};
+ const locale = this.Locale.get() || {};
- // fix for https://github.com/reactioncommerce/reaction/issues/248
- // we need to keep an eye for rates changes
- if (typeof locale.locale === "object" &&
+ // fix for https://github.com/reactioncommerce/reaction/issues/248
+ // we need to keep an eye for rates changes
+ if (typeof locale.locale === "object" &&
typeof locale.currency === "object" &&
typeof locale.locale.currency === "string") {
- const localeCurrency = locale.locale.currency.split(",")[0];
- if (typeof shop.currencies[localeCurrency] === "object") {
- if (typeof shop.currencies[localeCurrency].rate === "number") {
- locale.currency.rate = shop.currencies[localeCurrency].rate;
- localeDep.changed();
- }
+ const localeCurrency = locale.locale.currency.split(",")[0];
+ if (typeof shop.currencies[localeCurrency] === "object") {
+ if (typeof shop.currencies[localeCurrency].rate === "number") {
+ locale.currency.rate = shop.currencies[localeCurrency].rate;
+ localeDep.changed();
}
}
- // we are looking for a shopCurrency changes here
- if (typeof locale.shopCurrency === "object") {
- locale.shopCurrency = shop.currencies[shop.currency];
- localeDep.changed();
- }
+ }
+ // we are looking for a shopCurrency changes here
+ if (typeof locale.shopCurrency === "object") {
+ locale.shopCurrency = shop.currencies[shop.currency];
+ localeDep.changed();
}
}
}
@@ -199,7 +195,10 @@ export default {
} else {
permissions = checkPermissions;
}
- // if the user has admin, owner permissions we'll always check if those roles are enough
+ // if the user has owner permissions we'll always check if those roles are enough
+ // By adding the "owner" role to the permissions list, we are making hasPermission always return
+ // true for "owners". This gives owners global access.
+ // TODO: Review this way of granting global access for owners
permissions.push("owner");
permissions = _.uniq(permissions);
@@ -736,7 +735,7 @@ export default {
// valid application
if (reactionApp) {
const settingsData = _.find(reactionApp.registry, function (item) {
- return item.provides === provides && item.template === template;
+ return item.provides && item.provides.includes(provides) && item.template === template;
});
return settingsData;
}
diff --git a/client/modules/i18n/main.js b/client/modules/i18n/main.js
index 0953519a31d..cb6720b042f 100644
--- a/client/modules/i18n/main.js
+++ b/client/modules/i18n/main.js
@@ -119,7 +119,7 @@ Meteor.startup(() => {
locale.language = getBrowserLanguage();
moment.locale(locale.language);
// flag in case the locale currency isn't enabled
- locale.currencyEnabled = locale.currencyEnabled;
+ locale.currencyEnabled = locale.currency.enabled;
const user = Meteor.user();
if (user && user.profile && user.profile.currency) {
localStorage.setItem("currency", user.profile.currency);
diff --git a/imports/plugins/core/accounts/client/helpers/accountsHelper.js b/imports/plugins/core/accounts/client/helpers/accountsHelper.js
index fa2e75ee4d1..2aa24744094 100644
--- a/imports/plugins/core/accounts/client/helpers/accountsHelper.js
+++ b/imports/plugins/core/accounts/client/helpers/accountsHelper.js
@@ -111,6 +111,7 @@ export function groupPermissions(packages) {
shopId: pkg.shopId,
permission: registryItem.name || pkg.name + "/" + registryItem.template,
icon: registryItem.icon,
+ // TODO: Rethink naming convention for permissions list
label: registryItem.label || registryItem.provides || registryItem.route
});
}
diff --git a/imports/plugins/core/accounts/register.js b/imports/plugins/core/accounts/register.js
index 34de811a562..4a0334c9d7f 100644
--- a/imports/plugins/core/accounts/register.js
+++ b/imports/plugins/core/accounts/register.js
@@ -9,7 +9,7 @@ Reaction.registerPackage({
registry: [{
route: "/dashboard/accounts",
name: "accounts",
- provides: "dashboard",
+ provides: ["dashboard"],
label: "Accounts",
description: "Manage how members sign into your shop.",
icon: "fa fa-users",
@@ -26,7 +26,7 @@ Reaction.registerPackage({
}, {
label: "Account Settings",
icon: "fa fa-sign-in",
- provides: "settings",
+ provides: ["settings"],
route: "/dashboard/account/settings",
container: "accounts",
workflow: "coreAccountsWorkflow",
@@ -35,7 +35,7 @@ Reaction.registerPackage({
route: "/dashboard/accounts",
name: "dashboard/accounts",
workflow: "coreAccountsWorkflow",
- provides: "shortcut",
+ provides: ["shortcut"],
label: "Accounts",
icon: "fa fa-users",
priority: 1,
@@ -47,7 +47,7 @@ Reaction.registerPackage({
name: "account/profile",
label: "Profile",
icon: "fa fa-user",
- provides: "userAccountDropdown"
+ provides: ["userAccountDropdown"]
}],
layout: [{
layout: "coreLayout",
diff --git a/imports/plugins/core/catalog/register.js b/imports/plugins/core/catalog/register.js
index 9bf19f8d529..a7e877bc664 100644
--- a/imports/plugins/core/catalog/register.js
+++ b/imports/plugins/core/catalog/register.js
@@ -10,7 +10,7 @@ Reaction.registerPackage({
},
registry: [
{
- provides: "dashboard",
+ provides: ["dashboard"],
label: "Catalog",
description: "Product catalog",
icon: "fa fa-book",
@@ -20,7 +20,7 @@ Reaction.registerPackage({
label: "Catalog Settings",
icon: "fa fa-book",
name: "catalog/settings",
- provides: "settings",
+ provides: ["settings"],
template: "catalogSettings"
}
]
diff --git a/imports/plugins/core/checkout/client/helpers/cart.js b/imports/plugins/core/checkout/client/helpers/cart.js
index 8b9c8556c85..bb14649bc49 100644
--- a/imports/plugins/core/checkout/client/helpers/cart.js
+++ b/imports/plugins/core/checkout/client/helpers/cart.js
@@ -13,8 +13,8 @@ import { Template } from "meteor/templating";
* cartCount, cartSubTotal, cartShipping, cartTaxes, cartTotal
* are calculated by a transformation on the collection
* and are available to use in template as cart.xxx
- * in template: {{cart.cartCount}}
- * in code: Cart.findOne().cartTotal()
+ * in template: {{cart.getCount}}
+ * in code: Cart.findOne().getTotal()
* @return {Object} returns inventory helpers
*/
Template.registerHelper("cart", function () {
diff --git a/imports/plugins/core/components/lib/hoc.js b/imports/plugins/core/components/lib/hoc.js
index b8aab6a92f3..49a48a6fa12 100644
--- a/imports/plugins/core/components/lib/hoc.js
+++ b/imports/plugins/core/components/lib/hoc.js
@@ -40,14 +40,19 @@ export function withCurrentAccount(component) {
return null;
}
- // shoppers should always be guests
- const isGuest = Roles.userIsInRole(user, "guest", shopId);
- // but if a user has never logged in then they are anonymous
- const isAnonymous = Roles.userIsInRole(user, "anonymous", shopId);
-
- const account = Accounts.findOne(user._id);
-
- onData(null, { currentAccount: isGuest && !isAnonymous && account });
+ const accSub = Meteor.subscribe("Accounts", user._id);
+ if (accSub.ready()) {
+ // shoppers should always be guests
+ const isGuest = Reaction.hasPermission("guest");
+ // but if a user has never logged in then they are anonymous
+ const isAnonymous = Roles.userIsInRole(user, "anonymous", shopId);
+ // this check for "anonymous" uses userIsInRole instead of hasPermission because hasPermission
+ // always return `true` when logged in as the owner.
+ // But in this case, the anonymous check should be false when a user is logged in
+ const account = Accounts.findOne(user._id);
+
+ onData(null, { currentAccount: isGuest && !isAnonymous && account });
+ }
})(component);
}
diff --git a/imports/plugins/core/connectors/register.js b/imports/plugins/core/connectors/register.js
index e9de82be1db..0eaddd16e5a 100644
--- a/imports/plugins/core/connectors/register.js
+++ b/imports/plugins/core/connectors/register.js
@@ -9,7 +9,7 @@ Reaction.registerPackage({
name: "Connectors"
},
registry: [{
- provides: "dashboard",
+ provides: ["dashboard"],
route: "/dashboard/connectors",
name: "connectors",
label: "Connectors",
@@ -19,7 +19,7 @@ Reaction.registerPackage({
container: "core",
workflow: "coreDashboardWorkflow"
}, {
- provides: "settings",
+ provides: ["settings"],
name: "settings/connectors",
label: "Connectors",
description: "Configure connectors",
diff --git a/imports/plugins/core/dashboard/client/components/actionView.js b/imports/plugins/core/dashboard/client/components/actionView.js
index e02ded1c093..381dcb738a8 100644
--- a/imports/plugins/core/dashboard/client/components/actionView.js
+++ b/imports/plugins/core/dashboard/client/components/actionView.js
@@ -19,7 +19,11 @@ import { getComponent } from "@reactioncommerce/reaction-components";
const getStyles = (props) => {
let viewSize = 400;
const actionView = props.actionView || {};
- const isBigView = actionView.provides === "dashboard" || (actionView.provides === "shortcut" && actionView.container === "dashboard");
+ const provides = actionView.provides || [];
+ // legacy provides could be a string, is an array since 1.5.0, check for either.
+ // prototype.includes has the fortunate side affect of checking string equality as well as array inclusion.
+ const isBigView = provides.includes("dashboard") ||
+ (provides.includes("shortcut") && actionView.container === "dashboard");
if (isBigView) {
viewSize = "90vw";
@@ -277,8 +281,9 @@ class ActionView extends Component {
get actionViewIsLargeSize() {
const { meta } = this.props.actionView;
const dashboardSize = meta && meta.actionView && meta.actionView.dashboardSize || "sm";
+ const includesDashboard = this.props.actionView.provides && this.props.actionView.provides.includes("dashboard");
- return this.props.actionView.provides === "dashboard" || dashboardSize !== "sm";
+ return includesDashboard || dashboardSize !== "sm";
}
get showOverlay() {
diff --git a/imports/plugins/core/dashboard/client/components/shortcutBar.js b/imports/plugins/core/dashboard/client/components/shortcutBar.js
index 64ca5e2e5f3..bbcc4ad85e4 100644
--- a/imports/plugins/core/dashboard/client/components/shortcutBar.js
+++ b/imports/plugins/core/dashboard/client/components/shortcutBar.js
@@ -18,9 +18,6 @@ class ShortcutBar extends Component {