Skip to content
Open
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
40 changes: 40 additions & 0 deletions lib/filters/add_class/definition.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
/**
* @file The safe_join filter
*
* Docs for TwigExtension::safeJoin (Drupal 9.3.x):
*
* ```
* new TwigFilter('safe_join',
* [$this, 'safeJoin'],
* [
* 'needs_environment' => TRUE,
* 'is_safe' => ['html']
* ]
* )
* ```
*
* ```
* Joins several strings together safely.
*
* @param \Twig\Environment $env
* A Twig Environment instance.
* @param mixed[]|\Traversable|null $value
* The pieces to join.
* @param string $glue
* The delimiter with which to join the string. Defaults to an empty string.
* This value is expected to be safe for output and user provided data
* should never be used as a glue.
*
* @return string
* The strings joined together.
* ```
*/

export const name = 'add_class';

export const options = {
needs_environment: true,
is_safe: ['html'],
};

export const acceptedArguments = [{ name: 'className', defaultValue: '' }];
21 changes: 21 additions & 0 deletions lib/filters/add_class/twing.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { newTwingFilter } from '../../helpers/twing.js';
import { name, options, acceptedArguments } from './definition.js';

function surround(value, className) {
if (Array.isArray(className)) {
className = className.join(' ');
}
return `<div class="${className}">${value}</div>`;
}
export function callable(value, clasName) {
if (typeof value === 'object') {
const output = [];
Object.values(value).forEach((value) => {
output.push(surround(value, clasName));
});
return output.join(' ');
}
return surround(value, clasName);
}

