The name says it all: retrieve the first child ID of a given ID.
No preview available.
/**
* firstChildID
* Finds the first child from the given id
* Returns the first child id or the given id on failure
*
* @author Bert Oost <[email protected]> at OostDesign.nl
*
* Examples:
*
* As output filter:
* [[*id:firstChildID]]
*
* As snippet:
* [[firstChildID? &id=`[[*id]]`]]
*
* @var modX $modx
* @var array $scriptProperties
*/
$id = (isset($input) && !empty($input)) ? $input : false;
if(empty($id)) { $id = $modx->getOption('id', $scriptProperties, $modx->resource->get('id')); }
// select the first child
$c = $modx->newQuery('modResource');
$c->select(array('id'));
$c->where(array(
'parent' => $id,
'published' => true,
));
$c->sortby('menuindex', 'ASC');
$c->limit(1);
$child = $modx->getObject('modResource', $c);
if(!empty($child) && $child instanceof modResource) {
return $child->get('id');
}
return $id;
Context aware retrieval of a Configuration (ClientConfig) setting.
No preview available.
/**
* getConfigSetting
*
* Context aware retrieval of a Configuration (ClientConfig) setting.
*
* @var modX $modx
* @var array $scriptProperties
*/
$corePath = $modx->getOption('romanescobackyard.core_path', null, $modx->getOption('core_path') . 'components/romanescobackyard/');
$romanesco = $modx->getService('romanesco','Romanesco',$corePath . 'model/romanescobackyard/', array('core_path' => $corePath));
if (!($romanesco instanceof Romanesco)) return;
$context = $modx->getOption('context', $scriptProperties);
$setting = $modx->getOption('setting', $scriptProperties);
return $romanesco->getConfigSetting($setting, $context);
Retrieve a specific setting from a context of choice. Useful if you want to "borrow" a setting from another context, e.g. the correct site_url for assets only available in that context.
No preview available.
/**
* getContextSetting
*
* Useful for retrieving settings from a different context.
*
* @var modX $modx
* @var array $scriptProperties
*/
$corePath = $modx->getOption('romanescobackyard.core_path', null, $modx->getOption('core_path') . 'components/romanescobackyard/');
$romanesco = $modx->getService('romanesco','Romanesco',$corePath . 'model/romanescobackyard/', array('core_path' => $corePath));
if (!($romanesco instanceof Romanesco)) return;
$context = $modx->getOption('context', $scriptProperties);
$setting = $modx->getOption('setting', $scriptProperties);
return $romanesco->getContextSetting($setting, $context);
Retrieve the description of an element. Used in the front-end library to prevent having to enter the same information twice. This paragraph is also loaded with getElementDescription!
No preview available.
/**
* getElementDescription
*
* Retrieve the description of common database objects.
*
* You can retrieve another field or property value by specifying either the
* 'field' or 'property' parameter.
*
* @var modX $modx
* @var array $scriptProperties
*/
$elementType = $modx->getOption('type', $scriptProperties, '');
$elementName = $modx->getOption('name', $scriptProperties, '');
$fieldName = $modx->getOption('field', $scriptProperties, 'description');
$property = $modx->getOption('property', $scriptProperties, '');
// Set correct database table information based on the element type
switch($elementType) {
case stripos($elementType, 'electrontv') !== false:
$dbTable = "site_tmplvars";
$dbNameField = "name";
$modxObject = "modTemplateVar";
break;
case stripos($elementType, 'atom') !== false:
case stripos($elementType, 'molecule') !== false:
case stripos($elementType,'organism') !== false:
$dbTable = "site_htmlsnippets";
$dbNameField = "name";
$modxObject = "modChunk";
break;
case stripos($elementType, 'template') !== false:
$dbTable = "site_templates";
$dbNameField = "templatename";
$modxObject = "modTemplate";
break;
case stripos($elementType, 'page') !== false:
$dbTable = "site_content";
$dbNameField = "pagetitle";
$modxObject = "modResource";
break;
case stripos($elementType, 'formula') !== false:
$dbTable = "site_snippets";
$dbNameField = "name";
$modxObject = "modSnippet";
break;
case stripos($elementType, 'computation') !== false:
$dbTable = "site_plugins";
$dbNameField = "name";
$modxObject = "modPlugin";
break;
case stripos($elementType, 'bosonfield') !== false:
$dbTable = "contentblocks_field";
$dbNameField = "name";
$modxObject = "cbField";
break;
case stripos($elementType, 'bosonlayout') !== false:
$dbTable = "contentblocks_layout";
$dbNameField = "name";
$modxObject = "cbLayout";
break;
case stripos($elementType, 'bosontemplate') !== false:
$dbTable = "contentblocks_template";
$dbNameField = "name";
$modxObject = "cbTemplate";
break;
default:
$dbTable = "";
$dbNameField = "";
$modxObject = "";
break;
}
// In case we are dealing with a ContentBlocks element, load CB service
if (stripos($dbTable, 'contentblocks')) {
$cbCorePath = $modx->getOption('contentblocks.core_path', null, $modx->getOption('core_path').'components/contentblocks/');
$ContentBlocks = $modx->getService('contentblocks','ContentBlocks', $cbCorePath.'model/contentblocks/');
}
// User can opt to select another field or a property value instead
if ($property) {
$fieldName = 'properties';
}
// Prepare db query and retrieve value
if ($modxObject) {
$query = $modx->newQuery($modxObject, array(
$dbNameField => $elementName
));
$query->select($fieldName);
$output = $modx->getValue($query->prepare());
// Properties need to be unserialized first
if ($property) {
$properties = unserialize($output, ['allowed_classes' => false]);
$output = $properties[$property]['value'] ?? '';
}
return $output;
} else {
$modx->log(modX::LOG_LEVEL_ERROR, '[getElementDescription] ' . $elementName . ' could not be processed');
return '';
}
Get the raw value of a TV. Usually when retrieving a TV value, it gets processed first before being returned. But sometimes you need the unprocessed value instead, e.g. when using @inherit.
No preview available.
/**
* getRawTVValue
*
* Get the raw value of a TV.
*
* Usually when retrieving a TV value, it gets processed first before being returned. But sometimes you need the
* unprocessed value instead, e.g. when using @inherit.",
*
* @var modX $modx
* @var array $scriptProperties
*/
$resourceId = $modx->getOption('resource', $scriptProperties, $modx->resource->get('id'));
$tvName = $modx->getOption('tv', $scriptProperties, '');
// Get the TV
$tv = $modx->getObject('modTemplateVar', array('name'=>$tvName));
// Get the raw content of the TV
if (is_object($tv)) {
$rawValue = $tv->getValue($resourceId);
return $rawValue;
} else {
return '';
}
Get the ID of a TV, in case you only know its name. Created for the front-end library, to help with listing included TVs.
No preview available.
/**
* getTmplvarID
*
* Get the ID of a TV, in case you only know its name.
* Created for the front-end library, to help with listing included TVs.",
*
* @var modX $modx
* @var array $scriptProperties
*/
$tvName = $modx->getOption('tv', $scriptProperties, '');
// Get the TV by name
$tv = $modx->getObject('modTemplateVar',array('name'=>$tvName));
// Get the ID of the TV
if (is_object($tv)) {
$id = $tv->get('id');
return $id;
} else {
return '';
}
Feed it a bunch of properties, and it spits out the first one that's not empty. Property names are irrelevant. Sort order is all that matters.
No preview available.
/**
* returnFirstHit snippet
*
* Feed it a bunch of properties, and it spits out the first one that's not empty.
* Property names are irrelevant. Sort order is all that matters.
*
* [[!returnFirstHit?
* &1=`[[+redirect_id]]`
* &2=`[[+next_step]]`
* &3=`[[*fb_redirect_dynamic]]`
* &4=`[[*fb_redirect_id]]`
* &default=`Nothing there!`
* ]]
*
* @var array $scriptProperties
*/
// Avoid hitting snippet properties
unset($scriptProperties['elementExample']);
unset($scriptProperties['elementStatus']);
foreach ($scriptProperties as $key => $value) {
if ($value) return $value;
}
return '';
Return current timestamp.
No preview available.
/**
* tableOfContents snippet
*
* Not to be confused with the ToC plugin. The plugin takes care of generating
* the menu items, because this needs to be done during the rendering process.
*
* This snippet only sets a few placeholders for the plugin to pick up.
*
* The target attribute is required. Without it, no ToC menu is created.
*
* @var modX $modx
* @var array $scriptProperties
* @package romanesco
*/
$tpl = $modx->getOption('tpl', $scriptProperties, '');
$target = $modx->getOption('target', $scriptProperties, '');
if ($target) {
$modx->setPlaceholder('toc.target', $target);
} else {
return '';
}
if ($tpl) $modx->setPlaceholder('toc.tpl', $tpl);
This is a copy of the original cbHasField snippet that ships with ContentBlocks. The only difference is that it takes in a comma separate list of IDs, instead of just 1.
No preview available.
/**
* Use the cbHasField snippet for conditional logic depending on whether a certain field
* is in use on a resource or not.
*
* For example, this can be useful if you need to initialise a certain javascript library
* in your site's footer, but only when you have a Gallery on the page.
*
* Example usage:
*
* [[cbHasField?
* &field=`13`
* &then=`Has a Gallery!`
* &else=`Doesn't have a gallery!`
* ]]
*
* An optional &resource param allows checking for fields on other resources.
*
* Note that if the resource does not use ContentBlocks for the content, it will default to the &else value.
*
* @var modX $modx
* @var array $scriptProperties
*/
$resource = (isset($scriptProperties['resource']) && $scriptProperties['resource'] != $modx->resource->get('id')) ? $modx->getObject('modResource', $scriptProperties['resource']) : $modx->resource;
$fld = $modx->getOption('field', $scriptProperties, 0);
$then = $modx->getOption('then', $scriptProperties, '1');
$else = $modx->getOption('else', $scriptProperties, '');
// If comma-separated list in $fld, make array of IDs, else $fields = false
$fields = false;
if (strpos($fld, ',') !== false) {
$fields = array_filter(array_map('trim', explode(',', $fld)));
$fld = $fields[0]; // Let's not have $fld be a comma-separated string, in case it breaks something below
}
if(!$fld) {
$showDebug = true;
}
// Make sure this is a contentblocks-enabled resource
$enabled = $resource->getProperty('_isContentBlocks', 'contentblocks');
if ($enabled !== true) return $else;
// Get the field counts
$counts = $resource->getProperty('fieldcounts', 'contentblocks');
// Loop through $fields and replace the $fld var with the first matching element
if (is_array($counts) && is_array($fields)) {
foreach ($fields as $f) {
if (isset($counts[$f])) {
$fld = $f;
break;
}
}
}
// Otherwise, $fld is the first ID provided and the snippet continues as in previous versions. No harm no foul.
if (is_array($counts)) {
if (isset($counts[$fld])) return $then;
}
else {
$modx->log(modX::LOG_LEVEL_ERROR, '[ContentBlocks.cbHasField] Resource ' . $resource->get('id') . ' does not contain field count data. This feature was added in ContentBlocks 0.9.2. Any resources not saved since the update to 0.9.2 need to be saved in order for the field counts to be calculated and stored.');
}
return $else;
For use in a CB Code field, together with a field setting to control how the :tag modifier is rendered. Useful for dealing with MODX tags inside a Code field, i.e. when writing documentation.
No preview available.
/**
* cbRenderCodeField
*
* Useful when dealing with MODX tags inside a Code field, i.e. for documentation.
* Used in conjunction with a field setting to control how the :tag modifier is rendered.
*
* Available options:
*
* render -> Render :tag modifier(s) anyway and set code_field_raw placeholder
* respect -> Respect :tag modifier(s) and set code_field_rendered placeholder
* ignore -> Process everything as usual, without setting any placeholders
*
*/
$valueRaw = $modx->getOption('valueRaw', $scriptProperties, '');
$valueRendered = $modx->getOption('valueRendered', $scriptProperties, '');
$renderTag = $modx->getOption('renderTag', $scriptProperties, '');
$output = '';
switch($renderTag) {
case $renderTag == 'render':
$modx->toPlaceholder('code_field_raw', $valueRaw);
$output = $valueRendered;
break;
case $renderTag == 'respect':
$modx->toPlaceholder('code_field_rendered', $valueRendered);
$output = $valueRaw;
break;
case $renderTag == 'ignore':
default:
$output = $valueRaw;
break;
}
return $output;
After save hook for MIGXdb. Prevents database fields with default value of NULL from being set to 0 after a save action in MIGX.
No preview available.
/**
* migxResetNULL
*
* After save hook for MIGXdb. Prevents database fields with default value of
* NULL from being set to 0 after a save action in MIGX.
*
* @var modX $modx
* @var array $scriptProperties
*/
$object = $modx->getOption('object', $scriptProperties, null);
$properties = $modx->getOption('scriptProperties', $scriptProperties, '');
$configs = $modx->getOption('configs', $properties, '');
// Compare values in properties to newly saved object
foreach ($properties as $key => $value) {
$objectValue = $object->get($key);
// Reset to NULL if property value is empty and object value is 0
if ($objectValue === 0 && $value === '') {
//$modx->log(modX::LOG_LEVEL_ERROR, 'NULL was reset for: ' . $key);
$object->set($key, NULL);
$object->save();
}
}
return true;
Aftersave hook for MIGXdb. Increments the link number per resource, so you don't have to fiddle with that manually (as long as you enter the links in the correct order).
No preview available.
/**
* migxSaveExternalLink
*
* Aftersave hook for MIGXdb. Increments the link number per resource, so you
* don't have to fiddle with that manually (as long as you enter the links in
* the correct order).
*
* @var modX $modx
* @var array $scriptProperties
*/
$object = $modx->getOption('object', $scriptProperties);
$properties = $modx->getOption('scriptProperties', $scriptProperties, array());
$configs = $modx->getOption('configs', $properties, '');
// Set lowest new number available
if ($properties['object_id'] === 'new' && isset($properties['resource_id'])) {
// Ask for highest number so far
$q = $modx->newQuery('rmExternalLink', array('resource_id' => $properties['resource_id']));
$q->select(array(
"max(number)",
));
$lastNumber = $modx->getValue($q->prepare());
// Set and Save
$object->set('number', ++$lastNumber);
$object->save();
}
return '';
Aftersave hook for MIGXdb. Gets and sets the group (parent) ID inside a nested configuration. Also generates an alias if none is present and increments the sort order.
No preview available.
/**
* migxSaveOption
*
* Aftersave hook for MIGXdb. Gets and sets the group (parent) ID inside a
* nested configuration. Also generates an alias if none is present and
* increments the sort order.
*
* @var modX $modx
* @var array $scriptProperties
*/
$object = $modx->getOption('object', $scriptProperties);
$properties = $modx->getOption('scriptProperties', $scriptProperties, array());
$configs = $modx->getOption('configs', $properties, '');
$co_id = $modx->getOption('co_id', $properties, 0);
$parent = $modx->getObject('rmOptionGroup', array('id' => $co_id));
// Set key and ID of parent object
if (is_object($object)) {
$object->set('key', $parent->get('key'));
$object->set('group', $co_id);
$object->save();
}
// Generate alias if empty
if (!$object->get('alias')) {
$alias = $modx->runSnippet('stripAsAlias', (array('input' => $object->get('name'))));
$object->set('alias', $alias);
$object->save();
}
// Increment sort order of new items
if ($properties['object_id'] === 'new') {
// Ask for last position
$q = $modx->newQuery('rmOption');
$q->select(array(
"max(position)",
));
$lastPosition = $modx->getValue($q->prepare());
// Set and Save
$object->set('position', ++$lastPosition);
$object->save();
}
return '';
Aftersave hook for MIGXdb. Updates existing keys in child options if you change this setting in Group. Also increments the sort order.
No preview available.
/**
* migxSaveOptionGroup
*
* Aftersave hook for MIGXdb. Updates existing keys in child options if you
* change this setting in Group. Also increments the sort order.
*
* @var modX $modx
* @var array $scriptProperties
*/
$object = $modx->getOption('object', $scriptProperties, null);
$properties = $modx->getOption('scriptProperties', $scriptProperties, array());
$configs = $modx->getOption('configs', $properties, '');
// Update key in child options if you change it
if (is_object($object) && isset($properties['key'])) {
$children = $modx->getCollection('rmOption', array('group' => $object->get('id')));
foreach ($children as $child) {
$child = $modx->getObject('rmOption', array('id' => $child->get('id')));
$child->set('key', $properties['key']);
$child->save();
}
}
// Increment sort order of new items
//if ($properties['object_id'] === 'new') {
//
// // Ask for last position
// $q = $modx->newQuery('rmOptionGroup');
// $q->select(array(
// "max(position)",
// ));
// $lastPosition = $modx->getValue($q->prepare());
//
// // Set and Save
// $object->set('position', ++$lastPosition);
// $object->save();
//}
return '';
Aftersave hook for MIGXdb. Sets current resource ID as re-purpose source.
Aftersave hook for MIGXdb. Sets current resource ID as re-purpose destination.
No preview available.
/**
* fbLoadAssets snippet
*
* @var modX $modx
* @var array $scriptProperties
*
* @package romanesco
*/
$corePath = $modx->getOption('romanescobackyard.core_path', null, $modx->getOption('core_path') . 'components/romanescobackyard/');
$romanesco = $modx->getService('romanesco','Romanesco',$corePath . 'model/romanescobackyard/',array('core_path' => $corePath));
if (!($romanesco instanceof Romanesco)) {
$modx->log(modX::LOG_LEVEL_ERROR, '[Romanesco] Class not found!');
return;
}
$assetsPathCSS = $modx->getOption('romanesco.semantic_css_path', $scriptProperties, '');
$assetsPathJS = $modx->getOption('romanesco.semantic_js_path', $scriptProperties, '');
$assetsPathVendor = $modx->getOption('romanesco.semantic_vendor_path', $scriptProperties, '');
$assetsPathDist = $modx->getOption('romanesco.semantic_dist_path', $scriptProperties, '');
$uploadFile = $modx->getOption('uploadFile', $scriptProperties, 0);
$validation = $modx->getOption('frontendValidation', $scriptProperties, $modx->getOption('formblocks.frontend_validation'));
$validationTpl = $modx->getOption('validationTpl', $scriptProperties, 'fbValidation');
$ajax = $modx->getOption('ajaxMode', $scriptProperties, $modx->getOption('formblocks.ajax_mode'));
$ajaxTpl = $modx->getOption('submitAjaxTpl', $scriptProperties, 'fbSubmitAjax');
// Load strings to insert in asset paths when cache busting is enabled
$cacheBusterCSS = $romanesco->getCacheBustingString('CSS');
$cacheBusterJS = $romanesco->getCacheBustingString('JS');
// Load component asynchronously if critical CSS is enabled
$async = '';
if ($romanesco->getConfigSetting('critical_css', $modx->resource->get('context_key'))) {
$async = ' media="print" onload="this.media=\'all\'"';
}
// Load CSS
$modx->regClientStartupHTMLBlock('<link rel="stylesheet" href="' . $assetsPathDist . '/components/checkbox.min' . $cacheBusterCSS . '.css"' . $async . '>');
$modx->regClientStartupHTMLBlock('<link rel="stylesheet" href="' . $assetsPathDist . '/components/dropdown.min' . $cacheBusterCSS . '.css"' . $async . '>');
$modx->regClientStartupHTMLBlock('<link rel="stylesheet" href="' . $assetsPathDist . '/components/popup.min' . $cacheBusterCSS . '.css"' . $async . '>');
$modx->regClientStartupHTMLBlock('<link rel="stylesheet" href="' . $assetsPathDist . '/components/form.min' . $cacheBusterCSS . '.css"' . $async . '>');
$modx->regClientStartupHTMLBlock('<link rel="stylesheet" href="' . $assetsPathDist . '/components/calendar.min' . $cacheBusterCSS . '.css"' . $async . '>');
$modx->regClientStartupHTMLBlock('<link rel="stylesheet" href="' . $assetsPathDist . '/components/table.min' . $cacheBusterCSS . '.css"' . $async . '>');
// Load JS
$modx->regClientHTMLBlock('<script defer src="' . $assetsPathDist . '/components/checkbox.min' . $cacheBusterJS . '.js"></script>');
$modx->regClientHTMLBlock('<script defer src="' . $assetsPathDist . '/components/dropdown.min' . $cacheBusterJS . '.js"></script>');
$modx->regClientHTMLBlock('<script defer src="' . $assetsPathDist . '/components/popup.min' . $cacheBusterJS . '.js"></script>');
$modx->regClientHTMLBlock('<script defer src="' . $assetsPathDist . '/components/form.min' . $cacheBusterJS . '.js"></script>');
$modx->regClientHTMLBlock('<script defer src="' . $assetsPathDist . '/components/calendar.min' . $cacheBusterJS . '.js"></script>');
$modx->regClientHTMLBlock('<script defer src="' . $assetsPathJS . '/formblocks.min' . $cacheBusterJS . '.js"></script>');
// Load additional assets for file upload field, if present
if ($uploadFile) {
$modx->regClientHTMLBlock('<script defer src="' . $assetsPathVendor . '/arrive/arrive.min' . $cacheBusterJS . '.js"></script>');
$modx->regClientHTMLBlock('<script defer src="' . $assetsPathJS . '/fileupload.min' . $cacheBusterJS . '.js"></script>');
}
// Load frontend validation, if enabled
if ($validation) {
$modx->regClientHTMLBlock($modx->getChunk($validationTpl));
}
// Submit form via AJAX (only if frontend validation is disabled)
if (!$validation && $ajax) {
$modx->regClientHTMLBlock($modx->getChunk($ajaxTpl));
}
// Load custom assets, if present
// @todo: make this more dynamic
if (is_file('assets/js/formblocks.min.js')) {
$modx->regClientHTMLBlock('<script defer src="assets/js/formblocks.min' . $cacheBusterJS . '.js"></script>');
} elseif (is_file('assets/js/formblocks.js')) {
$modx->regClientHTMLBlock('<script defer src="assets/js/formblocks' . $cacheBusterJS . '.js"></script>');
}
if (is_file('assets/js/form-validation.min.js')) {
$modx->regClientHTMLBlock('<script defer src="assets/js/form-validation.min' . $cacheBusterJS . '.js"></script>');
} elseif (is_file('assets/js/form-validation.js')) {
$modx->regClientHTMLBlock('<script defer src="assets/js/form-validation' . $cacheBusterJS . '.js"></script>');
}
return '';
Generates the correct strings for the FormIt &validate property.
No preview available.
/**
* fbValidateProcessJSON
*
* A snippet for FormBlocks that generates the correct strings for the FormIt &validate property.
*
* @var modX $modx
* @var array $scriptProperties
*/
$formID = $modx->getOption('formID', $scriptProperties,'');
if ($formID) {
$resource = $modx->getObject('modResource', $formID);
} else {
$resource = $modx->resource;
$formID = $resource->get('id');
}
if (!is_object($resource) || !($resource instanceof modResource)) return '';
$prefix = $modx->getOption('prefix', $scriptProperties,'fb' . $formID . '-');
$cbData = $resource->getProperty('linear', 'contentblocks');
$output = array();
// Go through CB data and collect all required fields
foreach ($cbData as $field) {
$required = $field['settings']['field_required'] ?? 0;
if ($required != 1) {
continue;
}
// Get field name and format as alias
$fieldName = $field['settings']['field_name_html'] ?? '';
if (!$fieldName) {
$fieldName = $field['settings']['field_name'] ?? '';
}
$fieldName = $modx->runSnippet('fbStripAsAlias', array('input' => $fieldName));
// Special treatment for date fields
if ($field['field'] == $modx->getOption('formblocks.cb_input_date_range_id', $scriptProperties)) {
$output[] = $prefix . $fieldName . "-start:isDate:required,";
$output[] = $prefix . $fieldName . "-end:isDate:required,";
continue;
}
if ($field['field'] == $modx->getOption('formblocks.cb_input_date_id', $scriptProperties)) {
$output[] = $prefix . $fieldName . ":isDate:required,";
continue;
}
// All remaining fields
$output[] = $prefix . $fieldName . ":required,";
}
return implode('', $output);
Dummy hook to trick FormIt into not throwing errors saying there are no hooks, and to make the comma separation between hooks a little easier.
No preview available.
/**
*
* fbEmptyHook
*
* Dummy hook to trick FormIt into not throwing errors saying there are no hooks,
* and to make the comma separation between hooks a little easier.
*/
// Nothing to see here, move along..
return true;
Populate field values with available data stored in the user session. Enabled by default when using multi-page forms.
No preview available.
/**
* fbSetStoredValues snippet
*
* Populate field values with available data stored in the user session.
* Enabled by default when using multi-page forms.
*
* NB! This only applies to fields in the current step. Other steps still rely
* on FormItRetriever for populating the hidden fields in each form.
*
* @var modX $modx
* @var array $scriptProperties
* @var FormIt $formit
* @var fiHooks $hook
*
* @package romanesco
*/
// Erase session data if it's no longer valid
if (isset($_SESSION['formitStore']['valid']) && Time() > $_SESSION['formitStore']['valid']) {
$_SESSION['formitStore'] = '';
return true;
}
// Get and set stored values
$storedValues = $_SESSION['formitStore']['data'] ?? [];
$hook->setValues($storedValues);
return true;
No preview available.
/**
* fbPrefixOutput
*
* @var modX $modx
* @var array $scriptProperties
* @var string $input
* @var string $options
*/
$id = $modx->resource->get('id');
$options = !empty($options) ? $options: 'fb' . $id . '-';
return $options.$input;
No preview available.
/**
* fbResetNonAlpha
*
* @var modX $modx
* @var array $scriptProperties
* @var string $input
* @var string $options
*/
$input = preg_replace('/\[and]/', '&', $input);
$input = preg_replace('/\[qmark]/', '?', $input);
$input = preg_replace('/\[semicolon]/', ';', $input);
$input = preg_replace('/\[equals]/', '=', $input);
return $input;
No preview available.
/**
* fbStripAsAlias
*
* @var modX $modx
* @var array $scriptProperties
* @var string $input
* @var string $options
*/
$input = strip_tags($input); // strip HTML
$input = strtolower($input); // convert to lowercase
$input = preg_replace('/[^A-Za-z0-9 _-]/', '', $input); // strip non-alphanumeric characters
$input = preg_replace('/\s+/', '-', $input); // convert white-space to dash
$input = preg_replace('/-+/', '-', $input); // convert multiple dashes to one
$input = trim($input, '-'); // trim excess
return $input;
No preview available.
/**
* fbStripNonAlpha
*
* @var modX $modx
* @var array $scriptProperties
* @var string $input
* @var string $options
*/
$input = preg_replace('/[&]/', '[and]', $input);
$input = preg_replace('/[?]/', '[qmark]', $input);
$input = preg_replace('/[;]/', '[semicolon]', $input);
$input = preg_replace('/[=]/', '[equals]', $input);
$input = preg_replace('/[`]/', '', $input);
return $input;
Generate a sequence of Fibonacci numbers. In a Fibonacci sequence, every number after the first two is the sum of the two preceding ones.
No preview available.
/**
* FibonacciSequence
*
* Generate a sequence of Fibonacci numbers. In a Fibonacci sequence, every
* number after the first two is the sum of the two preceding ones.
*
* You can indicate where to start and how many numbers to generate:
*
* [[FibonacciSequence?
* &limit=`9`
* &start=`65`
* ]]
*
* If you want to retrieve a specific number from inside the sequence, you can
* do so using the position parameter:
*
* [[FibonacciSequence?
* &start=`40`
* &position=`5`
* ]]
*
* Without any parameters, the script will output a comma delimited sequence of
* 8 numbers. The duplicate 1 at position 2 and 3 is automatically removed.
*
* [[FibonacciSequence]]
* will output: 0,1,2,3,5,8,13,21
*
* @link http://www.hashbangcode.com/blog/get-fibonacci-numbers-using-php
*
* @var modX $modx
* @var array $scriptProperties
*/
$limit = $modx->getOption('limit', $scriptProperties, 8);
$start = $modx->getOption('start', $scriptProperties, 0);
$position = $modx->getOption('position', $scriptProperties, '');
$delimiter = $modx->getOption('delimiter', $scriptProperties, ',');
if ($start > 0) {
$second = $start * 2;
} else {
$second = 1;
$limit++; // The third 1 is removed later, so limit needs +1 to be accurate
}
$sequence = array();
if (!function_exists('fibonacciSequence')) {
function fibonacciSequence($limit, $start, $second, $position){
$sequence = array($start, $second);
if ($position > $limit) {
$limit = $position;
}
for ($i=2; $i<=$limit; ++$i) {
if ($i >= $limit) {
break;
} else {
$sequence[$i] = $sequence[$i-1] + $sequence[$i-2];
}
}
if ($position) {
return $sequence[$position - 1];
} else {
return $sequence;
}
}
}
$output = fibonacciSequence($limit, $start, $second, $position);
$output = array_unique($output); // Remove duplicate 1
if ($position) {
return $output;
} else {
return implode($delimiter, $output);
}
Overwrites the default snippet inside the Hits package. Contains a fix for preventing fatal errors in PHP 8.
No preview available.
/**
* Hits for MODX Revolution
*
* INCLUDED HERE, TO OVERWRITE DEFAULT SNIPPET AND FIX PHP8.1 COMPATIBILITY.
*
* USAGE: (assumes a chunk named hitID contains "[[+hit_key]]")
*
* Get a comma separated list of ids of the 10 most visited pages 10 levels down from the web context
* [[!Hits? &parents=`0` &depth=`10` &limit=`10` &outputSeparator=`,` &chunk=`hitID`]]
*
* Get a comma seperated list of ids of the 4 least visited pages that are children of resource 2 and set results to a placeholder
* [[!Hits? &parents=`2` limit=`4` &dir=`ASC` &outputSeparator=`,` &chunk=`hitID` &toPlaceholder=`hits`]]
*
* Record a hit for resource 3
* [[!Hits? &punch=`3`]]
*
* Record 20 hit for resource 4
* [[!Hits? &punch=`4` &amount=`20`]]
*
* Remove 4 hit from resource 5
* [[!Hits? &punch=`5` &amount=`-4`]]
*
* Get the four most hit resources, excluding the first
* [[!Hits? &parents=`0` &limit=`4` &offset=`1` &outputSeparator=`,`]]
*
* Knockout resource 3 then add 2 hits (knockout zeros value before adding punches)
* [[!Hits? &punch=`3` &amount=`2` &knockout=`1`]]
*
* @package Hits
* @var modX $modx
* @var array $scriptProperties
* @var string $input
* @var string $options
__ __
/\ \ __/\ \__ come to us for your dirty work
\ \ \___ /\_\ \ ,_\ ____ created by:
\ \ _ `\/\ \ \ \/ /',__\ JP DeVries @jpdevries
\ \ \ \ \ \ \ \ \_/\__, `\ YJ Tso @sepiariver
\ \_\ \_\ \_\ \__\/\____/ Jason Coward @drumshaman
\/_/\/_/\/_/\/__/\/__*/
// get the hit service
$defaultHitsCorePath = $modx->getOption('core_path').'components/hits/';
$hitsCorePath = $modx->getOption('hits.core_path',null,$defaultHitsCorePath);
$hitService = $modx->getService('hits','Hits',$hitsCorePath.'model/hits/',$scriptProperties);
if (!($hitService instanceof Hits)) return 'failed'; // you'll need another fool to do your dirty work
// setup default properties
$punch = $modx->getOption('punch',$scriptProperties,null);
(integer)$amount = $modx->getOption('amount',$scriptProperties,1);
$sort = $modx->getOption('sort',$scriptProperties,'hit_count');
$dir = $modx->getOption('dir',$scriptProperties,'DESC');
$parents = $modx->getOption('parents',$scriptProperties,null);
$hit_keys = explode(',',$modx->getOption('hit_keys',$scriptProperties,null));
$tpl = $modx->getOption('tpl',$scriptProperties,'hitTpl');
$limit = $modx->getOption('limit',$scriptProperties,5);
(integer)$depth = $modx->getOption('depth',$scriptProperties,10);
$outputSeparator = $modx->getOption('outputSeparator',$scriptProperties,"\n");
$toPlaceholder = $modx->getOption('toPlaceholder',$scriptProperties,"");
$offset = isset($offset) ? (integer) $offset : 0;
$knockout = (bool)$modx->getOption('knockout',$scriptProperties,false);
if (trim($parents) == '0') $parents = array(0); // i know, i know (and I hear ya)
else if ($parents) $parents = explode(',', $parents);
if ($depth < 1) $depth = 1;
// don't just go throwing punches blindly, only store a page hit if told to do so
if ($punch && $amount) {
$hit = $modx->getObject('Hit', array(
'hit_key' => $punch
));
if ($hit) {
// increment the amount
$hit->set('hit_count', ($knockout ? 0 : (integer)$hit->get('hit_count')) + $amount);
} else {
// create a new hit record
$hit = $modx->newObject('Hit');
$hit->fromArray(array(
'hit_key' => $punch,
'hit_count' => $amount
));
}
$hit->save();
}
$s = '';
// create an array of child ids to compare hits
$hits = array();
$childIds = array();
if (is_array($parents)) { // don't use count here, because it throws a mean 500 in PHPunch 8
foreach ($parents as $parent) {
$childIds = array_merge($childIds, $modx->getChildIds($parent, $depth));
}
$childIds = array_unique($childIds);
$hits = $hitService->getHits($childIds, $sort, $dir, $limit, $offset);
}
if (!is_null($hit_keys)) {
$hit_keys = array_diff($hit_keys, $childIds);
$hits = array_merge($hits, $hitService->getHits($hit_keys, $sort, $dir, $limit, $offset));
}
$hs = $hitService->processHits($hits, $tpl);
$s = implode($outputSeparator, $hs);
// would you like that for here or to go?
if ($toPlaceholder) {
$modx->setPlaceholder($toPlaceholder, $s);
return;
}
return $s;
Customized If snippet with additional 'contains', 'containsnot' and 'isnumeric' operators, output to placeholder and option to prevent chunks from parsing before If statement is evaluated.
No preview available.
/**
* If
*
* Simple if (conditional) snippet.
*
* Copyright 2009-2010 by Jason Coward <[email protected]> and Shaun McCormick
* <[email protected]>
*
* If is free software; you can redistribute it and/or modify it under the terms
* of the GNU General Public License as published by the Free Software
* Foundation; either version 2 of the License, or (at your option) any later
* version.
*
* If is distributed in the hope that it will be useful, but WITHOUT ANY
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
* A PARTICULAR PURPOSE. See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with
* If; if not, write to the Free Software Foundation, Inc., 59 Temple Place,
* Suite 330, Boston, MA 02111-1307 USA
*
* @var modX $modx
* @var array $scriptProperties
*/
if (!empty($debug)) {
print_r($scriptProperties);
if (!empty($die)) die();
}
$modx->parser->processElementTags('',$subject,true,true);
$output = '';
$operator = !empty($operator) ? $operator : '';
$operand = !isset($operand) ? '' : $operand;
if (isset($subject)) {
if (!empty($operator)) {
$operator = strtolower($operator);
switch ($operator) {
case '!=':
case 'ne':
case 'neq':
case 'not':
case 'isnot':
case 'isnt':
case 'unequal':
case 'notequal':
$output = (($subject != $operand) ? $then : (isset($else) ? $else : ''));
break;
case '<':
case 'lt':
case 'less':
case 'lessthan':
$output = (($subject < $operand) ? $then : (isset($else) ? $else : ''));
break;
case '>':
case 'gt':
case 'greater':
case 'greaterthan':
$output = (($subject > $operand) ? $then : (isset($else) ? $else : ''));
break;
case '<=':
case 'lte':
case 'lessthanequals':
case 'lessthanorequalto':
$output = (($subject <= $operand) ? $then : (isset($else) ? $else : ''));
break;
case '>=':
case 'gte':
case 'greaterthanequals':
case 'greaterthanequalto':
$output = (($subject >= $operand) ? $then : (isset($else) ? $else : ''));
break;
case 'isempty':
case 'empty':
$output = empty($subject) ? $then : (isset($else) ? $else : '');
break;
case '!empty':
case 'notempty':
case 'isnotempty':
$output = !empty($subject) && $subject != '' ? $then : (isset($else) ? $else : '');
break;
case 'isnull':
case 'null':
$output = $subject == null || strtolower($subject) == 'null' ? $then : (isset($else) ? $else : '');
break;
case 'iselement':
case 'element':
if (empty($operand) && $operand == '') break;
$operand = str_replace('mod','',$operand);
$query = $modx->newQuery('mod'.ucfirst($operand), array(
$operand == 'template' ? 'templatename' : 'name' => $subject
));
$query->select('id');
$output = $modx->getValue($query->prepare()) ? $then : (isset($else) ? $else : '');
break;
case 'inarray':
case 'in_array':
case 'ia':
$operand = explode(',',$operand);
$output = in_array($subject,$operand) ? $then : (isset($else) ? $else : '');
break;
case 'containsnot':
case 'includesnot':
$output = strpos($subject,$operand) == false ? $then : (isset($else) ? $else : '');
break;
case 'contains':
case 'includes':
$output = strpos($subject,$operand) !== false ? $then : (isset($else) ? $else : '');
break;
case 'numeric':
case 'isnumeric':
$output = is_numeric($subject) !== false ? $then : (isset($else) ? $else : '');
break;
case '==':
case '=':
case 'eq':
case 'is':
case 'equal':
case 'equals':
case 'equalto':
default:
$output = (($subject == $operand) ? $then : (isset($else) ? $else : ''));
break;
}
}
}
if (!empty($debug)) { var_dump($output); }
unset($subject);
// Prevent chunks or snippets from parsing before the If statement is evaluated.
// You can also use the mosquito technique, but that may cause issues in more complex scenarios.
$outputAsTpl = $modx->getOption('outputAsTpl', $scriptProperties, false);
if ($outputAsTpl) {
$output = $modx->getChunk($output, $scriptProperties);
}
// Output either to placeholder, or directly
$toPlaceholder = $modx->getOption('toPlaceholder', $scriptProperties, false);
if ($toPlaceholder) {
$modx->setPlaceholder($toPlaceholder, $output);
return '';
}
return $output;
Output the necessary class names for the applied Global Background.
No preview available.
/**
* setBackground
*
* Set the appropriate classes for an applied Global Background.
* For use in CB layout templates.
*
* Backwards compatible with the old, classname-based approach.
*
* @var modX $modx
* @var array $scriptProperties
* @var string $input
*/
$background = $modx->getOption('background', $scriptProperties, $input ?? null);
$cbField = $modx->getOption('romanesco.cb_field_background_id', $scriptProperties, '');
// Convert system default value
if ($background == 'default') {
$background = $modx->getObject('cgSetting', array('key' => 'layout_background_default'))->get('value');
}
// Numeric value means it's a resource-based global background
if (is_numeric($background)) {
$query = $modx->newQuery('modResource', $background);
$query->select('alias');
$alias = $modx->getValue($query->prepare());
// Get inverted value from CB settings
$inverted = $modx->runSnippet('cbGetFieldContent',array(
'resource' => $background,
'field' => $cbField,
'fieldSettingFilter' => 'inverted==1',
'returnAsJSON' => 1,
));
// Default output is []
if (strlen($inverted) > 2) {
$inverted = ' inverted';
} else {
$inverted = '';
}
$background = $alias . $inverted . ' background';
}
return $background;
Output the necessary class names for Overview patterns, based on their template. It was created because the chunks where getting a bit swamped by output modifiers trying to do the same thing.
No preview available.
/**
* setBoxType
*
* This snippet is used by Romanesco to set the appropriate classes for all Overview containers and rows.
* It was created because the chunks where getting a bit swamped by output modifiers trying to do the same thing.
*
* The snippet looks at the chunk name that is set for the Overview being used.
* If the name matches the case, the placeholders are populated with the values in that case.
*
* $box_type - The classes for the container (the overviewOuter chunks, found in Organisms)
* $row_type - The wrapper chunk for the actual template (useful for managing HTML5 elements or creating link items)
* $column_type - The class for each individual template (always closely tied to the class of the box_type)
*
* If your project needs specific overrides, create a new snippet 'setBoxTypeTheme' and add your switch cases there.
* These cases will be evaluated first. Make sure that the snippet returns an array if a match was found, or nothing.
*
* Example setBoxTypeTheme snippet:
*
* switch($input) {
* case stripos($input,'Card') !== false:
* $box_type = "cards";
* $row_type = "";
* $column_type = "[[+overview_color]] card";
* $grid_settings = "stackable doubling";
* break;
* default:
* return '';
* }
*
* $output = array(
* 'box_type' => $box_type,
* 'row_type' => $row_type,
* 'column_type' => $column_type,
* 'grid_settings' => $grid_settings,
* );
*
* return $output;
*
* ---
*
* Be advised: the cases are read from top to bottom, until it finds a match. This means that all following cases will
* not be processed, so always place input strings that contain the partial value of another string lower on the list!
*
* @author Hugo Peek
* @var modX $modx
* @var array $scriptProperties
*/
$input = $modx->getOption('input', $scriptProperties, '');
$prefix = $modx->getOption('prefix', $scriptProperties, '');
// Set prefix first to avoid duplicates
$modx->toPlaceholder('prefix', $prefix);
// Check if there's a theme override present and evaluate these cases first
$themeOverride = $modx->runSnippet('setBoxTypeTheme', (array(
'input' => $input,
'prefix' => $prefix,
)));
// themeOverride only returns an array when there is a match
if (is_array($themeOverride)) {
foreach ($themeOverride as $key => $value) {
$modx->toPlaceholder($key, $value, $prefix);
}
return;
}
switch($input) {
case stripos($input,'LinkCard') !== false:
$box_type = "link cards";
$row_type = "link";
$column_type = "card";
$grid_settings = "";
break;
case stripos($input,'Card') !== false:
$box_type = "cards";
$row_type = "";
$column_type = "card";
$grid_settings = "";
break;
case stripos($input,'Segment') !== false:
$box_type = "segments";
$row_type = "segment";
$column_type = "segment";
$grid_settings = "";
break;
case stripos($input,'ProjectTile') !== false:
$box_type = "grid";
$row_type = "";
$column_type = "ui dimmable column [[+alias]] background";
$grid_settings = "column";
break;
case stripos($input,'PersonTile') !== false:
$box_type = "grid";
$row_type = "";
$column_type = "ui column [[+alias]] background";
$grid_settings = "column";
break;
case stripos($input,'Item') !== false:
$box_type = "items";
$row_type = "";
$column_type = "item";
$grid_settings = "";
break;
case stripos($input,'Compact') !== false:
$box_type = "tiny middle aligned list";
$row_type = "";
$column_type = "item";
$grid_settings = "";
break;
case stripos($input,'IconTop') !== false:
$box_type = "centered grid";
$row_type = "";
$column_type = "center aligned column";
$grid_settings = "column";
break;
case stripos($input,'Logo') !== false:
$box_type = "centered middle aligned grid";
$row_type = "";
$column_type = "center aligned column logo";
$grid_settings = "column";
break;
default:
$box_type = "grid";
$row_type = "";
$column_type = "column";
$grid_settings = "column";
break;
}
$modx->toPlaceholder('box_type', $box_type, $prefix);
$modx->toPlaceholder('row_type', $row_type, $prefix);
$modx->toPlaceholder('column_type', $column_type, $prefix);
$modx->toPlaceholder('grid_settings', $grid_settings, $prefix);
return '';
No preview available.
$templateName = $modx->getOption('template', $scriptProperties, '');
$tpl = $modx->getOption('tpl', $scriptProperties, 'includedPatternsRow');
// Get template as object
$template = $modx->getObject('modTemplate', array('templatename'=>$templateName));
$templateID = '';
// Get the ID of the template
if ($template) {
$templateID = $template->get('id');
} else {
$modx->log(modX::LOG_LEVEL_WARN, '[assignedTVs] ' . $templateName . ' could not be processed');
}
// Look in the tmplvar_templates table to find attached TVs
$assignedTVs = $modx->getCollection('modTemplateVarTemplate', array('templateid' => $templateID));
// Create the list
$tvList = [];
foreach ($assignedTVs as $tv) {
$tvList[] = $tv->get('tmplvarid');
}
$tvList = array_filter($tvList);
// Sort list
// @todo: sort array alphabetically
sort($tvList);
// Set idx start value
$idx = 3000;
// Define output array
$output = array();
// Create a list of links to their corresponding PL locations
foreach ($tvList as $value) {
$tv = $modx->getObject('modTemplateVar', $value);
if (is_object($tv)) {
$name = $tv->get('name');
$category = $tv->get('category');
// The actual TV categories often contain spaces and hyphens and they
// don't accurately represent the file structure of the library.
// That's why we get the parent category instead.
$query = $modx->newQuery('modCategory', array(
'id' => $category
));
$query->select('parent');
$parent = $modx->getValue($query->prepare());
// Up idx value by 1, so a unique placeholder can be created
$idx++;
// Output to a chunk that contains the link generator
// Filter all TVs under the Status tab, since that's not relevant info
if (strpos($name, 'status_') === false) {
$output[] = $modx->getChunk($tpl, array(
'name' => $name,
'category' => $parent,
'idx' => $idx
));
}
}
}
return implode($output);
No preview available.
/**
* includedBosons
*
* List all ContentBlocks fields being used in a given layout, or if no layout
* is specified, on a given page.
*
* @author Hugo Peek
*/
$cbCorePath = $modx->getOption('contentblocks.core_path', null, $modx->getOption('core_path').'components/contentblocks/');
$ContentBlocks = $modx->getService('contentblocks','ContentBlocks', $cbCorePath.'model/contentblocks/');
$corePath = $modx->getOption('romanescobackyard.core_path', null, $modx->getOption('core_path') . 'components/romanescobackyard/');
$romanesco = $modx->getService('romanesco','Romanesco',$corePath . 'model/romanescobackyard/',array('core_path' => $corePath));
if (!($romanesco instanceof Romanesco)) {
$modx->log(modX::LOG_LEVEL_ERROR, '[Romanesco] Class not found!');
return;
}
$resourceID = $modx->getOption('resource', $scriptProperties, $modx->resource->get('id'));
$layoutIdx = $modx->getOption('layout', $scriptProperties, '');
$filterFields = $modx->getOption('filterFields', $scriptProperties, '');
$tpl = $modx->getOption('tpl', $scriptProperties, 'includedContentBlocksRow');
$htmlContentType = $modx->getObject('modContentType', array('name' => 'HTML'));
// Function to turn result into a link to its corresponding resource
if (!function_exists('createLink')) {
function createLink($catID, $uriExtension) {
global $modx;
// Since we have an ID, let's go hunt for the category name
$category = $modx->getObject('cbCategory', array(
'id' => $catID
));
$catName = '';
if ($category) {
$catName = strtolower($category->get('name'));
} else {
$modx->log(modX::LOG_LEVEL_WARN, '[includedBosons] Link could not be generated due to missing category ID');
}
// Use bosons as parent name, because we don't know if this is a layout or field
$parentName = 'bosons';
// Get the resource with an alias that matches both category and parent name
$query = $modx->newQuery('modResource');
$query->where(array(
'uri:LIKE' => '%' . $parentName . '%',
'AND:uri:LIKE' => '%' . $catName . $uriExtension
));
$query->select('uri');
return $modx->getValue($query->prepare());
}
}
// Get the properties of the current resource first
$query = $modx->newQuery('modResource', array(
'id' => $resourceID
));
$query->select('properties');
$properties = $modx->getValue($query->prepare());
// Prepare an array with just the content part
$propertiesArray = json_decode($properties, true);
$propertiesArray = json_decode($propertiesArray['contentblocks']['content'], true);
// If a layout idx is set, pick the corresponding layout from the array
if ($layoutIdx != '') {
$result = $propertiesArray[$layoutIdx];
} else {
$result = $propertiesArray; // And if not, just get all the fields
}
// Great! Now let's retrieve all field IDs from the array
if (is_array($result)) {
$result = $romanesco->recursiveArraySearch($result, 'field');
$result = array_unique($result);
} else {
$modx->log(modX::LOG_LEVEL_ERROR, '[includedBosons] Result is not a valid array. Is the layout idx correct?');
return '';
}
// User specified CB fields need to be excluded from result
$arrayFilter = explode(',', $filterFields);
// Turn each match into a list item with a link
$boson = '';
$output = [];
foreach ($result as $id) {
if (!in_array($id, $arrayFilter)) {
$boson = $modx->getObject('cbField', array(
'id' => $id
));
}
if ($boson) {
$name = $boson->get('name');
$link = createLink($boson->get('category'), $htmlContentType->get('file_extensions'));
$output[] = $modx->getChunk($tpl, array(
'name' => $name,
'link' => $link,
'label_classes' => ''
));
}
}
return implode(array_unique($output));
No preview available.
/**
* includedChunks
*
* This snippet is intended to list the chunks that are being used
* inside another chunk. It needs the raw content of the chunk as input.
* A regular chunk call won't work, since the referenced chunks have
* already been parsed there.
*
* You can get the raw input by looking directly in the database table
* of the chunk, using Rowboat for example:
*
* [[!Rowboat:toPlaceholder=`raw_chunk`?
* &table=`modx_site_htmlsnippets`
* &tpl=`displayRawElement`
* &where=`{"name":"overviewRowBasic"}`
* ]]
*
* Then scan the raw input for included chunks like this:
*
* [[!includedChunks? &input=`[[+raw_chunk]]`]]
*
* If you want to see which chunks have references to a specific chunk
* (the reverse thing, basically), you can use Rowboat again:
*
* [[Rowboat?
* &table=`modx_site_htmlsnippets`
* &tpl=`includedPatternsRow`
* &sortBy=`name`
* &where=`{ "snippet:LIKE":"%$buttonHrefOverview%" }`
* ]]
*
* This is not entirely accurate though, since a reference to a chunk
* called something like 'buttonHrefOverviewBasic' will also be listed
* in the results.
*
* @author Hugo Peek
*/
$string = $modx->getOption('input', $scriptProperties, '');
$patternID = $modx->getOption('id', $scriptProperties, '');
$patternName = $modx->getOption('name', $scriptProperties, '');
$patternType = $modx->getOption('type', $scriptProperties, '');
$tpl = $modx->getOption('tpl', $scriptProperties, 'includedPatternsRow');
// Finding chunks inside snippets only result in a lot of false positives, so let's disable that for now
// @todo: Create a different pattern for finding chunks inside snippets
if (stripos($patternType, 'formula')) {
return '';
}
// Find chunk names by their leading $ character or '&tpl' string
$regex = '/((?<!\w)\&tpl=`\w+|(?<!\w)\$\w+)/';
// Set idx start value
$idx = 0;
// Define output array
$output = array();
if (preg_match_all($regex, $string, $matches)) {
// Remove prefix from all matches
foreach ($matches as $match) {
$match = str_replace('$', '', $match);
$match = str_replace('&tpl=`', '', $match);
}
//print_r($match);
// Remove duplicates
$result = array_unique($match);
// Process matches individually
foreach ($result as $name) {
// Also fetch category, to help ensure the correct resource is being linked
$query = $modx->newQuery('modChunk', array(
'name' => $name
));
$query->select('category');
$category = $modx->getValue($query->prepare());
// Up idx value by 1, so a unique placeholder can be created
$idx++;
// Output to a chunk that contains the link generator
$output[] = $modx->getChunk($tpl, array(
'name' => $name,
'category' => $category,
'idx' => $idx
));
}
}
// If this pattern is a CB field with input type Chunk, then let's find that chunk
if (stripos($patternType, 'bosonfield') && $patternID) {
$cbCorePath = $modx->getOption('contentblocks.core_path', null, $modx->getOption('core_path').'components/contentblocks/');
$ContentBlocks = $modx->getService('contentblocks','ContentBlocks', $cbCorePath.'model/contentblocks/');
// First, let's check if this field contains a chunk ID
$result = $modx->getObject('cbField', array(
'id' => $patternID,
'properties:LIKE' => '%"chunk":"%'
));
// Do we have a winner?
if ($result) {
$properties = $result->get('properties');
$array = json_decode($properties, true);
$chunkID = $array['chunk'] ?? '';
$chunk = $modx->getObject('modChunk', array(
'id' => $chunkID
));
$idx++;
if ($chunk) {
$output[] = $modx->getChunk($tpl, array(
'name' => $chunk->get('name'),
'category' => $chunk->get('category'),
'label_classes' => 'blue',
'assigned' => 1,
'idx' => $idx
));
}
}
// No? Then maybe it's a chunk selector
if (!$result) {
$result = $modx->getObject('cbField', array(
'id' => $patternID,
'properties:LIKE' => '%"available_chunks":"%'
));
if (is_object($result)) {
$properties = $result->get('properties');
$array = json_decode($properties, true);
$chunks = $array['available_chunks'] ?? '';
$result = explode(',', $chunks);
foreach ($result as $name) {
// Also fetch category, to help ensure the correct resource is being linked
$query = $modx->newQuery('modChunk', array(
'name' => $name
));
$query->select('category');
$category = $modx->getValue($query->prepare());
$idx++;
$output[] = $modx->getChunk($tpl, array(
'name' => $name,
'category' => $category,
'label_classes' => 'blue',
'assigned' => 1,
'idx' => $idx
));
}
}
}
}
// No idea how it sorts the result, but seems better than the default
sort($output);
return implode($output);
No preview available.
$categoryID = $modx->getOption('input', $scriptProperties, '');
$pattern = $modx->getOption('pattern', $scriptProperties, '');
$placeholder = $modx->getOption('toPlaceholder', $scriptProperties, '');
$prefix = $modx->getOption('prefix', $scriptProperties, '');
$htmlContentType = $modx->getObject('modContentType', array('name' => 'HTML'));
// Get category object
$category = $modx->getObject('modCategory', array(
'id' => $categoryID
));
if (!is_object($category)) return;
// Grab only the last part of the category name
$categoryName = preg_match('([^_]+$)', $category->get('category'), $matchCategory);
// Get parent as well
$parent = $modx->getObject('modCategory', array(
'id' => $category->get('parent')
));
// If parent is empty, don't generate any link.
// All Romanesco elements are nested at least 1 level deep, so if a category
// has no parent, we can assume it's part of a MODX extra.
if (!is_object($parent)) {
$modx->toPlaceholder('pl', $prefix);
return;
}
// Grab last part of parent category name
$parentName = preg_match('([^_]+$)', $parent->get('category'), $matchParent);
// Grab parent categories one level deeper
$query = $modx->newQuery('modCategory', array(
'id' => $parent->get('parent')
));
$query->select('category');
$parentParent = $modx->getValue($query->prepare());
$parentParentName = preg_match('([^_]+$)', $parentParent, $matchParentParent);
// Collect matches
$matchCategory = strtolower($matchCategory[0]);
$matchParent = strtolower($matchParent[0]);
$matchParentParent = strtolower($matchParentParent[0]);
// Find resource with an alias that matches any of the collected category names
$query = $modx->newQuery('modResource');
$query->where(array(
'published' => 1,
array(
'uri:LIKE' => '%patterns%' . $matchCategory . $htmlContentType->get('file_extensions'),
),
array(
'OR:uri:LIKE' => '%patterns%' . $matchParent . $htmlContentType->get('file_extensions'),
),
array(
'OR:uri:LIKE' => '%patterns%' . $matchParentParent . $htmlContentType->get('file_extensions'),
)
));
$query->select('uri');
$link = $modx->getValue($query->prepare());
//$modx->toPlaceholder('category', $category->get('category'), $prefix);
//$modx->toPlaceholder('parent', $parent->get('category'), $prefix);
//$modx->toPlaceholder('parent2', $parentParent, $prefix);
// Add anchor already, if pattern name is defined
if ($pattern) {
$link = $link . '#' . strtolower($pattern);
}
// Output to placeholder if one is set
if ($placeholder) {
$modx->toPlaceholder('pl', $prefix);
$modx->toPlaceholder($placeholder, $link, $prefix);
return '';
} else {
return $link;
}
No preview available.
/**
* includedSnippets
*
* This snippet is intended to list the snippets that are being called
* inside a given chunk. It needs the raw content of the chunk as input.
*
* See includedChunks for more detailed instructions.
*
* @author Hugo Peek
*/
$string = $input;
$tpl = $modx->getOption('tpl', $scriptProperties, 'includedPatternsRow');
// Create a list with all available snippets
$snippetList = $modx->runSnippet('Rowboat', (array(
'table' => 'modx_site_snippets',
'tpl' => 'rawName',
'limit' => '0',
'columns' => '{ "name":"" }',
'outputSeparator' => '|'
)
));
// Find included snippets by comparing them to the list
$regex = '"(' . $snippetList . ')"';
// Set idx start value to something high, to prevent overlap
$idx = 1000;
// Define output array
$output = array();
if (preg_match_all($regex, $string, $matches)) {
foreach ($matches as $snippet) {
$match = $snippet;
}
// Remove duplicates
$result = array_unique($match);
// Process matches individually
foreach ($result as $name) {
// Also fetch category, to help ensure the correct resource is being linked
$query = $modx->newQuery('modSnippet', array(
'name' => $name
));
$query->select('category');
$category = $modx->getValue($query->prepare());
// Up idx value by 1, so a unique placeholder can be created
$idx++;
// Output to a chunk that contains the link generator
$output[] = $modx->getChunk($tpl, array(
'name' => $name,
'category' => $category,
'idx' => $idx
));
}
}
sort($output);
return implode($output);
No preview available.
/**
* includedTVs
*
* This snippet is intended to list the TVs that are being used
* inside a given chunk. It needs the raw content of the chunk as input.
*
* See includedChunks for more detailed instructions.
*
* @author Hugo Peek
*/
$string = $input;
$tpl = $modx->getOption('tpl', $scriptProperties, 'includedPatternsRow');
// Find possible TVs by looking at placeholders with a leading + character
// @todo: this should also consider other prefixes, such as &rowTpl or *.
$regex = '/(?<!\w)\+\w+/';
// Set idx start value to something high, to prevent overlap
$idx = 2000;
// Define output array
$output = array();
if (preg_match_all($regex, $string, $matches)) {
// Remove + from all matches
foreach ($matches as $match) {
$match = str_replace('+', '', $match);
}
// Remove duplicates
$result = array_unique($match);
// Create a comma separated list of possible TVs
foreach ($result as $key => $value) {
$query = $modx->newQuery('modTemplateVar', array(
'name' => $value
));
$query->select('id');
$possibleTVs[] = $modx->getValue($query->prepare());
}
// Filter results that returned false because TV name doesn't exist
$tvList = array_filter($possibleTVs, function($value) {
return ($value !== null && $value !== false && $value !== '');
});
// We have a list of positive IDs now (literally), so we can create a list
// of links to their corresponding PL locations.
foreach ($tvList as $value) {
$tv = $modx->getObject('modTemplateVar', $value);
$name = $tv->get('name');
$category = $tv->get('category');
// The actual TV categories often contain spaces and hyphens and they
// don't accurately represent the file structure of the library.
// That's why we get the parent category instead.
$query = $modx->newQuery('modCategory', array(
'id' => $category
));
$query->select('parent');
$parent = $modx->getValue($query->prepare());
// Up idx value by 1, so a unique placeholder can be created
$idx++;
// Output to a chunk that contains the link generator
$output[] = $modx->getChunk($tpl, array(
'name' => $name,
'category' => $parent,
'idx' => $idx
));
}
}
return implode($output);
No preview available.
$cbCorePath = $modx->getOption('contentblocks.core_path', null, $modx->getOption('core_path').'components/contentblocks/');
$ContentBlocks = $modx->getService('contentblocks','ContentBlocks', $cbCorePath.'model/contentblocks/');
$pattern = $modx->getOption('pattern', $scriptProperties, '');
$tpl = $modx->getOption('tpl', $scriptProperties, 'includedContentBlocksRow');
$htmlContentType = $modx->getObject('modContentType', array('name' => 'HTML'));
$output = array();
$fieldURI = 'patterns/bosons/fields';
$layoutURI = 'patterns/bosons/layouts';
// First, we need to know which CB elements contain the pattern name
// Let's start searching inside fields first, since they're the most common
$result = $modx->getCollection('cbField', array(
'template:LIKE' => '%' . $pattern . '%',
'OR:properties:LIKE' => '%' . $pattern . '%',
'OR:settings:LIKE' => '%' . $pattern . '%'
));
// Proceed if any matches are present
if ($result) {
// Turn each match into a list item with a link
foreach ($result as $field) {
$output[] = $modx->getChunk($tpl, array(
'name' => $field->get('name'),
'link' => $fieldURI,
'label_classes' => 'blue'
));
}
return implode($output);
}
// Maybe the field type is Chunk, meaning it is referenced by ID instead of name
$query = $modx->newQuery('modChunk');
$query->where(array(
'name' => $pattern
));
$query->select('id');
$patternID = $modx->getValue($query->prepare());
$result = $modx->getObject('cbField', array(
'properties:LIKE' => '%"chunk":"' . $patternID . '"%'
));
if ($result) {
return $modx->getChunk($tpl, array(
'name' => $result->get('name'),
'link' => $fieldURI,
'assigned' => 1
));
}
// If no fields where found, try the layouts table instead
$result = $modx->getCollection('cbLayout', array(
'template:LIKE' => '%' . $pattern . '%',
'OR:settings:LIKE' => '%' . $pattern . '%'
));
// Proceed if any matches are present
if ($result) {
// Turn each match into a list item with a link
foreach ($result as $layout) {
$output[] = $modx->getChunk($tpl, array(
'name' => $layout->get('name'),
'link' => $layoutURI,
'label_classes' => 'purple'
));
}
return implode($output);
}
return '';
No preview available.
$cbCorePath = $modx->getOption('contentblocks.core_path', null, $modx->getOption('core_path').'components/contentblocks/');
$ContentBlocks = $modx->getService('contentblocks','ContentBlocks', $cbCorePath.'model/contentblocks/');
//$ContentBlocks->loadInputs();
$cbField = $modx->getOption('cbField', $scriptProperties, '');
$cbLayout = $modx->getOption('cbLayout', $scriptProperties, '');
$prefix = $modx->getOption('prefix', $scriptProperties, '');
if ($cbField) {
$field = $modx->getObject('cbField', array(
'name' => $cbField
));
if ($field) {
// Create an array with all internal fields
$array = $field->toArray();
// Set all fields as placeholders
// Use a prefix to prevent collisions
$modx->toPlaceholders($array, $prefix);
// Set placeholder with all field settings parsed in an HTML table
//$settingsTable = $modx->runSnippet('jsonToHTML', array(
// 'json' => $field->get('settings')
//));
//$modx->toPlaceholder('settings_table', $settingsTable, $prefix);
// Above option doesn't work somehow, so just output raw json to placeholder
$modx->toPlaceholder('settings_json', $field->get('settings'), $prefix);
$modx->toPlaceholder('properties_json', $field->get('properties'), $prefix);
$modx->toPlaceholder('availability_json', $field->get('availability'), $prefix);
// Set placeholder with wrapper template, if present inside properties field
$properties = json_decode($field->get('properties'), true);
$wrapperTemplate = $properties['wrapper_template'] ?? '';
if ($wrapperTemplate) {
$output = $modx->getChunk('displayRawTemplate', array(
'template' => $wrapperTemplate,
));
$modx->toPlaceholder('wrapper_template', $output, $prefix);
}
// Set separate placeholder with prefix, for easier retrieval of the other placeholders
// Usage example: [[+[[+cb]].placeholder]]
$modx->toPlaceholder('cf', $prefix);
}
else {
$modx->log(modX::LOG_LEVEL_WARN, '[setPatternPlaceholders] ' . $cbField . ' could not be processed');
}
}
if ($cbLayout) {
$layout = $modx->getObject('cbLayout', array(
'name' => $cbLayout
));
if ($layout) {
// Create an array with all internal fields
$array = $layout->toArray();
// Set all fields as placeholders
// Use a prefix to prevent collisions
$modx->toPlaceholders($array, $prefix);
// Set placeholder with raw json output from the settings column
$modx->toPlaceholder('settings_json', $layout->get('settings'), $prefix);
// Set separate placeholder with prefix, for easier retrieval of the other placeholders
// Usage example: [[+[[+cl]].placeholder]]
$modx->toPlaceholder('cl', $prefix);
}
else {
$modx->log(modX::LOG_LEVEL_WARN, '[setPatternPlaceholders] ' . $cbLayout . ' could not be processed');
}
}
return '';
No preview available.
/**
* @var modX $modx
* @var array $scriptProperties
* @var string $input
* @var string $options
*/
$input = $modx->getOption('input', $scriptProperties, $input);
$length = $modx->getOption('length', $scriptProperties, $options);
switch($input) {
case stripos($input,'electrons') !== false:
$type = "Electron";
$type_s = "E";
break;
case stripos($input,'atoms') !== false:
$type = "Atom";
$type_s = "A";
break;
case stripos($input,'molecules') !== false:
$type = "Molecule";
$type_s = "M";
break;
case stripos($input,'organisms') !== false:
$type = "Organism";
$type_s = "O";
break;
case stripos($input,'templates') !== false:
$type = "Template";
$type_s = "T";
break;
case stripos($input,'pages') !== false:
$type = "Page";
$type_s = "P";
break;
case stripos($input,'formulas') !== false:
$type = "Formula";
$type_s = "F";
break;
case stripos($input,'computation') !== false:
$type = "Computation";
$type_s = "C";
break;
case stripos($input,'boson') !== false:
$type = "Boson";
$type_s = "B";
break;
default:
$type = "undefined";
$type_s = "U";
break;
}
if ($length == 'word') {
return $type;
}
return $type_s;
Search a JSON object for specific item and return the entire array. This is initially intended to turn CB repeater elements into CSS, without having to change the internal templating in CB.
No preview available.
/**
* jsonGetObject
*
* Search a JSON object for specific item and return the entire array.
*
* This is initially intended to turn CB repeater elements into CSS, without
* having to change the internal templating in ContentBlocks.
*
* @var modX $modx
* @var array $scriptProperties
*/
$corePath = $modx->getOption('romanescobackyard.core_path', null, $modx->getOption('core_path') . 'components/romanescobackyard/');
$romanesco = $modx->getService('romanesco','Romanesco',$corePath . 'model/romanescobackyard/',array('core_path' => $corePath));
if (!($romanesco instanceof Romanesco)) return;
$json = $modx->getOption('json', $scriptProperties, '');
$object = $modx->getOption('object', $scriptProperties, '');
$tpl = $modx->getOption('tpl', $scriptProperties, '');
$outputSeparator = $modx->getOption('outputSeparator', $scriptProperties, '');
$jsonArray = json_decode($json, true);
$output = array();
//$modx->log(modX::LOG_LEVEL_ERROR, print_r($jsonArray,1));
// Return directly if JSON input is not present or valid
if (!$jsonArray) {
$modx->log(modX::LOG_LEVEL_INFO, '[jsonGetObject] No valid JSON input provided.');
switch (json_last_error()) {
case JSON_ERROR_NONE:
$modx->log(modX::LOG_LEVEL_INFO, '[jsonGetObject] No errors');
break;
case JSON_ERROR_DEPTH:
$modx->log(modX::LOG_LEVEL_INFO, '[jsonGetObject] Maximum stack depth exceeded');
break;
case JSON_ERROR_STATE_MISMATCH:
$modx->log(modX::LOG_LEVEL_INFO, '[jsonGetObject] Underflow or the modes mismatch');
break;
case JSON_ERROR_CTRL_CHAR:
$modx->log(modX::LOG_LEVEL_INFO, '[jsonGetObject] Unexpected control character found');
break;
case JSON_ERROR_SYNTAX:
$modx->log(modX::LOG_LEVEL_INFO, '[jsonGetObject] Syntax error, malformed JSON');
break;
case JSON_ERROR_UTF8:
$modx->log(modX::LOG_LEVEL_INFO, '[jsonGetObject] Malformed UTF-8 characters, possibly incorrectly encoded');
break;
default:
$modx->log(modX::LOG_LEVEL_INFO, '[jsonGetObject] Unknown error');
break;
}
return '';
}
// Search array for given object
$result = $romanesco->recursiveArraySearch($jsonArray,$object);
// Flatten first level, since that's always the full JSON object itself
$result = $result[0];
// Return result if it's no longer an array
if (!is_array($result)) {
return $result;
}
// Flat arrays can be forwarded directly to the tpl chunk
if (!$result[0]) {
return $modx->getChunk($tpl, $result);
}
// Loop over multidimensional arrays
if ($result[0]) {
$idx = 1;
foreach ($result as $row) {
$row['idx'] = $idx++;
$output[] = $modx->getChunk($tpl, $row);
}
return implode($outputSeparator,$output);
}
return '';
// @todo: Investigate approach below, where recursiveArraySearch can find multiple instances using 'yield' instead of 'return'.
//foreach ($romanesco->recursiveArraySearch($jsonArray,$object) as $result) {
// // Flatten first level, since that's always the full JSON object itself
// $result = $result[0];
//
// // Return result directly if it's no longer an array
// if (!is_array($result)) {
// $output[] = $result;
// }
//
// // Flat arrays can be forwarded directly to the tpl chunk
// if (!$result[0]) {
// $output[] = $modx->getChunk($tpl, $result);
// }
//
// // Loop over multidimensional arrays
// if ($result[0]) {
// $rows = array();
// foreach ($result as $row) {
// $rows[] = $modx->getChunk($tpl, $row);
// }
// $output[] = implode($outputSeparator,$rows);
// }
//}
//
//return implode(',',$output);
Get the value of a specific key from a JSON string.
No preview available.
/**
* jsonGetValue
*
* Get the value of a specific key from a JSON string.
*
* @var modX $modx
* @var array $scriptProperties
* @var string $input
* @var string $options
*/
$corePath = $modx->getOption('romanescobackyard.core_path', null, $modx->getOption('core_path') . 'components/romanescobackyard/');
$romanesco = $modx->getService('romanesco','Romanesco',$corePath . 'model/romanescobackyard/',array('core_path' => $corePath));
if (!($romanesco instanceof Romanesco)) return;
$input = $modx->getOption('json', $scriptProperties, $input);
$key = $modx->getOption('key', $scriptProperties, $options);
$tpl = $modx->getOption('tpl', $scriptProperties, '');
$outputSeparator = $modx->getOption('outputSeparator', $scriptProperties, ',');
$toPlaceholder = $modx->getOption('toPlaceholder', $scriptProperties, false);
// @todo: test if input is valid JSON, otherwise NULL is returned
$input = utf8_encode($input);
$array = json_decode($input, true);
$output = '';
// Flatten first level, since that's always the full JSON object itself
$array = $array[0];
// Single result from flat array
if ($array[$key]) {
$output = $array[$key];
if ($tpl) {
$output = $modx->getChunk($tpl, array(
'content' => $output
));
}
};
// Single key from multidimensional array
if (is_array($array)) {
$output = $romanesco->recursiveArraySearch($array, $key);
if ($tpl) {
$output = $modx->getChunk($tpl, array(
'content' => $output
));
}
$output = implode($output);
}
// Output either to placeholder, or directly
if ($toPlaceholder) {
$modx->setPlaceholder($toPlaceholder, $output);
return '';
}
return $output;
Turn a JSON object into an HTML table. For documentation purposes.
No preview available.
/**
* jsonToHTML
*
* @var modX $modx
* @var array $scriptProperties
* @var string $input
* @var string $options
*/
// @todo: write documentation and use chunks for the HTML templating
$json = $modx->getOption('json', $scriptProperties, '');
//$filterKeys = $modx->getOption('filterKeys', $scriptProperties, 'template,process_tags,field_is_exposed');
//$filterKeys = $modx->getOption('filterKeys', $scriptProperties, '"template","process_tags","field_is_exposed"');
if (!$json) return '';
$jsonArray = json_decode($json, true);
//$filterArray = explode(',', $filterKeys);
if (!function_exists('jsonToHTML')) {
function jsonToHTML($array = array(), $tdClass = 'top level') {
$output = '<table class="ui compact very basic table"><tbody>';
$filterKeys = array("templates","process_tags","field_is_exposed");
foreach ($array as $key => $value) {
if ($value == 'icon_class') break;
if ($value == 'divider_icon_class') break;
// Exclude unwanted keys and keys with an empty value from result
// When not set to 'true', the first item in the array will always be excluded
if (in_array($key, $filterKeys, true) == false && $value != false) {
$output .= "<tr class='top aligned'>";
$output .= "<td class='$tdClass'><strong>$key</strong></td>";
$output .= "<td>";
if (is_array($value)) {
$output .= jsonToHTML($value, 'five wide');
} else {
$output .= $value;
}
$output .= "</td></tr>";
}
}
$output .= "</tbody></table>";
return $output;
}
}
return (jsonToHTML($jsonArray));
Prepare the input for being used in JSON. This means escaping backslashes and double quotes.
No preview available.
/**
* stripForJSON
*
* Escape backslashes and double quotes.
*
* @var modX $modx
* @var array $scriptProperties
* @var string $input
*/
$input = $modx->getOption('input', $scriptProperties, $input);
if ($input == '') { return ''; }
return str_replace('\n','',json_encode($input));
Output the properties of given TV to a JSON object. The output could be used by jsonToHTML to generate an HTML table.
No preview available.
/**
* tvToJSON
*
* Output the properties of given TV to a JSON object.
* The output could be used by jsonToHTML.
*
* Initially intended for use in the front-end library. TV settings can now be
* loaded automatically, instead of copy/pasting the JSON from the GPM config by hand.
*
* Usage example:
* [[tvToJSON? &tv=`[[+pattern_name]]`]]
*
* For GPM compatible output:
* [[!tvToJSON?
* &tv=`overview_img_landscape`
* &showName=`1`
* &showSource=`0`
* &optionsDelimiter=`0`
* ]]
*
* @var modX $modx
* @var array $scriptProperties
*/
$tvName = $modx->getOption('tv', $scriptProperties, '');
$showName = $modx->getOption('showName', $scriptProperties, 0);
$showSource = $modx->getOption('showSource', $scriptProperties, 1);
$optionsDelimiter = $modx->getOption('optionsDelimiter', $scriptProperties, '<br>');
// Get the TV by name
$tv = $modx->getObject('modTemplateVar', array('name'=>$tvName));
if (!is_object($tv)) {
return '';
}
// Render category name for clarity
$query = $modx->newQuery('modCategory', array(
'id' => $tv->get('category')
));
$query->select('category');
$catName = $modx->getValue($query->prepare());
// Render media source name for clarity
$sourceID = $tv->get('source');
if ($sourceID != false) {
$query = $modx->newQuery('modMediaSource', array(
'id' => $sourceID
));
$query->select('name');
$sourceName = $modx->getValue($query->prepare());
}
// Check if TV has input / output properties
if ($tv->get('input_properties')) {
$inputProperties = array_diff($tv->get('input_properties'),array(null));
}
if ($tv->get('output_properties')) {
$outputProperties = array_diff($tv->get('output_properties'),array(null));
}
// Control output delimiter of input options
$inputOptions = $tv->get('elements');
if ($optionsDelimiter) {
$inputOptions = str_replace('||', $optionsDelimiter, $inputOptions);
}
// Create a new object with altered elements
// The new key names mimic the properties used by GPM
$tvAltered = array(
'caption' => $tv->get('caption'),
'name' => $tv->get('name'),
'description' => $tv->get('description'),
'type' => $tv->get('type'),
'category' => $catName,
'sortOrder' => $tv->get('rank'),
'inputOptionValues' => $inputOptions,
'defaultValue' => $tv->get('default_text'),
'inputProperties' => $inputProperties ?? '',
'outputProperties' => $outputProperties ?? '',
'display' => $tv->get('display'),
'mediaSource' => $sourceName ?? '', // Not a GPM property, but good to know anyway
);
// Remove undesired keys
$tvAltered = array_diff($tvAltered,array(null,'description'));
if ($tvAltered['display'] == 'default') {
unset($tvAltered['display']);
}
if ($tvAltered['inputProperties']['allowBlank'] == 'true') {
unset($tvAltered['inputProperties']['allowBlank']);
}
if (!$showName) {
unset($tvAltered['name']);
}
if (!$showSource) {
unset($tvAltered['mediaSource']);
}
if (empty($tvAltered['inputProperties'])) {
unset($tvAltered['inputProperties']);
}
if (empty($tvAltered['outputProperties'])) {
unset($tvAltered['outputProperties']);
}
// Output as JSON object
return json_encode($tvAltered);
Grab a comma-separated list and prefix all items with given value. Optionally, the separator can be changed and output can be forwarded to a placeholder.
No preview available.
/**
* beforeEach snippet
*
* Grab a comma-separated list and prefix all items with given value.
* Optionally, the separator can be changed and output can be forwarded to a
* placeholder.
*
* Usage examples:
*
* [[++navbar_exclude_resources:beforeEach=`-`]]
*
* [[beforeEach?
* &input=`[[++navbar_exclude_resources]]`
* &before=`-`
* &toPlaceholder=`excluded_resources`
* ]]
*
* @var modX $modx
* @var array $scriptProperties
* @var string $input
* @var string $options
*/
$input = $modx->getOption('input', $scriptProperties, $input);
$before = $modx->getOption('before', $scriptProperties, $options ?? '-');
$separator = $modx->getOption('separator', $scriptProperties, ',');
$placeholder = $modx->getOption('toPlaceholder', $scriptProperties, false);
if (!$input) return '';
$resourceArray = explode($separator,$input);
$output = array();
foreach ($resourceArray as $input) {
$input = trim($input);
// Maybe value is already prefixed
if ($input[0] == $before) {
$output[] = $input;
} else {
$output[] = $before . $input;
}
}
$output = implode($separator, $output);
if ($placeholder) {
$modx->toPlaceholder($placeholder, $output);
return;
} else {
return $output;
}
Trim the edges of a string. The given value represents the number of characters that will be clipped. If the value is negative, they will be clipped from the end of the string.
No preview available.
/**
* clipString
*
* Trim a certain amount of characters from the edges of a string.
*
* If a negative value is used, this number of characters will be clipped from
* the end. Otherwise, they are clipped from the start of the string.
*
* If no value is given, whitespace is trimmed from the edges.
*
* Usage examples:
*
* [[*your_tv:clipString=`-1`]]
* (if the value of your_tv is 'https', this will return 'http')
*
* [[clipString?
* &input=`[[+some_string]]`
* &clip=`1`
* ]]
* (if your string is 'your website', this will return 'our website')
*
* You can also clip both edges:
*
* [[*your_tv:clipString=`8`:clipString=`-1`]]
* (if your_tv is 'https://your_website/', this will return 'your_website')
*
* @var modX $modx
* @var array $scriptProperties
* @var string $input
* @var string $options
*/
$input = $modx->getOption('input', $scriptProperties, $input);
$clip = (int) $modx->getOption('clip', $scriptProperties, $options);
// Output filters are also processed when the input is empty, so check for that
if ($input == '') { return ''; }
// Only trim whitespace if clip is not defined
if (!$clip) {
return trim($input);
}
// Decide whether to clip the start or end of the string
if ($clip < 0) {
return mb_substr($input, 0, $clip);
} else {
return mb_substr($input, $clip);
}
Search the input for lines containing a specific string. And then return those lines.
No preview available.
/**
* filterLine
*
* Search input for lines containing a specific string. And then return those
* lines.
*
* @var modX $modx
* @var array $scriptProperties
* @var string $input
* @var string $options
*/
$lines = $modx->getOption('input', $scriptProperties, $input);
$file = $modx->getOption('file', $scriptProperties, '');
$search = $modx->getOption('searchString', $scriptProperties, $options);
$limit = $modx->getOption('limit', $scriptProperties, 10);
$tpl = $modx->getOption('tpl', $scriptProperties, '');
// Check first if we're dealing with an external file
if ($file) {
$lines = file_get_contents($file);
}
// Create an array of all lines inside the input
$lines = explode("\n", $lines);
$i = 0;
$output = [];
// Check if the line contains the string we're looking for, and print if it does
foreach ($lines as $line) {
if(strpos($line, $search) !== false) {
$output[] = $line;
$i++;
if($i >= $limit) {
break;
}
if ($tpl) {
$output[] = $modx->getChunk($tpl, array(
'content' => $line,
));
}
}
}
if ($output) {
return implode('<br>', $output);
}
return '';
Scan input for duplicate lines and remove them from the output.
No preview available.
/**
* removeDuplicateLines
*
* Scan input for duplicate lines and remove them from the output.
*
* @var modX $modx
* @var array $scriptProperties
* @var string $input
* @var string $options
*/
$lines = $modx->getOption('input', $scriptProperties, $input);
$file = $modx->getOption('file', $scriptProperties, '');
// Check first if we're dealing with an external file
if ($file) {
$lines = file_get_contents($file);
}
// Create an array of all lines inside the input
$lines = explode("\n", $lines);
$i = 0;
// Check if the lines array contains duplicates
$output = array_unique($lines);
$output = array_filter($output);
if (is_array($output)) {
return implode("\n", $output);
} else {
return $output;
}
Find patterns with regex and replace them. By default, it removes all matches. If you want to replace each match with something else, you have to use a regular snippet call.
No preview available.
/**
* replaceRegex
*
* Find patterns with regex and replace them.
*
* By default, it removes all matches. If you want to replace each match with
* something else, you have to use a regular snippet call.
*
* @example [[*content:replaceRegex=`^---[\s\S]+?---[\s]+`]]
* (removes YAML front matter)
*
* @var modX $modx
* @var array $scriptProperties
* @var string $input
* @var string $options
*/
$input = $modx->getOption('input', $scriptProperties, $input);
$regex = $modx->getOption('pattern', $scriptProperties, $options);
$replace = $modx->getOption('replacement', $scriptProperties, '');
if ($input) {
return preg_replace('/' . $regex . '/', $replace, $input);
}
return '';
Round a decimal value to a whole number or with a specified amount of decimals. Usage: returns the value with 2 decimals, returns a whole number.
No preview available.
/**
* round
*
* The wheels on the bus... Round a decimal value to a whole number or with a
* specified amount of decimals.
*
* @example [[+value:round=`2`]] returns the value with 2 decimals
* @example [[+value:round]] returns a whole number
*
* You can also round to the next higher or lower whole number:
*
* @example [[+value:round=`up`]]
* @example [[+value:round=`down`]]
*
* Comma separator for decimals will be converted to a dot, so don't use ',' as
* thousands separator!
*
* @var modX $modx
* @var array $scriptProperties
* @var string $input
* @var string $options
*/
if ($input == '') return '';
if (!$options) $options = 0;
$input = str_replace(',', '.', $input); // Darn you Europeans
if ($options == 'up') return ceil($input);
if ($options == 'down') return floor($input);
return round($input,$options);
Divide string into multiple sections, based on a delimiter. Regular snippet call outputs sections to placeholders. If used as output modifier, specify the number of the part you want to get.
No preview available.
/**
* splitString
*
* Divide string into multiple sections, based on a delimiter.
*
* If used as a regular snippet, each part is output to a separate placeholder.
*
* If used as output modifier, you need to specify the number of the part you
* want to get. For example, if your string is:
*
* 'Ubuntu|300,700,300italic,700italic|latin'
*
* Then [[+placeholder:splitString=`1`]] will return 'Ubuntu'.
*
* @var modX $modx
* @var array $scriptProperties
* @var string $input
* @var string $options
*/
$input = $modx->getOption('input', $scriptProperties, $input);
$options = $modx->getOption('options', $scriptProperties, $options);
$delimiter = $modx->getOption('delimiter', $scriptProperties, '|');
$prefix = $modx->getOption('prefix', $scriptProperties, 'snippet');
// Output filters are also processed when the input is empty, so check for that
if ($input == '') { return ''; }
// Break up the string
$output = explode($delimiter,$input);
$idx = 0;
// If snippet is used as output modifier, return matching section
if ($options) {
return $output[$options - 1];
}
// Process each section individually
foreach ($output as $value) {
$idx++;
$modx->toPlaceholder($idx, trim($value), $prefix);
// Additional first and last placeholders
if ($idx == 1) {
$modx->toPlaceholder('first', trim($value), $prefix);
}
$modx->toPlaceholder('last', trim($value), $prefix);
}
// Return placeholder with total idx
$modx->toPlaceholder('total', $idx, $prefix);
return '';
Turn input into lowercase-hyphen-separated-alias-format and strip unwanted special characters. Useful for creating anchor links based on headings, for example.
No preview available.
/**
* stripAsAlias
*
* Turn input into lowercase-hyphen-separated-alias-format and strip unwanted
* special characters. Useful for creating anchor links based on headings, for
* example.
*
* @var modX $modx
* @var array $scriptProperties
* @var string $input
* @var string $options
*/
$input = strip_tags($input); // strip HTML
$input = strtolower($input); // convert to lowercase
$input = preg_replace('/[^.A-Za-z0-9 _-]/', '', $input); // strip non-alphanumeric characters
$input = preg_replace('/\s+/', '-', $input); // convert white-space to dash
$input = preg_replace('/-+/', '-', $input); // convert multiple dashes to one
$input = trim($input, '-'); // trim excess
return $input;
Prepare the input for use in Javascript. This means escaping certain characters to make sure the surrounding HTML doesn't break.
No preview available.
/**
* stripForJS
*
* Prepare the input for use in Javascript. This means escaping certain
* characters to make sure the surrounding HTML doesn't break.
*
* @var modX $modx
* @var array $scriptProperties
* @var string $input
* @var string $options
*/
$output = $input;
$output = str_replace('/', '\/', $output);
$output = str_replace("'", "\'", $output);
$output = str_replace("\n", '', $output);
$output = preg_replace("/(>+(\s)*<+)/", '><', $output);
$output = preg_replace("/\s+/", ' ', $output);
return $output;
As opposed to the native MODX stripString modifier (which only allows you to strip a single value), stripWords lets you enter multiple (comma separated) values.
No preview available.
/**
* stripWords
*
* As opposed to the native MODX stripString modifier (which only allows you to
* strip a single value), stripWords lets you enter multiple (comma separated)
* values.
*
* @var modX $modx
* @var array $scriptProperties
* @var string $input
* @var string $options
*/
$words = array_map('trim', explode(',', $options));
return str_replace($words, '', $input);
Turn a written number into an actual numeric value. In other words: turn "three" into "3". Can come in handy if you want to use the Semantic UI column width classes for other purposes.
No preview available.
/**
* textToNumber
*
* Turn a written number into an actual one.
*
* Written numbers are used for setting column counts in SemanticUI, but as
* numeric values they can be reused in other places too.
* For example: to set the number of visible slides in the JS configuration.
*
* Easter egg: reverses to numberToText functionality when input is numeric
*
* NB: there seems to be an issue when using this snippet inside a CB template
* as output modifier. It messes up the cache and prevents the alternative from
* working properly. To fix: remove all modifiers, replace with full snippet
* call and clear cache (from top menu, NOT by saving resource).
*
* @var modX $modx
* @var array $scriptProperties
* @var string $input
* @var string $options
*/
$input = $modx->getOption('input', $scriptProperties, $input);
$output = '';
$numbers = array(
'1' => 'one',
'2' => 'two',
'3' => 'three',
'4' => 'four',
'5' => 'five',
'6' => 'six',
'7' => 'seven',
'8' => 'eight',
'9' => 'nine',
'10' => 'ten',
'11' => 'eleven',
'12' => 'twelve',
'13' => 'thirteen',
'14' => 'fourteen',
'15' => 'fifteen',
'16' => 'sixteen'
);
if (is_numeric($input)) {
$output = $numbers[$input];
} else {
$output = array_search($input, $numbers);
}
return $output;
Utility snippet to determine which CSS styles are used above the fold and write them to a custom CSS file. This needs NPM and the critical package to be installed.
No preview available.
/**
* generateCriticalCSS
*
* Determine which CSS styles are used above the fold and write them to a custom
* CSS file. This needs NPM and the critical package to be installed.
*
* https://github.com/addyosmani/critical
*
* It works by using runProcessor to save the given resource, which triggers
* the GenerateCriticalCSS plugin, which in turn triggers the critical gulp task.
* This detour is required, because the gulp task needs to know the exact path
* of the critical CSS file, which is stored in a TV. Without the save action,
* that TV might still be empty.
*
* Usage:
*
* - As a utility snippet. Place it in the content somewhere and visit that page
* in the browser to generate the file.
* - As tpl inside a getResources / pdoTools call, to generate CSS for a batch
* of resources. Be careful though: will quickly lead to performance issues!
* - As snippet source for a Scheduler task. This will bypass the processor
* part and execute the task directly.
*
* Example:
*
* [[!generateCriticalCSS? &id=`[[+id]]`]]
*
* @var modX $modx
* @var array $scriptProperties
* @var object $task
*
* @package romanesco
*/
$corePath = $modx->getOption('romanescobackyard.core_path', null, $modx->getOption('core_path') . 'components/romanescobackyard/');
$romanesco = $modx->getService('romanesco','Romanesco',$corePath . 'model/romanescobackyard/',array('core_path' => $corePath));
if (!($romanesco instanceof Romanesco)) return;
$resourceID = $modx->getOption('id', $scriptProperties, '');
$resourceURL = $modx->getOption('url', $scriptProperties, '');
$resourceURI = $modx->getOption('uri', $scriptProperties, '');
$resource = $modx->getObject('modResource', $resourceID);
if (!($resource instanceof modResource)) return;
// If snippet is run as scheduled task, generate CSS directly
if (is_object($task)) {
$romanesco->generateCriticalCSS([
'id' => $resourceID,
'url' => $resourceURL,
'uri' => $resourceURI ?? $resource->get('uri'),
'cssPath' => $romanesco->getContextSetting('romanesco.custom_css_path', $resource->get('context_key')),
'criticalPath' => $romanesco->getContextSetting('romanesco.critical_css_path', $resource->get('context_key')),
'distPath' => $romanesco->getContextSetting('romanesco.semantic_dist_path', $resource->get('context_key')),
]);
return "Critical CSS generated for: {$resource->get('uri')} ($resourceID)";
}
// Run update processor to generate the critical_css_uri TV value
// NB: processor won't run without pagetitle and context_key!
// NB: sometimes an old alias is retrieved when alias is not forwarded!!
$resourceFields = [
'id' => $resourceID,
'pagetitle' => $resource->get('pagetitle'),
'alias' => $resource->get('alias'),
'context_key' => $resource->get('context_key')
];
// The update processor will trigger the GenerateCriticalCSS plugin
$response = $modx->runProcessor('resource/update', $resourceFields);
if ($response->isError()) {
$error = 'Failed to update resource: ' . $resource->get('pagetitle') . '. Errors: ' . implode(', ', $response->getAllErrors());
$modx->log(modX::LOG_LEVEL_ERROR, $error, __METHOD__, __LINE__);
return $error;
}
return "Critical CSS will be generated for: {$resource->get('uri')} ($resourceID)";
Create a physical HTML file of a resource in the designated location. This is a utility snippet. Place it in the content somewhere and visit that page in the browser to generate the file.
No preview available.
/**
* generateStaticFile
*
* Create a physical HTML file of a resource in the designated location.
*
* Usage: this is a utility snippet. Place it in the content somewhere and visit
* that page in the browser to generate the file.
*
* NB! Won't work if the snippet is on the page you want to export!
*
* @var modX $modx
* @var $scriptProperties array
*
* @package romanesco
*
* @todo: convert to plugin, support multiple files, add ability to change site_url.
*/
$file = $modx->getOption('file', $scriptProperties, '');
$resourceID = $modx->getOption('id', $scriptProperties, '');
$pathInfo = pathinfo($file);
$path = $pathInfo['dirname'] ?? '';
if (!file_exists($path)) {
mkdir($path, 0755, true);
}
if ($file) {
// The resource needs to be processed by MODX first.
// The easiest way to get the result is through the browser.
$content = file_get_contents($modx->makeUrl($resourceID,'','','full')) . PHP_EOL;
file_put_contents($file, $content);
}
return 'Done.';
Post hook for pThumb, that runs after the thumbnail is generated. It uses the Squoosh library from Google to create a WebP version of the image and optimize the original.
No preview available.
/**
* imgOptimizeThumb
*
* Output modifier for pThumb, to further optimize the generated thumbnail.
*
* It uses the Squoosh library from Google to create a WebP version of the image
* and optimize the original. You need to install the Squoosh CLI package on
* your server with NPM: 'npm install -g @squoosh/cli'
*
* If the Scheduler extra is installed, the Squoosh command is added there as an
* individual task. This means it takes a little while for all the images to be
* generated. Without Scheduler they're created when the page is requested,
* but the initial request will take a lot longer (the thumbnails are
* also being generated here).
*
* To serve the WebP images in the browser, use Nginx to intercept the image
* request and redirect it to the WebP version. It will do so by setting a
* different header with the correct mime type, but only if the WebP
* image is available (and if the browser supports it). So you don't need to
* change the image paths in MODX or provide any fallbacks in HTML.
*
* This guide perfectly explains this little trick:
* https://alexey.detr.us/en/posts/2018/2018-08-20-webp-nginx-with-fallback/
*
* @var modX $modx
* @var array $scriptProperties
* @var object $task
* @var string $input
* @var string $options
*/
use Jcupitt\Vips;
$corePath = $modx->getOption('romanescobackyard.core_path', null, $modx->getOption('core_path') . 'components/romanescobackyard/');
$romanesco = $modx->getService('romanesco','Romanesco',$corePath . 'model/romanescobackyard/',array('core_path' => $corePath));
if (!($romanesco instanceof Romanesco)) {
$modx->log(modX::LOG_LEVEL_ERROR, '[Romanesco] Class not found!');
return;
}
// Get image path from task properties, pThumb properties or input
$imgPath = $modx->getOption('img_path', $scriptProperties, $input ?? null);
$imgPathFull = str_replace('//','/', MODX_BASE_PATH . $imgPath);
$imgName = pathinfo($imgPathFull, PATHINFO_FILENAME);
$imgType = pathinfo($imgPathFull, PATHINFO_EXTENSION);
$imgType = strtolower($imgType);
$outputDir = dirname($imgPathFull);
// Check if path or file exist
if (!$imgPath || !file_exists($imgPathFull)) {
$modx->log(modX::LOG_LEVEL_WARN, '[imgOptimizeThumb] Image not found: ' . $imgPathFull);
return $imgPath;
}
// Look for resource context key
$context = $modx->getOption('context', $scriptProperties, '');
if (is_object($modx->resource) && !$context) {
$context = $modx->resource->get('context_key');
}
// Abort if optimization is disabled for this context
if (!$romanesco->getConfigSetting('img_optimize', $context)) {
return $imgPath;
}
// Abort if file format is not supported
if ($imgType == 'svg') {
return $imgPath;
}
// And if WebP version is already created
if (file_exists($outputDir . '/' . $imgName . '.webp')) {
return $imgPath;
}
// Get image quality from output modifier option, task properties or corresponding context setting
$imgQuality = $options ?? $modx->getOption('img_quality', $scriptProperties);
if (!$imgQuality) {
$imgQuality = $romanesco->getConfigSetting('img_quality', $context);
}
$imgQuality = (int) $imgQuality;
$configWebP = [
"Q" => $imgQuality,
];
$configJPG = [
"Q" => $imgQuality,
];
$configPNG = [
"Q" => $imgQuality,
];
// Use Scheduler for adding task to queue (if available)
/** @var Scheduler $scheduler */
$schedulerPath = $modx->getOption('scheduler.core_path', null, $modx->getOption('core_path') . 'components/scheduler/');
$scheduler = $modx->getService('scheduler', 'Scheduler', $schedulerPath . 'model/scheduler/');
// Generate CSS directly if snippet is run as scheduled task, or if Scheduler is not installed
if (!($scheduler instanceof Scheduler) || isset($task)) {
try {
$image = Vips\Image::newFromFile($imgPathFull);
}
catch (Vips\Exception $e) {
$modx->log(modX::LOG_LEVEL_ERROR, '[Vips] ' . $e->getMessage());
return $imgPath;
}
// Create WebP version
$image->webpsave($outputDir . '/' . $imgName . '.webp', $configWebP);
// Overwrite original with optimized version
if ($imgType == 'png') {
$image->pngsave($imgPathFull, $configPNG);
}
if ($imgType == 'jpg' || $imgType == 'jpeg') {
$image->jpegsave($imgPathFull, $configJPG);
}
return $imgPath;
}
// From here on, we're scheduling a task
$task = $scheduler->getTask('romanesco', 'ImgOptimizeThumb');
// Create task first if it doesn't exist
if (!($task instanceof sTask)) {
$task = $modx->newObject('sSnippetTask');
$task->fromArray(array(
'class_key' => 'sSnippetTask',
'content' => 'imgOptimizeThumb',
'namespace' => 'romanesco',
'reference' => 'ImgOptimizeThumb',
'description' => 'Create WebP version and reduce file size of thumbnail image.'
));
if (!$task->save()) {
return 'Error saving ImgOptimizeThumb task';
}
}
// Check if task is not already scheduled
$pendingTasks = $modx->getCollection('sTaskRun', array(
'task' => $task->get('id'),
'status' => 0,
'executedon' => NULL,
));
foreach ($pendingTasks as $pendingTask) {
$data = $pendingTask->get('data');
if (isset($data['img_path']) && $data['img_path'] == $imgPath && isset($data['img_quality']) && $data['img_quality'] == $imgQuality) {
return;
}
}
// Schedule a new run
$task->schedule('+1 minutes', array(
'img_path' => $imgPath,
'img_quality' => $imgQuality,
'context' => $context,
));
return $imgPath;
Generate minified version of given CSS file. To avoid increased saving times, execution of the Gulp process will be added to a task queue if Scheduler is installed.
No preview available.
/**
* MinifyCSS snippet
*
* Generate minified version of given CSS file.
*
* @var modX $modx
* @var array $scriptProperties
* @var object $task
* @var string $input
* @var Scheduler $scheduler
*
*/
$corePath = $modx->getOption('romanescobackyard.core_path', null, $modx->getOption('core_path') . 'components/romanescobackyard/');
$romanesco = $modx->getService('romanesco','Romanesco',$corePath . 'model/romanescobackyard/', array('core_path' => $corePath));
$corePath = $modx->getOption('scheduler.core_path', null, $modx->getOption('core_path') . 'components/scheduler/');
$scheduler = $modx->getService('scheduler', 'Scheduler', $corePath . 'model/scheduler/');
if (!($romanesco instanceof Romanesco)) {
$modx->log(modX::LOG_LEVEL_ERROR, '[Romanesco] Class not found!');
return;
}
// Get CSS path from task properties, snippet properties or input
$cssPath = $modx->getOption('css_path', $scriptProperties, $input);
// Generate CSS directly if snippet is run as scheduled task, or if Scheduler is not installed
if (!($scheduler instanceof Scheduler) || is_object($task)) {
$romanesco->minifyCSS($cssPath);
return;
}
// From here on, we're scheduling a task
$task = $scheduler->getTask('romanesco', 'MinifyCSS');
// Create task first if it doesn't exist
if (!($task instanceof sTask)) {
$task = $modx->newObject('sSnippetTask');
$task->fromArray([
'class_key' => 'sSnippetTask',
'content' => 'minifyCSS',
'namespace' => 'romanesco',
'reference' => 'MinifyCSS',
'description' => 'Generate minified version of given CSS file.'
]);
if (!$task->save()) {
return 'Error saving MinifyCSS task';
}
}
// Check if task is not already scheduled
$pendingTasks = $modx->getCollection('sTaskRun', [
'task' => $task->get('id'),
'status' => 0,
'executedon' => NULL,
]);
foreach ($pendingTasks as $pendingTask) {
$data = $pendingTask->get('data');
if (isset($data['css_path']) && $data['css_path'] == $cssPath) {
return;
}
}
// Schedule a new run
$task->schedule('+1 minutes', [
'css_path' => $cssPath
]);
Retrieve width and height from physical image files. Auto detects SVGs.
No preview available.
/**
* getImageDimensions
*
* Retrieve width and height from physical image files. Auto-detects SVGs.
*
* @var modX $modx
* @var array $scriptProperties
*/
$imgPath = $modx->getOption('image', $scriptProperties, '');
$imgFile = pathinfo($imgPath, PATHINFO_FILENAME);
$imgType = pathinfo($imgPath, PATHINFO_EXTENSION);
$phWidth = $modx->getOption('phWidth', $scriptProperties, 'img_width');
$phHeight = $modx->getOption('phHeight', $scriptProperties, 'img_height');
$prefix = $modx->getOption('prefix', $scriptProperties, '');
// Check if image is SVG
if (strtolower($imgType) == 'svg') {
$modx->log(modX::LOG_LEVEL_INFO, '[getImageDimensions] Image is SVG');
$xml = file_get_contents($imgPath);
$attributes = simplexml_load_string($xml)->attributes();
// Primarily relying on viewbox, since width and height values are not
// required and also kind of meaningless given the scalable nature of SVG.
$viewbox = (string) $attributes->viewBox;
if ($viewbox) {
$viewbox = explode(' ', $viewbox);
$width = round($viewbox[2], 5);
$height = round($viewbox[3], 5);
}
// Fall back on width and height attributes if viewbox is empty
else {
$width = (string) $attributes->width;
$width = preg_replace('/[a-zA-Z]+/', '', $width);
$width = round($width, 2);
$height = (string) $attributes->height;
$height = preg_replace('/[a-zA-Z]+/', '', $height);
$height = round($height, 2);
}
}
// Validate image file and get dimensions
else if (substr(mime_content_type($imgPath), 0, 5) === 'image') {
$modx->log(modX::LOG_LEVEL_INFO, '[getImageDimensions] Logo is valid image file');
$img = getimagesize($imgPath);
$width = $img[0];
$height = $img[1];
}
else {
$modx->log(modX::LOG_LEVEL_ERROR, '[getImageDimensions] Image file could not be found');
return '';
}
// Only output values if they exist
if ($width) $modx->toPlaceholder($phWidth, round($width), $prefix);
if ($height) $modx->toPlaceholder($phHeight, round($height), $prefix);
return '';
Retrieve the largest existing thumbnail image available. You can choose between JPG and webP extension. Can be used as output modifier as well.
No preview available.
/**
* getVimeoData
*
* Retrieve thumbnail and video ID through oEmbed. Outputs them as placeholder.
*
* You need to make this request because video ID and thumbnail ID are not
* always the same, depending on the Vimeo privacy settings.
*
* @var modX $modx
* @var array $scriptProperties
*/
$videoURL = $modx->getOption('videoURL', $scriptProperties, '');
$imgSize = $modx->getOption('imgSize', $scriptProperties, '720');
$imgType = $modx->getOption('imgType', $scriptProperties, 'webp');
$prefix = $modx->getOption('prefix', $scriptProperties, '');
$cacheKey = parse_url($videoURL, PHP_URL_PATH);
$cacheManager = $modx->getCacheManager();
$cacheLifetime = (int)$modx->getOption('cacheLifetime', $scriptProperties, 7 * 24 * 60 * 60, true);
$cacheOptions = [
xPDO::OPT_CACHE_KEY => 'video/vimeo',
];
$fromCache = true;
$data = $cacheManager->get($cacheKey, $cacheOptions);
// Use pThumb cache location if activated
if ($modx->getOption('pthumb.use_ptcache', null, true) ) {
$cachePath = $modx->getOption('pthumb.ptcache_location', null, 'assets/cache/img', true);
} else {
$cachePath = $modx->getOption('phpthumbof.cache_path', null, "assets/components/phpthumbof/cache", true);
}
$cachePath = rtrim($cachePath, '/') . '/video/vimeo/';
$cachePathFull = MODX_BASE_PATH . $cachePath;
// Invalidate cache if URL changed
if (isset($data['properties']) && array_diff($data['properties'], $scriptProperties)) {
$data = '';
}
// Invalidate cache if thumbnail can't be found
if (isset($data['thumbPath']) && !file_exists(MODX_BASE_PATH . $data['thumbPath'])) {
$data = '';
}
// Make the request and fetch thumbnail
if (!is_array($data)) {
$fromCache = false;
$curl = curl_init();
curl_setopt_array($curl, array(
CURLOPT_URL => "https://vimeo.com/api/oembed.json?url=" . $videoURL,
CURLOPT_AUTOREFERER => true,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 3,
CURLOPT_TIMEOUT => 10,
CURLOPT_CUSTOMREQUEST => "GET",
CURLOPT_POSTFIELDS => "",
));
$response = curl_exec($curl);
$error = curl_error($curl);
curl_close($curl);
if ($error) {
$modx->log(modX::LOG_LEVEL_ERROR, $error);
return;
}
// Response is in JSON
$response = json_decode($response, 1);
// Construct proper thumb URL
$thumbURL = $response['thumbnail_url'];
$videoID = $response['video_id'];
$thumbSplit = explode('_', $thumbURL);
$thumbID = str_replace('https://i.vimeocdn.com/video/', '', $thumbSplit[0]);
$thumbURL = 'https://i.vimeocdn.com/video/' . $thumbID . '_' . $imgSize . '.' . $imgType;
// Write image file to assets cache folder
$thumbFile = file_get_contents($thumbURL);
$thumbFileName = $videoID . '.' . $imgSize . '.' . $imgType;
$thumbPath = $cachePath . $thumbFileName;
if (!@is_dir($cachePathFull)) {
if (!@mkdir($cachePathFull, 0755, 1)) {
$modx->log(xPDO::LOG_LEVEL_ERROR, '[getVimeoData] Could not create cache path.', '', 'Romanesco');
}
}
if (!@file_put_contents(MODX_BASE_PATH . $thumbPath, $thumbFile)) {
$modx->log(xPDO::LOG_LEVEL_ERROR, '[getVimeoData] Could not create thumbnail file @ ' . $thumbPath, '', 'Romanesco');
}
// Create array of data to be cached
$data = [
'properties' => $scriptProperties,
'response' => $response,
'thumbURL' => $thumbURL,
'thumbPath' => $thumbPath,
];
$cacheManager->set($cacheKey, $data, $cacheLifetime, $cacheOptions);
}
if (!is_array($data)) {
$modx->log(modX::LOG_LEVEL_ERROR, '[getVimeoData] Could not find requested data');
return '';
}
// Load data from cache
$modx->toPlaceholder('vimeoID', $data['response']['video_id'], $prefix);
$modx->toPlaceholder('vimeoThumb', $data['thumbPath'], $prefix);
//return '<p>From cache: ' . ($fromCache ? 'Yes' : 'No') . '</p>';
return '';
Retrieve the largest existing thumbnail image available. You can choose between JPG and webP extension. Can be used as output modifier as well.
No preview available.
/**
* getYoutubeThumb
*
* Retrieve the largest existing thumbnail image available. You can choose
* between JPG and WebP extension.
*
* @var modX $modx
* @var array $scriptProperties
*/
$videoID = $modx->getOption('videoID', $scriptProperties, '');
$imgSize = $modx->getOption('imgSize', $scriptProperties, '720');
$imgType = $modx->getOption('imgType', $scriptProperties, 'webp');
$prefix = $modx->getOption('prefix', $scriptProperties, '');
$cacheKey = $videoID;
$cacheManager = $modx->getCacheManager();
$cacheLifetime = (int)$modx->getOption('cacheLifetime', $scriptProperties, 7 * 24 * 60 * 60, true);
$cacheOptions = [
xPDO::OPT_CACHE_KEY => 'video/youtube',
];
$fromCache = true;
$data = $cacheManager->get($cacheKey, $cacheOptions);
// Use pThumb cache location if activated
if ($modx->getOption('pthumb.use_ptcache', null, true) ) {
$cachePath = $modx->getOption('pthumb.ptcache_location', null, 'assets/cache/img', true);
} else {
$cachePath = $modx->getOption('phpthumbof.cache_path', null, "assets/components/phpthumbof/cache", true);
}
$cachePath = rtrim($cachePath, '/') . '/video/youtube/';
$cachePathFull = MODX_BASE_PATH . $cachePath;
// Invalidate cache if ID changed
if (isset($data['properties']) && array_diff($data['properties'], $scriptProperties)) {
$data = '';
}
// Invalidate cache if thumbnail can't be found
if (isset($data['thumbPath']) && !file_exists(MODX_BASE_PATH . $data['thumbPath'])) {
$data = '';
}
// Make the request and fetch thumbnail
if (!is_array($data)) {
$fromCache = false;
if (!$imgType) $imgType = 'jpg';
$vi = ($imgType == 'webp') ? 'vi_webp' : 'vi';
$imgURL = "https://img.youtube.com/$vi/$videoID/0.$imgType";
$resolutions = ['maxresdefault', 'hqdefault', 'mqdefault'];
// Loop through resolutions until a match is found
foreach($resolutions as $resolution) {
$maxResURL = "https://img.youtube.com/$vi/$videoID/$resolution.$imgType";
if(@getimagesize($maxResURL)) {
$imgURL = $maxResURL;
break;
}
}
// Write image file to assets cache folder
$thumbFile = file_get_contents($imgURL);
$thumbFileName = $videoID . '.' . $imgType;
$thumbPath = $cachePath . $thumbFileName;
if (!@is_dir($cachePathFull)) {
if (!@mkdir($cachePathFull, 0755, 1)) {
$modx->log(xPDO::LOG_LEVEL_ERROR, '[getYoutubeThumb] Could not create cache path.', '', 'Romanesco');
}
}
if (!@file_put_contents(MODX_BASE_PATH . $thumbPath, $thumbFile)) {
$modx->log(xPDO::LOG_LEVEL_ERROR, '[getYoutubeThumb] Could not create thumbnail file @ ' . $thumbPath, '', 'Romanesco');
}
// Create array of data to be cached
$data = [
'properties' => $scriptProperties,
'thumbURL' => $imgURL,
'thumbPath' => $thumbPath,
];
$cacheManager->set($cacheKey, $data, $cacheLifetime, $cacheOptions);
}
if (!is_array($data)) {
$modx->log(modX::LOG_LEVEL_ERROR, '[getYoutubeThumb] Could not find requested data');
return '';
}
// Load data from cache
$modx->toPlaceholder('youtubeThumb', $data['thumbPath'], $prefix);
//return '<p>From cache: ' . ($fromCache ? 'Yes' : 'No') . '</p>';
return;
Load CSS and JS for a specific component. Some assets are included in semantic.css by default, to keep its file size down.
No preview available.
/**
* loadAssets
*
* Generic asset loader. Needs a component value to decide which assets to load.
*
* The MODX regClient functions will automatically filter duplicate statements,
* so you don't need to worry about an element being loaded more than once (as
* opposed to putting the link / script in your templates).
*
* External javascript sources are loaded with defer, to keep their loading
* sequence intact (based on position in HTML). CSS is loaded asynchronously if
* it's not directly affecting page styling (modals for example) and if
* critical CSS is enabled.
*
* Cache busting and minification is also taken care of. The biggest limitation
* is that you can only add one component at the time.
*
* You can add custom CSS or JS by defining a custom component:
*
* [[loadAssets?
* &component=`custom`
* &css=`[[++romanesco.custom_css_path]]/custom[[+minify]][[+cache_buster_css]].css`
* &js=`[[++romanesco.custom_js_path]]/custom[[+minify]][[+cache_buster_js]].js`
* &inlineJS=`
* <script>
* window.addEventListener('DOMContentLoaded', function() {
* console.log('Something very custom');
* });
* </script>
* `
* ]]
*
* This does support multiple CSS and JS references. Just add them in a valid
* JSON array:
*
* [[loadAssets?
* &component=`custom`
* &css=`[
* "[[++romanesco.custom_css_path]]/custom1[[+minify]][[+cache_buster_css:empty=``]].css",
* "[[++romanesco.custom_css_path]]/custom2[[+minify]][[+cache_buster_css:empty=``]].css"
* ]`
* ]]
*
* You can prevent asynchronous loading of custom CSS with &cssAsync=`0`.
*
* @var modX $modx
* @var array $scriptProperties
*
* @package romanesco
*/
$corePath = $modx->getOption('romanescobackyard.core_path', null, $modx->getOption('core_path') . 'components/romanescobackyard/');
$romanesco = $modx->getService('romanesco','Romanesco',$corePath . 'model/romanescobackyard/',array('core_path' => $corePath));
if (!($romanesco instanceof Romanesco)) {
$modx->log(modX::LOG_LEVEL_ERROR, '[Romanesco] Class not found!');
return;
}
$assetsPathCSS = $modx->getOption('romanesco.semantic_css_path', $scriptProperties, '');
$assetsPathJS = $modx->getOption('romanesco.semantic_js_path', $scriptProperties, '');
$assetsPathVendor = $modx->getOption('romanesco.semantic_vendor_path', $scriptProperties, '');
$assetsPathDist = $modx->getOption('romanesco.semantic_dist_path', $scriptProperties, '');
// Which component are we loading assets for
$component = $modx->getOption('component', $scriptProperties, '');
if (!$component) {
$modx->log(modX::LOG_LEVEL_ERROR, '[loadAssets] Component not defined!');
return;
}
// Custom components bring their own CSS or JS
$customCSS = $modx->getOption('css', $scriptProperties, '');
$customJS = $modx->getOption('js', $scriptProperties, '');
$customInlineJS = $modx->getOption('inlineJS', $scriptProperties, '');
// Load strings to insert in asset paths when cache busting is enabled
$cacheBusterCSS = $romanesco->getCacheBustingString('CSS');
$cacheBusterJS = $romanesco->getCacheBustingString('JS');
// Check if minify assets setting is activated in Configuration
$minify = '';
if ($modx->getObject('cgSetting', array('key' => 'minify_css_js'))->get('value') == 1) {
$minify = '.min';
}
// Define conditions for loading CSS asynchronously
$async = array(
'always' => ' media="print" onload="this.media=\'all\'"',
'critical' => '',
'custom' => '',
);
if ($romanesco->getConfigSetting('critical_css', $modx->resource->get('context_key'))) {
$async['critical'] = $async['always'];
}
if ($modx->getOption('cssAsync', $scriptProperties, 1)) {
$async['custom'] = $async['always'];
}
switch ($component) {
case 'hub':
case 'status grid':
case 'status-grid':
$modx->regClientStartupHTMLBlock('<link rel="stylesheet" href="' . $assetsPathDist . '/components/table.min' . $cacheBusterCSS . '.css"' . $async['critical'] . '>');
$modx->regClientStartupHTMLBlock('<link rel="stylesheet" href="' . $assetsPathDist . '/components/step.min' . $cacheBusterCSS . '.css"' . $async['critical'] . '>');
$modx->regClientStartupHTMLBlock('<link rel="stylesheet" href="' . $assetsPathDist . '/components/dimmer.min' . $cacheBusterCSS . '.css"' . $async['always'] . '>');
$modx->regClientStartupHTMLBlock('<link rel="stylesheet" href="' . $assetsPathDist . '/components/modal.min' . $cacheBusterCSS . '.css"' . $async['always'] . '>');
$modx->regClientHTMLBlock('<script defer src="' . $assetsPathDist . '/components/dimmer.min' . $cacheBusterJS . '.js"></script>');
$modx->regClientHTMLBlock('<script defer src="' . $assetsPathDist . '/components/modal.min' . $cacheBusterJS . '.js"></script>');
$modx->regClientHTMLBlock('<script defer src="' . $assetsPathVendor . '/tablesort/tablesort.min' . $cacheBusterJS . '.js"></script>');
$modx->regClientHTMLBlock('<script defer src="' . $assetsPathJS . '/hub' . $minify . $cacheBusterJS . '.js"></script>');
break;
case 'table':
$modx->regClientStartupHTMLBlock('<link rel="stylesheet" href="' . $assetsPathDist . '/components/table.min' . $cacheBusterCSS . '.css"' . $async['critical'] . '>');
$modx->regClientHTMLBlock('<script defer src="' . $assetsPathVendor . '/tablesort/tablesort.min' . $cacheBusterJS . '.js"></script>');
break;
case 'tab':
case 'tabs':
$modx->regClientStartupHTMLBlock('<link rel="stylesheet" href="' . $assetsPathDist . '/components/tab.min' . $cacheBusterCSS . '.css"' . $async['critical'] . '>');
$modx->regClientHTMLBlock('<script defer src="' . $assetsPathDist . '/components/tab.min' . $cacheBusterJS . '.js"></script>');
break;
case 'step':
case 'steps':
$modx->regClientStartupHTMLBlock('<link rel="stylesheet" href="' . $assetsPathDist . '/components/step.min' . $cacheBusterCSS . '.css"' . $async['critical'] . '>');
break;
case 'dropdown':
$modx->regClientStartupHTMLBlock('<link rel="stylesheet" href="' . $assetsPathDist . '/components/dropdown.min' . $cacheBusterCSS . '.css"' . $async['critical'] . '>');
$modx->regClientHTMLBlock('<script defer src="' . $assetsPathDist . '/components/dropdown.min' . $cacheBusterJS . '.js"></script>');
break;
case 'dropdown-css':
$modx->regClientStartupHTMLBlock('<link rel="stylesheet" href="' . $assetsPathDist . '/components/dropdown.min' . $cacheBusterCSS . '.css"' . $async['critical'] . '>');
break;
case 'popup':
$modx->regClientStartupHTMLBlock('<link rel="stylesheet" href="' . $assetsPathDist . '/components/popup.min' . $cacheBusterCSS . '.css"' . $async['critical'] . '>');
$modx->regClientHTMLBlock('<script defer src="' . $assetsPathDist . '/components/popup.min' . $cacheBusterJS . '.js"></script>');
break;
case 'modal':
$modx->regClientStartupHTMLBlock('<link rel="stylesheet" href="' . $assetsPathDist . '/components/dimmer.min' . $cacheBusterCSS . '.css"' . $async['always'] . '>');
$modx->regClientStartupHTMLBlock('<link rel="stylesheet" href="' . $assetsPathDist . '/components/modal.min' . $cacheBusterCSS . '.css"' . $async['always'] . '>');
$modx->regClientHTMLBlock('<script defer src="' . $assetsPathDist . '/components/dimmer.min' . $cacheBusterJS . '.js"></script>');
$modx->regClientHTMLBlock('<script defer src="' . $assetsPathDist . '/components/modal.min' . $cacheBusterJS . '.js"></script>');
break;
case 'dimmer':
$modx->regClientStartupHTMLBlock('<link rel="stylesheet" href="' . $assetsPathDist . '/components/dimmer.min' . $cacheBusterCSS . '.css"' . $async['always'] . '>');
$modx->regClientHTMLBlock('<script defer src="' . $assetsPathDist . '/components/dimmer.min' . $cacheBusterJS . '.js"></script>');
break;
case 'embed':
$modx->regClientStartupHTMLBlock('<link rel="stylesheet" href="' . $assetsPathDist . '/components/embed.min' . $cacheBusterCSS . '.css"' . $async['critical'] . '>');
$modx->regClientHTMLBlock('<script defer src="' . $assetsPathDist . '/components/embed.min' . $cacheBusterJS . '.js"></script>');
break;
case 'toast':
$modx->regClientStartupHTMLBlock('<link rel="stylesheet" href="' . $assetsPathDist . '/components/toast.min' . $cacheBusterCSS . '.css"' . $async['always'] . '>');
$modx->regClientHTMLBlock('<script defer src="' . $assetsPathDist . '/components/toast.min' . $cacheBusterJS . '.js"></script>');
break;
case 'loader':
$modx->regClientStartupHTMLBlock('<link rel="stylesheet" href="' . $assetsPathDist . '/components/loader.min' . $cacheBusterCSS . '.css"' . $async['always'] . '>');
break;
case 'feed':
$modx->regClientStartupHTMLBlock('<link rel="stylesheet" href="' . $assetsPathDist . '/components/feed.min' . $cacheBusterCSS . '.css"' . $async['critical'] . '>');
break;
case 'flag':
$modx->regClientStartupHTMLBlock('<link rel="stylesheet" href="' . $assetsPathDist . '/components/flag.min' . $cacheBusterCSS . '.css"' . $async['always'] . '>');
break;
case 'code':
$modx->regClientStartupHTMLBlock('<link rel="stylesheet" href="' . $assetsPathVendor . '/prism/prism.min' . $cacheBusterCSS . '.css"' . $async['critical'] . '>');
$modx->regClientHTMLBlock('<script defer src="' . $assetsPathVendor . '/prism/prism.min' . $cacheBusterJS . '.js"></script>');
break;
case 'map':
$modx->regClientStartupHTMLBlock('<link rel="stylesheet" href="' . $assetsPathVendor . '/leaflet/leaflet' . $cacheBusterCSS . '.css"' . $async['critical'] . '>');
$modx->regClientHTMLBlock('<script defer src="' . $assetsPathVendor . '/leaflet/leaflet' . $cacheBusterJS . '.js"></script>');
break;
case 'custom':
if (!function_exists('is_json')) {
function is_json($string) {
json_decode($string);
return json_last_error() === JSON_ERROR_NONE;
}
}
if ($customCSS) {
if (is_json($customCSS)) {
$customCSS = json_decode($customCSS, true);
foreach ($customCSS as $CSS) {
$modx->regClientStartupHTMLBlock('<link rel="stylesheet" href="' . $CSS . '"' . $async['custom'] . '>');
}
} else {
$modx->regClientStartupHTMLBlock('<link rel="stylesheet" href="' . $customCSS . '"' . $async['custom'] . '>');
}
}
if ($customJS) {
if (is_json($customJS)) {
$customJS = json_decode($customJS, true);
foreach ($customJS as $JS) {
$modx->regClientHTMLBlock('<script defer src="' . $JS . '"></script>');
}
} else {
$modx->regClientHTMLBlock('<script defer src="' . $customJS . '"></script>');
}
}
if ($customInlineJS) {
$modx->regClientHTMLBlock($customInlineJS);
}
break;
}
return '';
Generate a number of srcset properties, for use inside an img tag.
No preview available.
/**
* responsiveImgSrcset
*
* Generates a number of srcset properties, for use inside an <img> tag.
*
* The dimensions for each srcset image are defined inside the
* img_breakpoints configuration setting.
*
* @var modX $modx
* @var array $scriptProperties
*/
$breakpoints = $modx->getOption('breakpoints', $scriptProperties, '');
$src = $modx->getOption('src', $scriptProperties, '');
$crop = $modx->getOption('crop', $scriptProperties, '');
$width = $modx->getOption('width', $scriptProperties, '');
$quality = $modx->getOption('quality', $scriptProperties, '');
$tpl = $modx->getOption('tpl', $scriptProperties, 'imgResponsiveRowSrcset');
$prefix = $modx->getOption('prefix', $scriptProperties, '');
$placeholder = $modx->getOption('toPlaceholder', $scriptProperties, '');
// Output filters are also processed when the input is empty, so check for that.
if ($breakpoints == '') { return ''; }
$breakpoints = explode(',', $breakpoints);
$output = array();
// Process each breakpoint individually
foreach ($breakpoints as $key => $value) {
$output[] = $modx->getChunk($tpl, array(
'src' => $src,
'crop' => $crop,
'width' => $width,
'breakpoint' => $value,
'quality' => $quality
));
}
$output = implode(",\n", $output);
if ($placeholder) {
$modx->toPlaceholder($placeholder, $output, $prefix);
return '';
} else {
return $output;
}
Load CSS and JS dependencies for Swiper slider. It also initializes a Swiper instance for each slider, with it's own parameters. This means you can use multiple sliders on one page.
No preview available.
/**
* sliderLoadAssets
*
* Loads dependencies for the Swiper carousel (https://swiperjs.com/).
*
* @var modX $modx
* @var array $scriptProperties
*
* @package romanesco
*/
$corePath = $modx->getOption('romanescobackyard.core_path', null, $modx->getOption('core_path') . 'components/romanescobackyard/');
$romanesco = $modx->getService('romanesco','Romanesco',$corePath . 'model/romanescobackyard/',array('core_path' => $corePath));
if (!($romanesco instanceof Romanesco)) {
$modx->log(modX::LOG_LEVEL_ERROR, '[Romanesco] Class not found!');
return;
}
$uid = $modx->getOption('uid', $scriptProperties, 0);
$init = $modx->getOption('init', $scriptProperties, 'true');
$columns = $modx->getOption('columns', $scriptProperties, 1);
$scroll = $modx->getOption('slidesToScroll', $scriptProperties, 1);
$direction = $modx->getOption('direction', $scriptProperties, 'horizontal');
$spacing = $modx->getOption('spacing', $scriptProperties, 'none');
$overflow = $modx->getOption('watchOverflow', $scriptProperties, 'true');
$behaviour = $modx->getOption('behaviour', $scriptProperties, '');
$transition = $modx->getOption('transition', $scriptProperties, 'slide');
$pagination = $modx->getOption('pagination', $scriptProperties, 'none');
$responsive = $modx->getOption('responsive', $scriptProperties, 0);
$mobile = $modx->getOption('mobileOnly', $scriptProperties, 0);
$lightbox = $modx->getOption('lightbox', $scriptProperties, 0);
$tpl = $modx->getOption('tpl', $scriptProperties, 'sliderInitJS');
// Convert option values to JS settings
// Keep in mind that 'true' / 'false' needs to be a string here
// -----------------------------------------------------------------------------
// Set element ID and variable name
$id = 'swiper-' . $uid;
$var = 'Swiper' . $uid;
// Convert semantic padding to numeric value
switch ($spacing) {
case 'relaxed':
$spacing = 20;
break;
case 'very relaxed':
$spacing = 30;
break;
default:
$spacing = 0;
break;
}
// Create variable for each behaviour setting
$behaviour = explode(',', $behaviour);
foreach ($behaviour as $option) {
$$option = 'true';
}
// Only bullet pagination can be clickable
$clickable = ($pagination == 'bullets') ? 'true' : 'false';
// Effects
$effects = array(
'fade' => '
fadeEffect: {
crossFade: true
},
',
'coverflow' => '
coverflowEffect: {
rotate: 30,
slideShadows: false,
},
',
'flip' => '
flipEffect: {
rotate: 30,
slideShadows: false,
},
',
'cube' => '
cubeEffect: {
slideShadows: false,
},
',
);
// Responsive
if ($responsive) {
$breakpoints = "
breakpoints: {
'@0.75': {
slidesPerView: " . round($columns / 2) . ",
spaceBetween: " . $spacing / 2 . ",
},
'@1.00': {
slidesPerView: " . round($columns * 0.75) . ",
spaceBetween: $spacing,
},
'@1.50': {
slidesPerView: $columns,
spaceBetween: " . $spacing * 1.5 . ",
},
},
";
// This feature is mobile-first, so set columns for smallest screens
$columns = round($columns / 4);
}
// Init lightbox modals with Swiper inside
if ($lightbox == 1) {
$init = 'false';
$initLightbox = "
$('#gallery-$uid .ui.lightbox.image').click(function () {
var idx = $(this).data('idx');
var modalID = '#lightbox-$uid';
var lazyLoadLightbox = new LazyLoad({
elements_selector: modalID + ' .lazy'
});
$(modalID)
.modal({
onVisible: function() {
lazyLoadLightbox.loadAll();
lazyLoadInstance.update();
}
})
.modal('show')
;
$var.init();
$var.slideTo(idx, 0, false);
});
";
}
// Use different tpl chunk for mobile only JS
if ($mobile) {
$init = 'true';
$tpl = 'sliderMobileInitJS';
}
// Load assets in head and footer
// -----------------------------------------------------------------------------
// Check if minify assets setting is activated in Configuration
$minify = '';
if ($modx->getObject('cgSetting', array('key' => 'minify_css_js'))->get('value') == 1) {
$minify = '.min';
}
// Paths
$assetsPathCSS = $modx->getOption('romanesco.semantic_css_path', $scriptProperties, '');
$assetsPathJS = $modx->getOption('romanesco.semantic_js_path', $scriptProperties, '');
$assetsPathVendor = $modx->getOption('romanesco.semantic_vendor_path', $scriptProperties, '');
$assetsPathDist = $modx->getOption('romanesco.semantic_dist_path', $scriptProperties, '');
// Load strings to insert in asset paths when cache busting is enabled
$cacheBusterCSS = $romanesco->getCacheBustingString('CSS');
$cacheBusterJS = $romanesco->getCacheBustingString('JS');
// Load component asynchronously if critical CSS is enabled
$async = '';
if ($romanesco->getConfigSetting('critical_css', $modx->resource->get('context_key'))) {
$async = ' media="print" onload="this.media=\'all\'"';
}
// Head
$modx->regClientStartupHTMLBlock('<link rel="stylesheet" href="' . $assetsPathCSS . '/swiper' . $minify . $cacheBusterCSS . '.css"' . $async . '>');
// Footer
$modx->regClientHTMLBlock('<script defer src="' . $assetsPathVendor . '/swiper/swiper-bundle.min' . $cacheBusterJS . '.js"></script>');
$modx->regClientHTMLBlock($modx->getChunk($tpl, array(
'var' => $var,
'id' => $id,
'init' => $init,
'cols' => $columns,
'slides_to_scroll' => $scroll,
'direction' => $direction,
'spacing' => $spacing,
'overflow' => $overflow ?? 'true',
'loop' => $loop ?? 'false',
'free' => $free ?? 'false',
'center' => $center ?? 'false',
'auto_height' => $autoHeight ?? 'false',
'autoplay' => $autoplay ?? 'false',
'keyboard' => $keyboard ?? 'false',
'transition' => $transition,
'pagination' => $pagination ?? '',
'clickable' => $clickable,
'breakpoints' => $breakpoints ?? '',
'effects' => $effects[$transition] ?? '',
'init_lightbox' => $initLightbox ?? '',
)));
// Load modal assets if lightbox is active
if ($lightbox == 1) {
$modx->regClientStartupHTMLBlock('<link rel="stylesheet" href="' . $assetsPathDist . '/components/dimmer.min' . $cacheBusterCSS . '.css"' . $async . '>');
$modx->regClientStartupHTMLBlock('<link rel="stylesheet" href="' . $assetsPathDist . '/components/modal.min' . $cacheBusterCSS . '.css"' . $async . '>');
$modx->regClientHTMLBlock('<script defer src="' . $assetsPathDist . '/components/dimmer.min' . $cacheBusterJS . '.js"></script>');
$modx->regClientHTMLBlock('<script defer src="' . $assetsPathDist . '/components/modal.min' . $cacheBusterJS . '.js"></script>');
}
return '';
Load CSS and JS dependencies for status grid.
No preview available.
/**
* statusGridLoadAssets
*
* @var modX $modx
* @var array $scriptProperties
*
* @package romanesco
*/
$corePath = $modx->getOption('romanescobackyard.core_path', null, $modx->getOption('core_path') . 'components/romanescobackyard/');
$romanesco = $modx->getService('romanesco','Romanesco',$corePath . 'model/romanescobackyard/',array('core_path' => $corePath));
if (!($romanesco instanceof Romanesco)) {
$modx->log(modX::LOG_LEVEL_ERROR, '[Romanesco] Class not found!');
return;
}
$assetsPathCSS = $modx->getOption('romanesco.semantic_css_path', $scriptProperties, '');
$assetsPathJS = $modx->getOption('romanesco.semantic_js_path', $scriptProperties, '');
$assetsPathVendor = $modx->getOption('romanesco.semantic_vendor_path', $scriptProperties, '');
$assetsPathDist = $modx->getOption('romanesco.semantic_dist_path', $scriptProperties, '');
// Load strings to insert in asset paths when cache busting is enabled
$cacheBusterCSS = $romanesco->getCacheBustingString('CSS');
$cacheBusterJS = $romanesco->getCacheBustingString('JS');
// Header
$modx->regClientCSS($assetsPathDist . '/components/step.min' . $cacheBusterCSS . '.css');
$modx->regClientCSS($assetsPathDist . '/components/modal.min' . $cacheBusterCSS . '.css');
// Footer
$modx->regClientScript($assetsPathDist . '/components/modal.min' . $cacheBusterJS . '.js');
$modx->regClientScript($assetsPathVendor . '/tablesort/tablesort.min' . $cacheBusterJS . '.js');
return '';
Load CSS styles for Steps component. This is not included in semantic.css by default, to keep its file size down.
No preview available.
/**
* stepsLoadAssets
*
* @var modX $modx
* @var array $scriptProperties
*
* @package romanesco
*/
$corePath = $modx->getOption('romanescobackyard.core_path', null, $modx->getOption('core_path') . 'components/romanescobackyard/');
$romanesco = $modx->getService('romanesco','Romanesco',$corePath . 'model/romanescobackyard/',array('core_path' => $corePath));
if (!($romanesco instanceof Romanesco)) {
$modx->log(modX::LOG_LEVEL_ERROR, '[Romanesco] Class not found!');
return;
}
$assetsPathDist = $modx->getOption('romanesco.semantic_dist_path', $scriptProperties, '');
$cacheBusterCSS = $romanesco->getCacheBustingString('CSS');
// Header
$modx->regClientCSS($assetsPathDist . '/components/step.min' . $cacheBusterCSS . '.css');
return '';
Generate the tab buttons based on data-heading attribute in the tabs themselves. It basically links every tab button to the correct tab content.
No preview available.
/**
* tabsGenerateNav
*
* Create tab buttons based on the tab content's HTML.
* Each content field contains data attributes with the correct text for each heading.
*
* Many thanks to @christianseel for the original idea and code.
*
* @var modX $modx
* @var array $scriptProperties
* @var string $input
* @var string $options
*/
$input = $modx->getOption('input', $scriptProperties, $input);
$tpl = $modx->getOption('tpl', $scriptProperties, 'tabsNavItem');
$tplIcon = $modx->getOption('tplIcon', $scriptProperties, 'tabsNavItemIcon');
$prefix = $modx->getOption('prefix', $scriptProperties, '');
$placeholder = $modx->getOption('toPlaceholder', $scriptProperties, false);
$doc = new DOMDocument();
// Set error level to suppress warnings in log over special characters in HTML
$internalErrors = libxml_use_internal_errors(true);
// Load HTML
$doc->loadHTML('<meta http-equiv="content-type" content="text/html; charset=utf-8">' . $input);
// Restore error level
$internalErrors = libxml_use_internal_errors(false);
$divs = $doc->getElementsByTagName('div'); // Little flaky.. If div element changes, navigation breaks.
$tabs = array();
$idx = 1;
foreach($divs as $div) {
if ($div->hasAttribute('data-tab')) {
$tabs[$div->getAttribute('data-tab')] = array(
'heading' => $div->getAttribute('data-heading'),
'level' => $div->getAttribute('data-level'),
'subtitle' => $div->getAttribute('data-subtitle'),
'icon' => $div->getAttribute('data-icon')
);
}
}
$tabHeaders = '';
$idx = 1;
foreach($tabs as $tab) {
if ($tab['icon']) {
$tpl = $tplIcon;
}
$properties = array(
'idx' => $idx,
'heading' => $tab['heading'],
'level' => $tab['level'],
'subtitle' => $tab['subtitle'],
'icon' => $tab['icon'],
);
$tabHeaders .= $modx->getChunk($tpl, $properties);
$idx++;
}
// Return placeholder with idx, so tab menu can be justified
$modx->toPlaceholder('tabs_total', $idx - 1);
// Output either to placeholder, or directly
if ($placeholder) {
$modx->toPlaceholder('pl', $prefix);
$modx->toPlaceholder($placeholder, $tabHeaders, $prefix);
return '';
}
return $tabHeaders;
Return the amount of child pages a resource has. Now you can make one of those shiny little badges inside a menu button, telling the user upfront how much treasure is inside.
No preview available.
/**
* getChildCount
*
* @var modX $modx
* @var array $scriptProperties
* @var string $parent
*/
$count = 0;
$parent = isset($parent) ? (integer) $parent : 0;
if ($parent > 0) {
$criteria = array(
'parent' => $parent,
'deleted' => false,
'published' => true,
);
$count = $modx->getCount('modResource', $criteria);
}
return (string) $count;
Return the IDs of resources that link to current resource through a given TV. Did that make sense? For example: you can show things like relevant tags or reviews in blog posts and vice versa.
No preview available.
/**
* getCrossLinks
*
* @var modX $modx
* @var array $scriptProperties
* @var string $input
* @var string $options
*/
// @todo: get them!
Show the level of a given resource based on the number of parent IDs. Useful for example if you only want to show a breadcrumb trail on pages that are two or three levels deep.
No preview available.
/**
* @var modX $modx
*/
$parents = $modx->getParentIds($modx->resource->get('id'));
return count($parents);
Takes an ID as input and returns the content type. Mainly intended as snippet renderer for Collections, but can be used independently or as output modifier too.
No preview available.
/**
* renderContentType
*
* @var modX $modx
* @var array $scriptProperties
* @var string $input
* @var string $options
*/
$id = $modx->getOption('id', $scriptProperties, $input);
$type = $modx->getObject('modContentType', $id);
if (is_object($type)) {
return $type->get('name');
}
return;
Takes an ID as input and returns a list of pages in which this resource is used. Intended as snippet renderer for Collections, to show where Forms, CTAs and Backgrounds are being used.
No preview available.
/**
* renderReferringPages
*
* Takes an ID as input and returns a list of pages in which this resource is
* referenced. Intended as snippet renderer for Collections, to show where Forms,
* CTAs and Backgrounds are being used.
*
* Scans content and TVs. Note that for TVs, inherited values are not evaluated.
*
* If you want to limit the list to only include pages from certain contexts,
* you may do so by creating the system referring_pages_contexts in the
* Romanesco namespace.
*
* @var modX $modx
* @var array $scriptProperties
* @var string $input
* @var string $options
*/
$id = $modx->getOption('id', $scriptProperties, $scriptProperties['row']['id'] ?? '');
$contexts = $modx->getOption('contexts', $scriptProperties, $modx->getOption('romanesco.referring_pages_contexts') ?? '');
$column = $modx->getOption('column', $scriptProperties);
$where = '';
// Content
switch ($column) {
case 'referring_pages_form':
$where = '{ "properties:LIKE":"%\"form_id\":\"' . $id . '\"%" }';
break;
case 'referring_pages_cta':
$where = '{ "properties:LIKE":"%\"cta_id\":\"' . $id . '\"%" }';
break;
case 'referring_pages_background':
$where = '{ "properties:LIKE":"%background_____' . $id . '__,%" }';
break;
}
if (!$where) return;
// TVs
$tvValues = [];
$tvValuesHead = $modx->getCollection('modTemplateVarResource', [
'tmplvarid' => 3, // header_cta
'value' => $id
]);
$tvValuesFooter = $modx->getCollection('modTemplateVarResource', [
'tmplvarid' => 104, // footer_cta
'value' => $id
]);
$tvValuesSidebar = $modx->getCollection('modTemplateVarResource', [
'tmplvarid' => 148, // sidebar_cta
'value' => $id
]);
foreach ($tvValuesHead as $value) {
$tvValues[] = $value->get('contentid');
}
foreach ($tvValuesFooter as $value) {
$tvValues[] = $value->get('contentid');
}
foreach ($tvValuesSidebar as $value) {
$tvValues[] = $value->get('contentid');
}
if ($tvValues) {
$where .= ',{ "OR:id:IN": [' . implode(',', $tvValues) . '] }';
}
$output = $modx->runSnippet('pdoMenu', (array(
'parents' => '',
'context' => $contexts,
'limit' => 0,
'depth' => 0,
'showHidden' => 1,
'showUnpublished' => 1,
'tplOuter' => '@INLINE <ul>[[+wrapper]]</ul>',
'tpl' => '@INLINE <li><a href="[[~[[+id]]]]" target="_blank">[[+pagetitle]]</a> ([[+id]])</li>',
'sortby' => 'menuindex',
'sortdir' => 'ASC',
'where' => "[$where]",
)));
return $output;
Takes an ID as input and returns the pagetitle. Mainly intended as snippet renderer for Collections, but can be used independently or as output modifier too.
No preview available.
/**
* renderResourceName
*
* @var modX $modx
* @var array $scriptProperties
* @var string $input
* @var string $options
*/
$id = $modx->getOption('id', $scriptProperties, $input);
$resource = $modx->getObject('modResource', $id);
if (is_object($resource)) {
return $resource->get('pagetitle') . " ($id)";
}
return;
If SeoTab (StercSEO) is installed, this snippet displays the indexation setting for given resource.
No preview available.
/**
* SeoTabIndexation
*
* If SeoTab (StercSEO) is installed, this snippet displays the indexation
* setting for given resource.
*
* Can be used as output modifier:
*
* [[+id:SeoTabIndexation]]
*/
$resourceID = $modx->getOption('resource', $scriptProperties, $input);
$resource = $modx->getObject('modResource', $resourceID);
$seoTab = $modx->getObject('modPlugin', array('name'=>'StercSEO','disabled'=>0));
//$resource =& $modx->event->params['resource'];
// First, check if SEOTab plugin is installed, and active
if (!is_object($seoTab) || !is_object($resource)) {
return '';
}
$properties = $resource->getProperties('stercseo');
if ($properties['index'] == 1) {
$index = 'index';
} else {
$index = 'noindex';
}
if ($properties['follow'] == 1) {
$follow = 'follow';
} else {
$follow = 'nofollow';
}
return $index . '/' . $follow;
Check if user is allowed to access the given (or current) context and redirect to unauthorized page if that's not the case.
No preview available.
/**
* checkPermissions
*
* Check if user has access permissions for a certain context and redirect to
* unauthorized page if that's not the case.
*
* @var modX $modx
* @var array $scriptProperties
*/
$context = $modx->getOption('context', $scriptProperties, $modx->context->get('key'));
$redirectTo = $modx->getOption('redirectTo', $scriptProperties, null);
// If a context is specified, make sure we're in it
if ($context !== $modx->context->get('key')) {
return '';
}
// Exclude the unauthorized page from any redirects
if ($modx->resource->get('id') == $modx->getOption('unauthorized_page')) {
return '';
}
if (!$modx->user->hasSessionContext($context)) {
if (!empty($redirectTo)) {
$redirectParams = !empty($redirectParams) ? $modx->fromJSON($redirectParams) : '';
$url = $modx->makeUrl($redirectTo,'',$redirectParams,'full');
$modx->sendRedirect($url);
} else {
$modx->sendUnauthorizedPage();
}
}
return '';
Check if user is logged in to the manager.
No preview available.
/**
* isEditor snippet
*
* Check if user is logged in to the manager.
*
* @author Mark Hamstra
*
* @var modX $modx
*/
if ($modx->user instanceof modUser) {
if ($modx->user->hasSessionContext('mgr')) {
return true;
}
}
return false;
Gets user by ID and returns the username. Mainly intended as snippet renderer for Collections, but can be used independently or as output modifier too.
No preview available.
/**
* renderUserName
*
* @var modX $modx
* @var array $scriptProperties
* @var string $input
* @var string $options
*/
// Get a specific user
$id = $modx->getOption('id', $scriptProperties, $input);
$user = $modx->getObject('modUser', $id);
// Get user profile and fail gracefully if user doesn't exist
if ($user) {
return $user->get('username');
} else {
return '';
}
Make any extended fields that are attached to a MODX user available as placeholder.
No preview available.
/**
* setUserPlaceholders
*
* @var modX $modx
* @var array $scriptProperties
*/
$userId = $modx->getOption('userId', $scriptProperties, '');
// Get a specific user
$user = $modx->getObject('modUser', $userId);
// Get user profile and fail gracefully if user doesn't exist
if ($user) {
$profile = $user->getOne('Profile');
} else {
$modx->log(modX::LOG_LEVEL_WARN, '[setUserPlaceholders] User not found in MODX');
return '';
}
// Get extended fields of this user
if ($profile) {
$extended = $profile->get('extended');
$modx->toPlaceholders($extended, '');
} else {
$modx->log(modX::LOG_LEVEL_ERROR, '[setUserPlaceholders] Could not find profile for user: ' . $user->get('username'));
}
return '';