'My Title', 'content' => '

...

' ). * @return string|false Rendered template string, or false if the plugin-side * template file does not exist. */ function cmplz_tc_get_template( $filename, $args = array() ) { /** * Filters the resolved path to a plugin template file. * * @since 1.0.0 * * @param string $file Absolute path to the plugin template file. * @param string $filename Template filename relative to the templates/ directory. */ $file = apply_filters( 'cmplz_tc_template_file', trailingslashit( cmplz_tc_path ) . 'templates/' . $filename, $filename ); // Build the path where the active theme could provide a template override. $theme_file = trailingslashit( get_stylesheet_directory() ) . trailingslashit( basename( cmplz_tc_path ) ) . 'templates/' . $filename; // Return false early when the plugin-bundled template is missing entirely. if ( ! file_exists( $file ) ) { return false; } // Use the theme override when it exists. if ( file_exists( $theme_file ) ) { $file = $theme_file; } if ( strpos( $file, '.php' ) !== false ) { // Execute PHP templates with output buffering to capture dynamic output. ob_start(); require $file; $contents = ob_get_clean(); } else { // Read static (non-PHP) template files directly. $contents = file_get_contents( $file ); // phpcs:ignore WordPress.WP.AlternativeFunctions.file_get_contents_file_get_contents -- Reading a local file path, not a remote URL. } // Replace {key} placeholder tokens with the provided argument values. if ( ! empty( $args ) ) { foreach ( $args as $fieldname => $value ) { $contents = str_replace( '{' . $fieldname . '}', $value, $contents ); } } return $contents; } } if ( ! function_exists( 'cmplz_tc_settings_page' ) ) { /** * Returns the URL of the plugin's admin settings page. * * When the full Complianz suite is active (detected via the `cmplz_version` * constant), the page is hosted under `admin.php`; on standalone installs it * falls back to `tools.php`. * * @since 1.0.0 * * @return string Absolute URL to the Terms & Conditions settings page. */ function cmplz_tc_settings_page() { if ( ! defined( 'cmplz_version' ) ) { // Standalone install: page lives under Tools. return add_query_arg( array( 'page' => 'terms-conditions' ), admin_url( 'tools.php' ) ); } else { // Full Complianz suite: page is registered under the main admin menu. return add_query_arg( array( 'page' => 'terms-conditions' ), admin_url( 'admin.php' ) ); } } } if ( ! function_exists( 'cmplz_tc_get_value' ) ) { /** * Retrieves a stored plugin option value for a given field name. * * Looks up the field definition in the config to determine which option group * (source) the value belongs to, then reads it from the database. Falls back * to the field's configured default when the stored value is absent and * `$use_default` is true. When the field is marked as translatable, the value * is passed through Polylang, WPML, or any registered `wpml_translate_single_string` * filter before being returned. * * For very early usage (before the plugin classes are available), supply `$page` * directly to bypass the config class lookup. * * @since 1.0.0 * * @param string $fieldname The option field name (e.g. 'company_name'). * @param bool|string $page The option group/source identifier (e.g. 'settings'), * or false to resolve it automatically from the field config. * @param bool $use_default Whether to fall back to the field's configured default * value when no stored value exists. Default true. * @param bool $use_translate Whether to pass the value through multilingual * translation plugins (Polylang, WPML). Default true. * @return array|bool|mixed|string The stored (and optionally translated) value, the field * default, an empty string, or false when the field is * unknown and no page is supplied. */ function cmplz_tc_get_value( $fieldname, $page = false, $use_default = true, $use_translate = true ) { // Return early when no page is given and the field is not registered in config. if ( ! $page && ! isset( COMPLIANZ_TC::$config->fields[ $fieldname ] ) ) { return false; } // Resolve the option source/group from the field definition when not supplied. if ( ! $page ) { $page = COMPLIANZ_TC::$config->fields[ $fieldname ]['source']; } $fields = get_option( 'complianz_tc_options_' . $page ); $default = ( $use_default && $page && isset( COMPLIANZ_TC::$config->fields[ $fieldname ]['default'] ) ) ? COMPLIANZ_TC::$config->fields[ $fieldname ]['default'] : ''; $value = isset( $fields[ $fieldname ] ) ? $fields[ $fieldname ] : $default; // Pass translatable values through active multilingual plugins. if ( $use_translate ) { if ( isset( COMPLIANZ_TC::$config->fields[ $fieldname ]['translatable'] ) && COMPLIANZ_TC::$config->fields[ $fieldname ]['translatable'] ) { if ( function_exists( 'pll__' ) ) { $value = pll__( $value ); } if ( function_exists( 'icl_translate' ) ) { $value = icl_translate( 'complianz', $fieldname, $value ); } /** * Filters a translatable option 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_intro' ) ) { /** * Renders an introductory message panel in the plugin admin UI. * * Outputs a styled `
` 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 = "
{$msg}
"; 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 = "
{$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_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; } }