export default newTwingFilter(name, callable, options, acceptedArguments);
1 change: 0 additions & 1 deletion lib/filters/clean_class/definition.js
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,6 @@ export const acceptedArguments = [];
*/
export function cleanClass(config, string) {
const identifier = String(string);

if (
!Object.prototype.hasOwnProperty.call(config.cleanClassCache, identifier)
) {
Expand Down
2 changes: 1 addition & 1 deletion lib/filters/clean_class/twing.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { newTwingFilter } from '../../helpers/twing.js';
import config from '../../config.js';
import { name, options, acceptedArguments, cleanClass } from './definition.js';

export async function callable(string) {
export function callable(string) {
return cleanClass(config, string);
}

Expand Down
2 changes: 1 addition & 1 deletion lib/filters/clean_id/twing.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { newTwingFilter } from '../../helpers/twing.js';
import { name, options, acceptedArguments, cleanID } from './definition.js';

export async function callable(...args) {
export function callable(...args) {
return cleanID(...args);
}

Expand Down
15 changes: 12 additions & 3 deletions lib/filters/drupal_escape/twing.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,16 @@
import { escape } from 'twing/dist/cjs/lib/extension/core/filters/escape.js';
import { newTwingFilter } from '../../helpers/twing.js';
import { name, options, acceptedArguments } from './definition.js';

export const callable = escape;

// Simple escape function
export function callable(str) {
if (typeof str !== 'string') {
str = String(str);
}
return str
.replace(/&/g, '&amp;')
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;')
.replace(/"/g, '&quot;')
.replace(/'/g, '&#039;');
}
export default newTwingFilter(name, callable, options, acceptedArguments);
2 changes: 1 addition & 1 deletion lib/filters/format_date/twing.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { newTwingFilter } from '../../helpers/twing.js';
import config from '../../config.js';
import { name, options, acceptedArguments, formatDate } from './definition.js';

export async function callable(timestamp, type, format, timezone, langcode) {
export function callable(timestamp, type, format, timezone, langcode) {
return formatDate(config, timestamp, type, format, timezone, langcode);
}

Expand Down
4 changes: 2 additions & 2 deletions lib/filters/placeholder/twing.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@ import {
wrapPlaceholder,
} from './definition.js';

export async function callable(template, value) {
const escapedValue = await escape(template, value, 'html', null, true);
export function callable(template, value) {
const escapedValue = escape(template, value, 'html', null, true);
return wrapPlaceholder(escapedValue);
}

Expand Down
3 changes: 3 additions & 0 deletions lib/filters/render/definition.js
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,9 @@ export function renderVar(arg) {
// throw errors saying `"0" is an invalid render array key` and print an empty
// string for that variable. Until we implement a renderer, we should return
// an empty string.
if (typeofArg === 'array' && typeof arg[0] === 'string') {
return arg.join('');
}
if (typeofArg === 'array') {
return '';
}
Expand Down
6 changes: 1 addition & 5 deletions lib/filters/render/twing.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,4 @@
import { newTwingFilter } from '../../helpers/twing.js';
import { name, options, acceptedArguments, renderVar } from './definition.js';

export async function callable(...args) {
return renderVar(...args);
}

export default newTwingFilter(name, callable, options, acceptedArguments);
export default newTwingFilter(name, renderVar, options, acceptedArguments);
90 changes: 83 additions & 7 deletions lib/filters/safe_join/twing.js
Original file line number Diff line number Diff line change
@@ -1,17 +1,93 @@
import { join } from 'twing/dist/cjs/lib/extension/core/filters/join.js';
import { isTraversable } from 'twing/dist/cjs/lib/helpers/is-traversable.js';
import { isPlainObject } from 'is-plain-object';
import { newTwingFilter } from '../../helpers/twing.js';
import { name, options, acceptedArguments } from './definition.js';

export async function callable(value, glue) {
// Twing's join() doesn't join Object values like it should. Issue #44
// @see https://gitlab.com/nightlycommit/twing/-/issues/544
// @see https://github.com/JohnAlbin/drupal-twig-extensions/issues/45
function isTraversable(value) {
if (isPlainObject(value)) {
return true;
}
if (value !== null && value !== undefined) {
if (typeof value === 'string') {
return false;
}
if (typeof value['entries'] === 'function') {
return true;
}
if (
typeof value[Symbol.iterator] === 'function' ||
typeof value['next'] === 'function'
) {
return true;
}
}
return false;
}

function iteratorToArray(value) {
if (Array.isArray(value)) {
return value;
} else {
let result = [];
if (value.entries) {
for (let entry of value.entries()) {
result.push(entry[1]);
}
} else if (typeof value[Symbol.iterator] === 'function') {
for (let entry of value) {
result.push(entry);
}
} else if (typeof value['next'] === 'function') {
let next;
while ((next = value.next()) && !next.done) {
result.push(next.value);
}
} else {
for (let k in value) {
result.push(value[k]);
}
}
return result;
}
}

const joinSynchronously = (value, glue, and) => {
if (value == null) {
return '';
}
if (glue == null) {
glue = '';
}
if (isTraversable(value)) {
value = iteratorToArray(value);
// this is ugly, but we have to ensure that each element of the array is rendered as PHP would render it
const safeValue = value.map((item) => {
if (typeof item === 'boolean') {
return item === true ? '1' : '';
}
if (Array.isArray(item)) {
return 'Array';
}
return item;
});
if (and == null || and === glue) {
return safeValue.join(glue);
}
if (safeValue.length === 1) {
return safeValue[0];
}
return (
safeValue.slice(0, -1).join(glue) + and + safeValue[safeValue.length - 1]
);
}
return '';
};

export function callable(value, glue) {
const newValue =
typeof value === 'object' && !isTraversable(value)
? Object.values(value)
: value;
return join(newValue, glue);
return joinSynchronously(newValue, glue);
}

export default newTwingFilter(name, callable, options, acceptedArguments);
11 changes: 11 additions & 0 deletions lib/filters/set_attribute/definition.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
export const name = 'set_attribute';

export const options = {
needs_environment: true,
is_safe: ['html'],
};

export const acceptedArguments = [
{ name: 'attributeName', defaultValue: '' },
{ name: 'attributeValue', defaultValue: '' },
];
18 changes: 18 additions & 0 deletions lib/filters/set_attribute/twing.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { newTwingFilter } from '../../helpers/twing.js';
import { name, options, acceptedArguments } from './definition.js';

function surround(value, attributeName, attributeValue) {
return `<div ${attributeName}="${attributeValue}">${value}</div>`;
}
export function callable(value, attributeName, attributeValue) {
if (typeof value === 'object') {
const output = [];
Object.values(value).forEach((value) => {
output.push(surround(value, attributeName, attributeValue));
});
return output.join(' ');
}
return surround(value, attributeName, attributeValue);
}

export default newTwingFilter(name, callable, options, acceptedArguments);
4 changes: 4 additions & 0 deletions lib/filters/twing.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ import cleanIdFilter from './clean_id/twing.js';
import drupalEscapeFilter from './drupal_escape/twing.js';
import formatDateFilter from './format_date/twing.js';
import placeholderFilter from './placeholder/twing.js';
import addClassFilter from './add_class/twing.js';
import setAttributeFilter from './set_attribute/twing.js';
import renderFilter from './render/twing.js';
import safeJoinFilter from './safe_join/twing.js';
import {
Expand All @@ -18,7 +20,9 @@ import withoutFilter from './without/twing.js';

const filters = [
cleanClassFilter,
setAttributeFilter,
cleanIdFilter,
addClassFilter,
drupalEscapeFilter,
formatDateFilter,
placeholderFilter,
Expand Down
13 changes: 2 additions & 11 deletions lib/filters/without/twing.js
Original file line number Diff line number Diff line change
@@ -1,17 +1,8 @@
import { newTwingFilter } from '../../helpers/twing.js';
import { name, options, without } from './definition.js';

export async function callable(element, argsMap) {
// Twing will give an is_variadic filter its arguments as a Map.
const args = Array.from(argsMap.values()).map((value) => {
if (value instanceof Map) {
// Twing v5 converts Twig [] into JS Maps; convert to an Array.
return Array.from(value.values());
} else {
return value;
}
});
return without(element, ...args);
export function callable(element, ...argsMap) {
return without(element, ...argsMap);
}

export default newTwingFilter(name, callable, options);
2 changes: 1 addition & 1 deletion lib/functions/active_theme/definition.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ export const name = 'active_theme';

export const options = {};

export const acceptedArguments = [];
export const acceptedArguments = [{ name: 'name', defaultValue: {} }];

/**
* Gets the name of the active theme.
Expand Down
2 changes: 1 addition & 1 deletion lib/functions/active_theme/twing.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { newTwingFunction } from '../../helpers/twing.js';
import config from '../../config.js';
import { name, options, acceptedArguments, activeTheme } from './definition.js';

export async function callable() {
export function callable() {
return activeTheme(config);
}

Expand Down
2 changes: 1 addition & 1 deletion lib/functions/active_theme_path/definition.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ export const name = 'active_theme_path';

export const options = {};

export const acceptedArguments = [];
export const acceptedArguments = [{ name: 'name', defaultValue: {} }];

/**
* Gets the path of the active theme.
Expand Down
2 changes: 1 addition & 1 deletion lib/functions/active_theme_path/twing.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import {
activeThemePath,
} from './definition.js';

export async function callable() {
export function callable() {
return activeThemePath(config);
}

Expand Down
2 changes: 1 addition & 1 deletion lib/functions/create_attribute/twing.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import {
createAttribute,
} from './definition.js';

export async function callable(...args) {
export function callable(...args) {
return createAttribute(...args);
}

Expand Down
10 changes: 3 additions & 7 deletions lib/functions/file_url/definition.js
Original file line number Diff line number Diff line change
Expand Up @@ -50,20 +50,17 @@ export const acceptedArguments = [{ name: 'uri' }];
export function fileUrl(config, uri) {
// Non-strings are cast to a string with Drupal's file_url.
const path = `${uri}`;

// This regex matches against uri schemes (e.g. 'public://', 'https://'). It
// is copied from \Drupal\Core\StreamWrapper\StreamWrapperManager::getScheme()
const scheme = /^([\w-]+):\/\//;

// If the uri includes a streamWrapper scheme, replace it.
if (
scheme.test(path) &&
Object.keys(config.streamWrapper).includes(path.match(scheme)[0])
) {
return `${config.baseUrl}${path.replace(
scheme,
(substring) => config.streamWrapper[substring] + '/',
)}`;
return `${config.baseUrl}${path.replace(scheme, (substring) => {
return config.streamWrapper[substring] + '/';
})}`;
}

// Allow for:
Expand Down Expand Up @@ -92,7 +89,6 @@ export function fileUrl(config, uri) {
// if ($options['fragment']) {
// $path .= '#' . $options['fragment'];
// }

return path;
}

Expand Down
Loading