Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: STRF-9873 use modified copies of get, getObject, moment, option 3p helpers #171

Merged
merged 1 commit into from
Jun 16, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
# Changelog

## Unreleased
- fix: STRF-9873 use modified implementations of `get`, `getObject`, `moment`, `option` 3p helpers ([#171](https://github.com/bigcommerce/paper-handlebars/pull/171))

## 5.0.0
- feat: STRF-9791 drop node 12 support ([#169](https://github.com/bigcommerce/paper-handlebars/pull/169))
Expand Down
4 changes: 4 additions & 0 deletions helpers.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ const helpersList = [
'dynamicComponent',
'encodeHtmlEntities',
'for',
'get',
'getContentImage',
'getContentImageSrcset',
'getFontLoaderConfig',
Expand All @@ -20,6 +21,7 @@ const helpersList = [
'getImageManagerImageSrcset',
'getImageSrcset',
'getImageSrcset1x2x',
'getObject',
'getVar',
'helperMissing',
'if',
Expand All @@ -31,9 +33,11 @@ const helpersList = [
'lang',
'langJson',
'limit',
'moment',
'money',
'nl2br',
'occurrences',
'option',
'or',
'partial',
'pluck',
Expand Down
31 changes: 31 additions & 0 deletions helpers/get.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
'use strict';

const { getValue } = require('./lib/common');

/**
* Based on https://github.com/helpers/handlebars-helpers/blob/0.9.0/lib/object.js#L128-L134
*
* Get a value from the given context object. Property paths (`a.b.c`) may be used
* to get nested properties.
*/
const factory = () => {
return function (path, context) {
let options = arguments[arguments.length - 1];

// use an empty context if none was given
if (arguments.length < 2) {
context = {};
}

let value = getValue(context, path);
if (options && options.fn) {
return value ? options.fn(value) : options.inverse(context);
}
return value;
};
};

module.exports = [{
name: 'get',
factory: factory,
}];
51 changes: 51 additions & 0 deletions helpers/getObject.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
'use strict';

const { getValue } = require('./lib/common');

/*
* Based on https://github.com/helpers/handlebars-helpers/blob/0.9.0/lib/object.js#L149-L151
* and https://github.com/jonschlinkert/get-object/blob/0.2.0/index.js#L12-L24
*
* Get an object or array containing a value from the given context object.
* Property paths (`a.b.c`) may be used to get nested properties.
*/
const factory = () => {
return function (path, context) {
// use an empty context if none was given
// (expect 3 args: `path`, `context`, and the `options` object
// Handlebars always passes as the last argument to a helper)
if (arguments.length < 3) {
context = {};
}

if (!path) {
// return entire context object if no property/path specified
return context;
}

path = String(path);

let value = getValue(context, path);

// for backwards compatibility: `get-object` returns on empty object instead of
// getting props with 'false' values (not just undefined)
if (!value) {
return {};
}

// return an array if the final path part is numeric to mimic behavior of `get-object`
const parts = path.split(/[[.\]]/).filter(Boolean);
const last = parts[parts.length - 1];
if (Number.isFinite(Number(last))) {
return [ value ];
}
let result = {};
result[last] = value
return result;
};
};

module.exports = [{
name: 'getObject',
factory: factory,
}];
36 changes: 36 additions & 0 deletions helpers/lib/common.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,41 @@ function isValidURL(val) {
}
}

/*
* Based on https://github.com/jonschlinkert/get-value/blob/3.0.1/index.js, but
* with configurability that was not used in handlebars-helpers removed.
*/
function getValue(object, path) {
let parts;
// accept array or string for backwards compatibility
if (!Array.isArray(path)) {
if (typeof path !== 'string') {
return object;
}
parts = path.split(/[[.\]]/).filter(Boolean);
} else {
parts = path.map(v => String(v));
}

let result = object;
let prefix = '';
for (let key of parts) {
// preserve handling of trailing backslashes for backwards compatibility
if (key.slice(-1) === '\\') {
prefix = prefix + key.slice(0, -1) + '.';
continue;
}
key = prefix + key;
if (Object.prototype.hasOwnProperty.call(result, key)) {
result = result[key];
prefix = '';
} else {
return;
}
}
return result;
}

function unwrapIfSafeString(handlebars, val) {
if (val instanceof handlebars.SafeString) {
val = val.toString();
Expand All @@ -20,6 +55,7 @@ const maximumPixelSize = 5120;

module.exports = {
isValidURL,
getValue,
unwrapIfSafeString,
maximumPixelSize
};
80 changes: 80 additions & 0 deletions helpers/moment.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
'use strict';

const utils = require('handlebars-utils');
const date = require('date.js');

// suppress error messages that are not actionable
const moment = require('moment');
moment.suppressDeprecationWarnings = true;

/*
* Modified from https://github.com/helpers/helper-date/blob/1.0.1/index.js
*/
const factory = () => {
return function (str, pattern) {
// always use the Handlebars-generated options object
let options = arguments[arguments.length - 1];
if (arguments.length < 3) {
pattern = null;
}
if (arguments.length < 2) {
str = null;
}

// if no args are passed, return a formatted date
if (str === null && pattern === null) {
moment.locale('en');
return moment().format('MMMM DD, YYYY');
}

var defaults = { lang: 'en', date: new Date(str) };
var opts = utils.context(this, defaults, options);

// set the language to use
moment.locale(opts.lang || opts.language);

if (opts.datejs === false) {
return moment(new Date(str)).format(pattern);
}

// if both args are strings, this could apply to either lib.
// so instead of doing magic we'll just ask the user to tell
// us if the args should be passed to date.js or moment.
if (typeof str === 'string' && typeof pattern === 'string') {
return moment(date(str)).format(pattern);
}

// If handlebars, expose moment methods as hash properties
if (options && options.hash) {
if (options.context) {
options.hash = Object.assign({}, options.hash, options.context);
}

var res = moment(str);
for (var key in options.hash) {
// prevent access to prototype methods
if (res.hasOwnProperty(key) && typeof res[key] === 'function') {
return res[key](options.hash[key]);
} else {
console.error('moment.js does not support "' + key + '"');
}
}
}

if (utils.isObject(str)) {
return moment(str).format(pattern);
}

// if only a string is passed, assume it's a date pattern ('YYYY')
if (typeof str === 'string' && !pattern) {
return moment().format(str);
}

return moment(str).format(pattern);
};
};

module.exports = [{
name: "moment",
factory: factory,
}]
33 changes: 33 additions & 0 deletions helpers/option.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
'use strict';

const util = require('handlebars-utils');
const { getValue } = require('./lib/common');

/**
* Based on https://github.com/helpers/handlebars-helpers/blob/0.9.0/lib/misc.js#L26-L28
*
* Get a value from the options object. Property paths (`a.b.c`) may be used
* to get nested properties.
*/
const factory = () => {
return function (path, locals) {
// preserve `option` behavior with missing args while ensuring the correct
// options object is used
let options = arguments[arguments.length - 1];
if (arguments.length < 3) {
locals = null;
}
if (arguments.length < 2) {
path = '';
}

let opts = util.options(this, locals, options);

return getValue(opts, path);
};
};

module.exports = [{
name: "option",
factory: factory,
}]
14 changes: 1 addition & 13 deletions helpers/thirdParty.js
Original file line number Diff line number Diff line change
Expand Up @@ -55,16 +55,6 @@ const whitelist = [
'unlessLteq',
],
},
{
name: 'date',
include: ['moment'],
init: () => {
// date-helper uses moment under the hood, so we can hook in to supress
// error messages that are not actionable
const moment = require('moment');
moment.suppressDeprecationWarnings = true;
},
},
{
name: 'html',
include: ['ellipsis', 'sanitize', 'ul', 'ol', 'thumbnailImage']
Expand All @@ -83,7 +73,7 @@ const whitelist = [
},
{
name: 'misc',
include: ['default', 'option', 'noop', 'withHash'],
include: ['default', 'noop', 'withHash'],
},
{
name: 'number',
Expand All @@ -106,8 +96,6 @@ const whitelist = [
'forIn',
'forOwn',
'toPath',
'get',
'getObject',
'hasOwn',
'isObject',
'merge',
Expand Down
8 changes: 4 additions & 4 deletions spec/helpers.js
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ describe('helper registration', () => {
'dynamicComponent',
'encodeHtmlEntities',
'for',
'get',
'getContentImage',
'getContentImageSrcset',
'getFontLoaderConfig',
Expand All @@ -39,6 +40,7 @@ describe('helper registration', () => {
'getImageManagerImageSrcset',
'getImageSrcset',
'getImageSrcset1x2x',
'getObject',
'getVar',
'helperMissing',
'if',
Expand All @@ -50,9 +52,11 @@ describe('helper registration', () => {
'lang',
'langJson',
'limit',
'moment',
'money',
'nl2br',
'occurrences',
'option',
'or',
'partial',
'pluck',
Expand Down Expand Up @@ -106,7 +110,6 @@ describe('helper registration', () => {
'unlessLt',
'unlessGteq',
'unlessLteq',
'moment',
'ellipsis',
'sanitize',
'ul',
Expand All @@ -125,7 +128,6 @@ describe('helper registration', () => {
'sum',
'avg',
'default',
'option',
'noop',
'withHash',
'addCommas',
Expand All @@ -141,8 +143,6 @@ describe('helper registration', () => {
'forIn',
'forOwn',
'toPath',
'get',
'getObject',
'hasOwn',
'isObject',
'merge',
Expand Down
Loading