` panel containing the provided message. Used to
* display contextual introductions at the top of wizard steps or settings
* sections. Silently returns when `$msg` is empty.
*
* @since 1.0.0
*
* @param string $msg The message to display. HTML is allowed. Returns without
* output when this is an empty string.
* @return void
*/
function cmplz_tc_intro( $msg ) {
if ( '' === $msg ) {
return;
}
// Output the intro panel directly; $msg is expected to be pre-sanitised by the caller.
$html = "
";
echo $html; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- HTML is pre-sanitised upstream.
}
}
if ( ! function_exists( 'cmplz_tc_notice' ) ) {
/**
* Renders or returns a notification panel for the plugin admin UI.
*
* Generates a styled notification `
` that can be echoed immediately or
* returned as a string for inclusion in a larger template. Supports conditional
* display: when `$condition` is provided, the panel is shown or hidden based on
* whether the specified wizard field currently matches the expected answer.
* The `$remove_after_change` flag adds a CSS class that hides the notice as soon
* as any wizard field value changes (handled by frontend JS).
*
* @since 1.0.0
*
* @param string $msg The notification message. HTML is allowed.
* Returns without output when empty.
* @param string $type Visual style of the notice. One of:
* 'notice' (default), 'warning', 'success'.
* @param bool $remove_after_change When true, adds the 'cmplz-remove-after-change'
* CSS class so JS hides the notice on field change.
* @param bool $do_echo When true (default) the HTML is echoed; when false
* it is returned as a string.
* @param array|bool $condition Optional condition array with keys:
* - 'question' (string) Field name to check.
* - 'answer' (string) Expected field value.
* The notice is hidden via CSS when the condition
* does not currently apply. Default false.
* @return string|void The HTML string when $do_echo is false, otherwise void.
*/
function cmplz_tc_notice( $msg, $type = 'notice', $remove_after_change = false, $do_echo = true, $condition = false ) {
if ( '' === $msg ) {
return;
}
// Build conditional visibility attributes when a condition is supplied.
$condition_check = '';
$condition_question = '';
$condition_answer = '';
$cmplz_hidden = '';
if ( $condition ) {
$condition_check = 'condition-check';
$condition_question = "data-condition-question='{$condition['question']}'";
$condition_answer = "data-condition-answer='{$condition['answer']}'";
$args['condition'] = array( $condition['question'] => $condition['answer'] );
// Check whether the condition currently applies to determine initial visibility.
$cmplz_hidden = cmplz_field::this()->condition_applies( $args ) ? '' : 'cmplz-hidden';
}
// Add the removal class when the notice should disappear on any field change.
$remove_after_change_class = $remove_after_change ? 'cmplz-remove-after-change' : '';
$html = "
";
if ( $do_echo ) {
echo $html; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- HTML is pre-sanitised upstream.
} else {
return $html;
}
}
}
if ( ! function_exists( 'cmplz_tc_sidebar_notice' ) ) {
/**
* Renders or returns a sidebar-style notification for the plugin admin UI.
*
* Similar to cmplz_tc_notice() but uses a different wrapper structure and CSS
* class intended for placement in the wizard or settings sidebar (help modals,
* contextual tips). Supports the same condition and remove-after-change behaviour.
*
* @since 1.0.0
*
* @see cmplz_tc_notice() For the main-content equivalent.
*
* @param string $msg The notification message. HTML is allowed.
* Returns without output when empty.
* @param string $type Visual style. One of: 'notice' (default),
* 'warning', 'success'.
* @param bool $remove_after_change When true, adds 'cmplz-remove-after-change'
* so the notice is hidden by JS on field change.
* @param bool $do_echo When true (default) the HTML is echoed; when false
* it is returned as a string.
* @param bool|array $condition Optional condition array with 'question' and
* 'answer' keys (see cmplz_tc_notice()). Default false.
* @return string|void The HTML string when $do_echo is false, otherwise void.
*/
function cmplz_tc_sidebar_notice( $msg, $type = 'notice', $remove_after_change = false, $do_echo = true, $condition = false ) {
if ( '' === $msg ) {
return;
}
// Build conditional visibility attributes when a condition is supplied.
$condition_check = '';
$condition_question = '';
$condition_answer = '';
$cmplz_hidden = '';
if ( $condition ) {
$condition_check = 'condition-check';
$condition_question = "data-condition-question='{$condition['question']}'";
$condition_answer = "data-condition-answer='{$condition['answer']}'";
$args['condition'] = array( $condition['question'] => $condition['answer'] );
// Check whether the condition currently applies to determine initial visibility.
$cmplz_hidden = cmplz_field::this()->condition_applies( $args ) ? '' : 'cmplz-hidden';
}
// Add the removal class when the notice should disappear on any field change.
$remove_after_change_class = $remove_after_change ? 'cmplz-remove-after-change' : '';
$html = "
{$msg}
";
if ( $do_echo ) {
echo $html; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- HTML is pre-sanitised upstream.
} else {
return $html;
}
}
}
if ( ! function_exists( 'cmplz_tc_localize_date' ) ) {
/**
* Translates the month name and weekday name within a date string to the current locale.
*
* Extracts the English month name (e.g. "June") and weekday name (e.g. "Wednesday")
* from the supplied date string using PHP's date() function, then substitutes each
* with its localised equivalent via WordPress's __() translation function.
*
* @since 1.0.0
*
* @param string $date A date string parseable by strtotime() (e.g. '2024-06-12').
* @return string The date string with the month and weekday names translated to the
* current WordPress locale.
*/
function cmplz_tc_localize_date( $date ) {
// Extract the English month name and replace it with the localised equivalent.
$month = gmdate( 'F', strtotime( $date ) ); // e.g. "June".
$month_localized = __( $month ); // phpcs:ignore WordPress.WP.I18n.NonSingularStringLiteralText -- Variable contains an English month name produced by gmdate(); runtime translation is intentional.
$date = str_replace( $month, $month_localized, $date );
// Extract the English weekday name and replace it with the localised equivalent.
$weekday = gmdate( 'l', strtotime( $date ) ); // e.g. "Wednesday".
$weekday_localized = __( $weekday ); // phpcs:ignore WordPress.WP.I18n.NonSingularStringLiteralText -- Variable contains an English weekday name produced by gmdate(); runtime translation is intentional.
$date = str_replace( $weekday, $weekday_localized, $date );
return $date;
}
}
if ( ! function_exists( 'cmplz_tc_read_more' ) ) {
/**
* Builds a localised "Read more" sentence with a hyperlink to an external article.
*
* Returns a formatted string like " For more information on this subject, please
* read this [article]." with `$url` as the link target. Optionally prepends a
* non-breaking space to visually separate it from preceding text.
*
* @since 1.0.0
*
* @param string $url The URL of the article or documentation page to link to.
* @param bool $add_space Whether to prepend a non-breaking space (` `) before
* the sentence. Default true.
* @return string The complete "read more" sentence HTML.
*/
function cmplz_tc_read_more( $url, $add_space = true ) {
$html
= sprintf(
// translators: %1$s is the opening anchor tag, %2$s is the closing anchor tag.
__(
'For more information on this subject, please read this %1$sarticle%2$s.',
'complianz-terms-conditions'
),
'
',
''
);
if ( $add_space ) {
$html = ' ' . $html;
}
return $html;
}
}
if ( ! function_exists( 'cmplz_tc_get_regions' ) ) {
/**
* Returns the list of geographical regions supported by the plugin.
*
* Currently the plugin generates a single Terms & Conditions document covering
* all regions. This function provides the region list used by the document and
* wizard configuration systems; future versions may expand it with region-specific
* entries.
*
* @since 1.0.0
*
* @return array Associative array of region slugs to display labels.
* Example: array( 'all' => 'All regions' )
*/
function cmplz_tc_get_regions() {
$output['all'] = __( 'All regions', 'complianz-terms-conditions' );
return $output;
}
}
// Register the activation hook before defining the callback so WordPress can bind it.
register_activation_hook( __FILE__, 'cmplz_tc_set_activation_time_stamp' );
if ( ! function_exists( 'cmplz_tc_set_activation_time_stamp' ) ) {
/**
* Stores the plugin activation timestamp as a WordPress option.
*
* Fired on plugin activation via register_activation_hook(). The timestamp is
* used by other parts of the plugin to determine how long the plugin has been
* installed (e.g. for delaying review prompts or deferred notices).
*
* @since 1.0.0
*
* @param bool $networkwide True when activated network-wide on a multisite install,
* false for a single-site activation.
* @return void
*/
function cmplz_tc_set_activation_time_stamp( $networkwide ) { // phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.Found -- Required by register_activation_hook() signature; multisite support may use it in future.
update_option( 'cmplz_tc_activation_time', time() );
}
}
if ( ! function_exists( 'cmplz_tc_allowed_html' ) ) {
/**
* Returns the list of HTML tags and attributes permitted for wp_kses() sanitisation.
*
* Defines an extended allowed-tag map used when sanitising plugin-generated HTML
* that may contain rich markup (e.g. wizard notices, document content). The list
* can be extended by third parties via the `cmplz_tc_allowed_html` filter.
*
* @since 1.0.0
*
* @return array Associative array of allowed HTML tags mapped to their permitted
* attributes. Format matches the $allowed_html parameter of wp_kses().
*/
function cmplz_tc_allowed_html() {
// Define the full set of tags and attributes the plugin may output.
$allowed_tags = array(
'a' => array(
'class' => array(),
'href' => array(),
'rel' => array(),
'title' => array(),
'target' => array(),
'id' => array(),
),
'button' => array(
'id' => array(),
'class' => array(),
'href' => array(),
'rel' => array(),
'title' => array(),
'target' => array(),
),
'b' => array(),
'br' => array(),
'blockquote' => array(
'cite' => array(),
),
'div' => array(
'class' => array(),
'id' => array(),
),
'h1' => array(),
'h2' => array(),
'h3' => array(),
'h4' => array(),
'h5' => array(),
'h6' => array(),
'i' => array(),
'input' => array(
'type' => array(),
'class' => array(),
'id' => array(),
'required' => array(),
'value' => array(),
'placeholder' => array(),
'data-category' => array(),
'style' => array(
'color' => array(),
),
),
'img' => array(
'alt' => array(),
'class' => array(),
'height' => array(),
'src' => array(),
'width' => array(),
),
'label' => array(
'for' => array(),
'class' => array(),
'style' => array(
'visibility' => array(),
),
),
'li' => array(
'class' => array(),
'id' => array(),
),
'ol' => array(
'class' => array(),
'id' => array(),
),
'p' => array(
'class' => array(),
'id' => array(),
),
'span' => array(
'class' => array(),
'title' => array(),
'style' => array(
'color' => array(),
'display' => array(),
),
'id' => array(),
),
'strong' => array(),
'table' => array(
'class' => array(),
'id' => array(),
),
'tr' => array(),
// SVG elements are included to support inline icon output.
'svg' => array(
'width' => array(),
'height' => array(),
'viewBox' => array(),
),
'polyline' => array(
'points' => array(),
),
'path' => array(
'd' => array(),
),
'style' => array(),
'td' => array(
'colspan' => array(),
'scope' => array(),
),
'th' => array( 'scope' => array() ),
'ul' => array(
'class' => array(),
'id' => array(),
),
);
/**
* Filters the allowed HTML tags and attributes for plugin output sanitisation.
*
* @since 1.0.0
*
* @param array $allowed_tags Associative array of tags to permitted attributes.
*/
return apply_filters( 'cmplz_tc_allowed_html', $allowed_tags );
}
}
if ( ! function_exists( 'cmplz_tc_translate' ) ) {
/**
* Translates a field value through active multilingual plugins.
*
* Passes the supplied value through Polylang (pll__()), WPML (icl_translate()),
* and the `wpml_translate_single_string` filter in sequence, returning the
* translated result. Only active translation plugins affect the value; inactive
* ones are silently skipped.
*
* @since 1.0.0
*
* @param string $value The original field value to translate.
* @param string $fieldname The field name used as the WPML string context identifier.
* @return string The translated value, or the original when no translation is found.
*/
function cmplz_tc_translate( $value, $fieldname ) {
if ( function_exists( 'pll__' ) ) {
$value = pll__( $value );
}
if ( function_exists( 'icl_translate' ) ) {
$value = icl_translate( 'complianz', $fieldname, $value );
}
/**
* Filters the translated value via WPML string translation.
*
* @since 1.0.0
*
* @param mixed $value The current field value.
* @param string $context The WPML context ('complianz').
* @param string $fieldname The field name being translated.
*/
$value = apply_filters( 'wpml_translate_single_string', $value, 'complianz', $fieldname );
return $value;
}
}
if ( ! function_exists( 'cmplz_tc_sanitize_language' ) ) {
/**
* Sanitises a language string to a two-letter ISO 639-1 code.
*
* Truncates the input to its first two characters, validates that they form a
* string of exactly two ASCII letters, and returns the code in lowercase. This
* prevents invalid or overly long locale strings (e.g. 'en_US') from being
* stored where a simple language code is expected.
*
* @since 1.0.0
*
* @param mixed $language The raw language value to sanitise (e.g. 'en', 'EN', 'en_US').
* @return bool|string Lowercase two-letter language code on success (e.g. 'en'),
* or false when the input is not a valid string or does not
* contain two ASCII letters.
*/
function cmplz_tc_sanitize_language( $language ) {
// Pattern matches exactly two ASCII letters (a-z or A-Z).
$pattern = '/^[a-zA-Z]{2}$/';
if ( ! is_string( $language ) ) {
return false;
}
// Truncate to two characters to handle locale strings like 'en_US'.
$language = substr( $language, 0, 2 );
if ( (bool) preg_match( $pattern, $language ) ) {
$language = strtolower( $language );
return $language;
}
return false;
}
}
if ( ! function_exists( 'cmplz_tcf_creative_commons' ) ) {
/**
* Callback: checks whether the Creative Commons document section should be shown.
*
* Used as a `callback_condition` in the document element configuration to
* conditionally include the Creative Commons licence paragraph. Returns false
* (hide the section) when the copyright setting is 'allrights' or 'norights',
* because those values indicate a standard copyright notice rather than a CC licence.
*
* @since 1.0.0
*
* @return bool True when a Creative Commons licence type is selected, false otherwise.
*/
function cmplz_tcf_creative_commons() {
$type = cmplz_tc_get_value( 'about_copyright' );
if ( 'allrights' === $type || 'norights' === $type ) {
return false;
} else {
return true;
}
}
}
if ( ! function_exists( 'cmplz_tcf_nuts' ) ) {
/**
* Callback: checks whether the NUTS (Network Utility Transaction Services) section applies.
*
* Used as a `callback_condition` for document sections that are specific to
* utility or service subscriptions sold via NUTS. Returns true when the return
* policy covers NUTS services or utilities, so that the relevant withdrawal
* provisions are included in the generated document.
*
* @since 1.0.0
*
* @return bool True when the return type is 'nuts_services' or 'nuts_utilities',
* false otherwise.
*/
function cmplz_tcf_nuts() {
$services = cmplz_tc_get_value( 'about_returns' ) === 'nuts_services';
$utilities = cmplz_tc_get_value( 'about_returns' ) === 'nuts_utilities';
if ( $services || $utilities ) {
return true;
} else {
return false;
}
}
}
if ( ! function_exists( 'cmplz_tc_uses_gutenberg' ) ) {
/**
* Checks whether the current WordPress install is using the Gutenberg block editor.
*
* Returns true when the has_block() function exists (WordPress 5.0+) and the
* Classic Editor plugin is not active. This is used to decide whether to register
* a Gutenberg block or fall back to a classic shortcode widget.
*
* @since 1.0.0
*
* @return bool True when Gutenberg is available and Classic Editor is not active,
* false otherwise.
*/
function cmplz_tc_uses_gutenberg() {
if ( function_exists( 'has_block' )
&& ! class_exists( 'Classic_Editor' )
) {
return true;
}
return false;
}
}
if ( ! function_exists( 'cmplz_tc_user_can_manage' ) ) {
/**
* Checks whether the current user has permission to manage plugin settings.
*
* Used as a capability gate throughout the plugin's admin UI. Requires both an
* active login session and the `manage_options` capability (typically limited to
* administrators). Returns false immediately when either condition is not met.
*
* @since 1.0.0
*
* @return bool True if the current user is logged in and has `manage_options`,
* false otherwise.
*/
function cmplz_tc_user_can_manage() {
if ( ! is_user_logged_in() ) {
return false;
}
if ( ! current_user_can( 'manage_options' ) ) {
return false;
}
return true;
}
}
if ( ! function_exists( 'cmplz_tc_array_filter_multidimensional' ) ) {
/**
* Filters a multidimensional array, keeping only elements where a specific key matches a value.
*
* Iterates over a flat array of associative sub-arrays and returns only those whose
* `$filter_key` element equals `$filter_value`. Elements that do not have the
* specified key are excluded. This is used by the wizard to extract fields that
* match a particular attribute, such as all required fields (`'required' => true`).
*
* @since 1.0.0
*
* @param array $items The input array of associative sub-arrays to filter.
* @param string $filter_key The key to check within each sub-array.
* @param mixed $filter_value The value that $filter_key must equal for an element
* to be included in the result.
* @return array Filtered array preserving original keys, containing only
* elements where $items[n][$filter_key] === $filter_value.
*/
function cmplz_tc_array_filter_multidimensional(
$items,
$filter_key,
$filter_value
) {
$new = array_filter(
$items,
function ( $element ) use ( $filter_value, $filter_key ) {
return isset( $element[ $filter_key ] ) ? ( $element[ $filter_key ] === $filter_value ) : false;
}
);
return $new;
}
}