Refactor DisplayManager for admin field display rules centralization.

This commit is contained in:
Pedro Silva 2023-09-15 17:32:00 +01:00
parent 93f7e5dca6
commit e31e09f0c7
No known key found for this signature in database
GPG key ID: E2EE20C0669D24B3
20 changed files with 619 additions and 273 deletions

View file

@ -12,7 +12,7 @@ namespace WooCommerce\PayPalCommerce\Applepay;
use WooCommerce\PayPalCommerce\Applepay\Assets\PropertiesDictionary;
use WooCommerce\PayPalCommerce\Onboarding\State;
use WooCommerce\PayPalCommerce\Vendor\Psr\Container\ContainerInterface;
use WooCommerce\PayPalCommerce\WcGateway\Helper\FieldDisplayManager;
use WooCommerce\PayPalCommerce\WcGateway\Helper\DisplayManager;
return array(
@ -25,8 +25,8 @@ return array(
return array_merge( array_slice( $array, 0, $pos ), $new, array_slice( $array, $pos ) );
};
$fields_manager = $container->get( 'wcgateway.field-display-manager' );
assert( $fields_manager instanceof FieldDisplayManager );
$display_manager = $container->get( 'wcgateway.display-manager' );
assert( $display_manager instanceof DisplayManager );
return $insert_after(
$fields,
@ -51,14 +51,14 @@ return array(
'custom_attributes' => array(
'data-ppcp-display' => wp_json_encode(
array(
$fields_manager
$display_manager
->rule()
->condition( 'applepay_button_enabled', 'equals', '1' )
->action( 'applepay_sandbox_validation_file', 'visible' )
->action( 'applepay_live_validation_file', 'visible' )
->action( 'applepay_button_color', 'visible' )
->action( 'applepay_button_type', 'visible' )
->action( 'applepay_button_language', 'visible' )
->condition_element( 'applepay_button_enabled', '1' )
->action_visible( 'applepay_sandbox_validation_file' )
->action_visible( 'applepay_live_validation_file' )
->action_visible( 'applepay_button_color' )
->action_visible( 'applepay_button_type' )
->action_visible( 'applepay_button_language' )
->to_array(),
)
),

View file

@ -12,7 +12,7 @@ namespace WooCommerce\PayPalCommerce\Googlepay;
use WooCommerce\PayPalCommerce\Googlepay\Helper\PropertiesDictionary;
use WooCommerce\PayPalCommerce\Onboarding\State;
use WooCommerce\PayPalCommerce\Vendor\Psr\Container\ContainerInterface;
use WooCommerce\PayPalCommerce\WcGateway\Helper\FieldDisplayManager;
use WooCommerce\PayPalCommerce\WcGateway\Helper\DisplayManager;
return array(
@ -32,8 +32,8 @@ return array(
return array_merge( array_slice( $array, 0, $pos ), $new, array_slice( $array, $pos ) );
};
$fields_manager = $container->get( 'wcgateway.field-display-manager' );
assert( $fields_manager instanceof FieldDisplayManager );
$display_manager = $container->get( 'wcgateway.display-manager' );
assert( $display_manager instanceof DisplayManager );
return $insert_after(
$fields,
@ -58,13 +58,13 @@ return array(
'custom_attributes' => array(
'data-ppcp-display' => wp_json_encode(
array(
$fields_manager
$display_manager
->rule()
->condition( 'googlepay_button_enabled', 'equals', '1' )
->action( 'googlepay_button_type', 'visible' )
->action( 'googlepay_button_color', 'visible' )
->action( 'googlepay_button_language', 'visible' )
->action( 'googlepay_button_shipping_enabled', 'visible' )
->condition_element( 'googlepay_button_enabled', '1' )
->action_visible( 'googlepay_button_type' )
->action_visible( 'googlepay_button_color' )
->action_visible( 'googlepay_button_language' )
->action_visible( 'googlepay_button_shipping_enabled' )
->to_array(),
)
),

View file

@ -1,4 +1,4 @@
import FieldDisplayManager from "./common/FieldDisplayManager";
import DisplayManager from "./common/display-manager/DisplayManager";
import moveWrappedElements from "./common/wrapped-elements";
document.addEventListener(
@ -10,15 +10,19 @@ document.addEventListener(
moveWrappedElements();
}, 0);
// Initialize FieldDisplayManager.
const fieldDisplayManager = new FieldDisplayManager();
// Initialize DisplayManager.
const displayManager = new DisplayManager();
jQuery( '*[data-ppcp-display]' ).each( (index, el) => {
const rules = jQuery(el).data('ppcpDisplay');
console.log('rules', rules);
for (const rule of rules) {
fieldDisplayManager.addRule(rule);
displayManager.addRule(rule);
}
});
displayManager.register();
}
);

View file

@ -1,74 +0,0 @@
class FieldDisplayManager {
constructor() {
this.rules = [];
document.ppcpDisplayManagerLog = () => {
console.log('rules', this.rules);
}
}
addRule(rule) {
this.rules.push(rule);
for (const condition of rule.conditions) {
jQuery(document).on('change', condition.selector, () => {
this.updateElementsVisibility(condition, rule);
});
this.updateElementsVisibility(condition, rule);
}
}
updateElementsVisibility(condition, rule) {
let value = this.getValue(condition.selector);
value = (value !== null ? value.toString() : value);
if (condition.value === value) {
for (const action of rule.actions) {
if (action.action === 'visible') {
jQuery(action.selector).removeClass('ppcp-field-hidden');
}
if (action.action === 'enable') {
jQuery(action.selector).removeClass('ppcp-field-disabled')
.off('mouseup')
.find('> *')
.css('pointer-events', '');
}
}
} else {
for (const action of rule.actions) {
if (action.action === 'visible') {
jQuery(action.selector).addClass('ppcp-field-hidden');
}
if (action.action === 'enable') {
jQuery(action.selector).addClass('ppcp-field-disabled')
.on('mouseup', function(event) {
event.stopImmediatePropagation();
})
.find('> *')
.css('pointer-events', 'none');
}
}
}
}
getValue(element) {
const $el = jQuery(element);
if ($el.is(':checkbox') || $el.is(':radio')) {
if ($el.is(':checked')) {
return $el.val();
} else {
return null;
}
} else {
return $el.val();
}
}
}
export default FieldDisplayManager;

View file

@ -0,0 +1,14 @@
import ElementAction from "./action/ElementAction";
class ActionFactory {
static make(actionConfig) {
switch (actionConfig.type) {
case 'element':
return new ElementAction(actionConfig);
}
throw new Error('[ActionFactory] Unknown action: ' + actionConfig.type);
}
}
export default ActionFactory;

View file

@ -0,0 +1,17 @@
import ElementCondition from "./condition/ElementCondition";
import BoolCondition from "./condition/BoolCondition";
class ConditionFactory {
static make(conditionConfig, triggerUpdate) {
switch (conditionConfig.type) {
case 'element':
return new ElementCondition(conditionConfig, triggerUpdate);
case 'bool':
return new BoolCondition(conditionConfig, triggerUpdate);
}
throw new Error('[ConditionFactory] Unknown condition: ' + conditionConfig.type);
}
}
export default ConditionFactory;

View file

@ -0,0 +1,32 @@
import Rule from "./Rule";
class DisplayManager {
constructor() {
this.rules = {};
this.ruleStatus = {}; // The current status for each rule. Maybe not necessary, for now just for logging.
document.ppcpDisplayManagerLog = () => {
console.log('DisplayManager', this);
}
}
addRule(ruleConfig) {
const updateStatus = () => {
this.ruleStatus[ruleConfig.key] = this.rules[ruleConfig.key].status;
console.log('ruleStatus', this.ruleStatus);
}
this.rules[ruleConfig.key] = new Rule(ruleConfig, updateStatus.bind(this));
console.log('Rule', this.rules[ruleConfig.key]);
}
register() {
this.ruleStatus = {};
for (const [key, rule] of Object.entries(this.rules)) {
rule.register();
}
}
}
export default DisplayManager;

View file

@ -0,0 +1,65 @@
import ConditionFactory from "./ConditionFactory";
import ActionFactory from "./ActionFactory";
class Rule {
constructor(config, triggerUpdate) {
this.config = config;
this.conditions = {};
this.actions = {};
this.triggerUpdate = triggerUpdate;
const updateStatus = this.updateStatus.bind(this);
for (const conditionConfig of this.config.conditions) {
const condition = ConditionFactory.make(conditionConfig, updateStatus);
this.conditions[condition.key] = condition;
console.log('Condition', condition);
}
for (const actionConfig of this.config.actions) {
const action = ActionFactory.make(actionConfig);
this.actions[action.key] = action;
console.log('Action', action);
}
}
get key() {
return this.config.key;
}
updateStatus() {
let status = true;
for (const [key, condition] of Object.entries(this.conditions)) {
status &= condition.status;
}
if (status !== this.status) {
this.status = status;
this.triggerUpdate();
this.runActions();
}
}
runActions() {
for (const [key, action] of Object.entries(this.actions)) {
action.run(this.status);
}
}
register() {
for (const [key, condition] of Object.entries(this.conditions)) {
condition.register(this.updateStatus.bind(this));
}
for (const [key, action] of Object.entries(this.actions)) {
action.register();
}
this.updateStatus();
}
}
export default Rule;

View file

@ -0,0 +1,21 @@
class BaseAction {
constructor(config) {
this.config = config;
}
get key() {
return this.config.key;
}
register() {
// To override.
}
run(status) {
// To override.
}
}
export default BaseAction;

View file

@ -0,0 +1,35 @@
import BaseAction from "./BaseAction";
class ElementAction extends BaseAction {
run(status) {
if (status) {
if (this.config.action === 'visible') {
jQuery(this.config.selector).removeClass('ppcp-field-hidden');
}
if (this.config.action === 'enable') {
jQuery(this.config.selector).removeClass('ppcp-field-disabled')
.off('mouseup')
.find('> *')
.css('pointer-events', '');
}
} else {
if (this.config.action === 'visible') {
jQuery(this.config.selector).addClass('ppcp-field-hidden');
}
if (this.config.action === 'enable') {
jQuery(this.config.selector).addClass('ppcp-field-disabled')
.on('mouseup', function(event) {
event.stopImmediatePropagation();
})
.find('> *')
.css('pointer-events', 'none');
}
}
}
}
export default ElementAction;

View file

@ -0,0 +1,19 @@
class BaseCondition {
constructor(config, triggerUpdate) {
this.config = config;
this.status = false;
this.triggerUpdate = triggerUpdate;
}
get key() {
return this.config.key;
}
register() {
// To override.
}
}
export default BaseCondition;

View file

@ -0,0 +1,15 @@
import BaseCondition from "./BaseCondition";
class BoolCondition extends BaseCondition {
register() {
this.status = this.check();
}
check() {
return !! this.config.value;
}
}
export default BoolCondition;

View file

@ -0,0 +1,27 @@
import BaseCondition from "./BaseCondition";
import {inputValue} from "../../../helper/form";
class ElementCondition extends BaseCondition {
register() {
jQuery(document).on('change', this.config.selector, () => {
const status = this.check();
if (status !== this.status) {
this.status = status;
this.triggerUpdate();
}
});
this.status = this.check();
}
check() {
let value = inputValue(this.config.selector);
value = (value !== null ? value.toString() : value);
return this.config.value === value;
}
}
export default ElementCondition;

View file

@ -0,0 +1,13 @@
export const inputValue = (element) => {
const $el = jQuery(element);
if ($el.is(':checkbox') || $el.is(':radio')) {
if ($el.is(':checked')) {
return $el.val();
} else {
return null;
}
} else {
return $el.val();
}
}

View file

@ -48,7 +48,7 @@ use WooCommerce\PayPalCommerce\WcGateway\Gateway\PayUponInvoice\PayUponInvoiceGa
use WooCommerce\PayPalCommerce\WcGateway\Gateway\TransactionUrlProvider;
use WooCommerce\PayPalCommerce\WcGateway\Helper\CheckoutHelper;
use WooCommerce\PayPalCommerce\WcGateway\Helper\DCCProductStatus;
use WooCommerce\PayPalCommerce\WcGateway\Helper\FieldDisplayManager;
use WooCommerce\PayPalCommerce\WcGateway\Helper\DisplayManager;
use WooCommerce\PayPalCommerce\WcGateway\Helper\PayUponInvoiceHelper;
use WooCommerce\PayPalCommerce\WcGateway\Helper\PayUponInvoiceProductStatus;
use WooCommerce\PayPalCommerce\WcGateway\Helper\RefundFeesUpdater;
@ -1417,9 +1417,10 @@ return array(
$container->get( 'wcgateway.settings' )
);
},
'wcgateway.field-display-manager' => SingletonDecorator::make(
static function( ContainerInterface $container ): FieldDisplayManager {
return new FieldDisplayManager();
'wcgateway.display-manager' => SingletonDecorator::make(
static function( ContainerInterface $container ): DisplayManager {
$settings = $container->get( 'wcgateway.settings' );
return new DisplayManager( $settings );
}
),
);

View file

@ -0,0 +1,60 @@
<?php
/**
* Helper to manage the field display behaviour.
*
* @package WooCommerce\PayPalCommerce\WcGateway\Helper;
*/
declare(strict_types=1);
namespace WooCommerce\PayPalCommerce\WcGateway\Helper;
use WooCommerce\PayPalCommerce\WcGateway\Settings\Settings;
/**
* DisplayManager class.
*/
class DisplayManager {
/**
* The settings.
*
* @var Settings
*/
private $settings;
/**
* The rules.
*
* @var array
*/
protected $rules = array();
/**
* FieldDisplayManager constructor.
*
* @param Settings $settings The settings.
* @return void
*/
public function __construct( Settings $settings ) {
$this->settings = $settings;
}
/**
* Creates and returns a rule.
*
* @param string|null $key The rule key.
* @return DisplayRule
*/
public function rule( string $key = null ): DisplayRule {
if ( null === $key ) {
$key = '_rule_' . ( (string) count( $this->rules ) );
}
$rule = new DisplayRule( $key, $this->settings );
$this->rules[ $key ] = $rule;
return $rule;
}
}

View file

@ -0,0 +1,262 @@
<?php
/**
* Element used by field display manager.
*
* @package WooCommerce\PayPalCommerce\WcGateway\Helper;
*/
declare(strict_types=1);
namespace WooCommerce\PayPalCommerce\WcGateway\Helper;
use WooCommerce\PayPalCommerce\WcGateway\Settings\Settings;
/**
* DisplayRule class.
*/
class DisplayRule {
const CONDITION_TYPE_ELEMENT = 'element';
const CONDITION_TYPE_BOOL = 'bool';
const CONDITION_OPERATION_EQUALS = 'equals';
const CONDITION_OPERATION_NOT_EQUALS = 'not_equals';
const CONDITION_OPERATION_IN = 'in';
const CONDITION_OPERATION_NOT_IN = 'not_in';
const CONDITION_OPERATION_EMPTY = 'empty';
const CONDITION_OPERATION_NOT_EMPTY = 'not_empty';
const ACTION_TYPE_ELEMENT = 'element';
const ACTION_VISIBLE = 'visible';
const ACTION_ENABLE = 'enable';
/**
* The element selector.
*
* @var string
*/
protected $key;
/**
* The settings.
*
* @var Settings
*/
private $settings;
/**
* The conditions of this rule.
*
* @var array
*/
protected $conditions = array();
/**
* The actions of this rule.
*
* @var array
*/
protected $actions = array();
/**
* Indicates if this class should add selector prefixes.
*
* @var bool
*/
protected $add_selector_prefixes = true;
/**
* FieldDisplayElement constructor.
*
* @param string $key The rule key.
* @param Settings $settings The settings.
*/
public function __construct( string $key, Settings $settings ) {
$this->key = $key;
$this->settings = $settings;
}
/**
* Adds a condition related to an HTML element.
*
* @param string $selector The condition selector.
* @param mixed $value The value to compare against.
* @param string $operation The condition operation (ex: equals, differs, in, not_empty, empty).
* @return self
*/
public function condition_element( string $selector, $value, string $operation = self::CONDITION_OPERATION_EQUALS ): self {
$this->add_condition(
array(
'type' => self::CONDITION_TYPE_ELEMENT,
'selector' => $selector,
'operation' => $operation,
'value' => $value,
)
);
return $this;
}
/**
* Adds a condition related to a bool check.
*
* @param bool $value The value to enable / disable the condition.
* @return self
*/
public function condition_is_true( bool $value ): self {
$this->add_condition(
array(
'type' => self::CONDITION_TYPE_BOOL,
'value' => $value,
)
);
return $this;
}
/**
* Adds a condition related to the settings.
*
* @param string $settings_key The settings key.
* @param mixed $value The value to compare against.
* @param string $operation The condition operation (ex: equals, differs, in, not_empty, empty).
* @return self
*/
public function condition_is_settings( string $settings_key, $value, string $operation = self::CONDITION_OPERATION_EQUALS ): self {
$settings_value = null;
if ( $this->settings->has( $settings_key ) ) {
$settings_value = $this->settings->get( $settings_key );
}
$this->condition_is_true( $this->resolve_operation( $settings_value, $value, $operation ) );
return $this;
}
/**
* Adds a condition to show/hide the element.
*
* @param string $selector The condition selector.
*/
public function action_visible( string $selector ): self {
$this->add_action(
array(
'type' => self::ACTION_TYPE_ELEMENT,
'selector' => $selector,
'action' => self::ACTION_VISIBLE,
)
);
return $this;
}
/**
* Adds a condition to enable/disable the element.
*
* @param string $selector The condition selector.
*/
public function action_enable( string $selector ): self {
$this->add_action(
array(
'type' => self::ACTION_TYPE_ELEMENT,
'selector' => $selector,
'action' => self::ACTION_ENABLE,
)
);
return $this;
}
/**
* Adds a condition to the rule.
*
* @param array $options The condition options.
* @return void
*/
private function add_condition( array $options ): void {
if ( $this->add_selector_prefixes && isset( $options['selector'] ) ) {
$options['selector'] = '#ppcp-' . $options['selector']; // Refers to the input.
}
if ( ! isset( $options['key'] ) ) {
$options['key'] = '_condition_' . ( (string) count( $this->conditions ) );
}
$this->conditions[] = $options;
}
/**
* Adds an action to do.
*
* @param array $options The action options.
* @return void
*/
private function add_action( array $options ): void {
if ( $this->add_selector_prefixes && isset( $options['selector'] ) ) {
$options['selector'] = '#field-' . $options['selector']; // Refers to the whole field.
}
if ( ! isset( $options['key'] ) ) {
$options['key'] = '_action_' . ( (string) count( $this->actions ) );
}
$this->actions[] = $options;
}
/**
* Set if selector prefixes like, "#ppcp-" or "#field-" should be added to condition or action selectors.
*
* @param bool $add_selector_prefixes If should add prefixes.
* @return self
*/
public function should_add_selector_prefixes( bool $add_selector_prefixes = true ): self {
$this->add_selector_prefixes = $add_selector_prefixes;
return $this;
}
/**
* Adds a condition related to the settings.
*
* @param mixed $value_1 The value 1.
* @param mixed $value_2 The value 2.
* @param string $operation The condition operation (ex: equals, differs, in, not_empty, empty).
* @return bool
*/
private function resolve_operation( $value_1, $value_2, string $operation ): bool {
switch ( $operation ) {
case self::CONDITION_OPERATION_EQUALS:
return $value_1 === $value_2;
case self::CONDITION_OPERATION_NOT_EQUALS:
return $value_1 !== $value_2;
case self::CONDITION_OPERATION_IN:
return in_array( $value_1, $value_2, true );
case self::CONDITION_OPERATION_NOT_IN:
return ! in_array( $value_1, $value_2, true );
case self::CONDITION_OPERATION_EMPTY:
return empty( $value_1 );
case self::CONDITION_OPERATION_NOT_EMPTY:
return ! empty( $value_1 );
}
return false;
}
/**
* Returns array representation.
*
* @return array
*/
public function to_array(): array {
return array(
'key' => $this->key,
'conditions' => $this->conditions,
'actions' => $this->actions,
);
}
/**
* Returns JSON representation.
*
* @return string
*/
public function json(): string {
return wp_json_encode( $this->to_array() ) ?: '';
}
}

View file

@ -1,41 +0,0 @@
<?php
/**
* Helper to manage the field display behaviour.
*
* @package WooCommerce\PayPalCommerce\WcGateway\Helper;
*/
declare(strict_types=1);
namespace WooCommerce\PayPalCommerce\WcGateway\Helper;
/**
* FieldsManager class.
*/
class FieldDisplayManager {
/**
* The rules.
*
* @var array
*/
protected $rules = array();
/**
* Creates and returns a rule.
*
* @param string|null $key The rule key.
* @return FieldDisplayRule
*/
public function rule( string $key = null ): FieldDisplayRule {
if ( null === $key ) {
$key = (string) count( $this->rules );
}
$rule = new FieldDisplayRule( $key );
$this->rules[ $key ] = $rule;
return $rule;
}
}

View file

@ -1,124 +0,0 @@
<?php
/**
* Element used by field display manager.
*
* @package WooCommerce\PayPalCommerce\WcGateway\Helper;
*/
declare(strict_types=1);
namespace WooCommerce\PayPalCommerce\WcGateway\Helper;
/**
* FieldDisplayRule class.
*/
class FieldDisplayRule {
/**
* The element selector.
*
* @var string
*/
protected $key;
/**
* The conditions of this rule.
*
* @var array
*/
protected $conditions = array();
/**
* The actions of this rule.
*
* @var array
*/
protected $actions = array();
/**
* Indicates if this class should add selector prefixes.
*
* @var bool
*/
protected $add_selector_prefixes = true;
/**
* FieldDisplayElement constructor.
*
* @param string $key The rule key.
*/
public function __construct( string $key ) {
$this->key = $key;
}
/**
* Adds a condition to the rule.
*
* @param string $selector The condition selector.
* @param string $operation The condition operation (ex: equals, differs, in, not_empty, empty).
* @param mixed $value The value to compare against.
* @return self
*/
public function condition( string $selector, string $operation, $value ): self {
if ( $this->add_selector_prefixes ) {
$selector = '#ppcp-' . $selector; // Refers to the input.
}
$this->conditions[] = array(
'selector' => $selector,
'operation' => $operation,
'value' => $value,
);
return $this;
}
/**
* Adds a condition to enable the element.
*
* @param string $selector The condition selector.
* @param string $action The action.
*/
public function action( string $selector, string $action ): self {
if ( $this->add_selector_prefixes ) {
$selector = '#field-' . $selector; // Refers to the whole field.
}
$this->actions[] = array(
'selector' => $selector,
'action' => $action,
);
return $this;
}
/**
* Set if selector prefixes like, "#ppcp-" or "#field-" should be added to condition or action selectors.
*
* @param bool $add_selector_prefixes If should add prefixes.
* @return self
*/
public function should_add_selector_prefixes( bool $add_selector_prefixes = true ): self {
$this->add_selector_prefixes = $add_selector_prefixes;
return $this;
}
/**
* Returns array representation.
*
* @return array
*/
public function to_array(): array {
return array(
'key' => $this->key,
'conditions' => $this->conditions,
'actions' => $this->actions,
);
}
/**
* Returns JSON representation.
*
* @return string
*/
public function json(): string {
return wp_json_encode( $this->to_array() ) ?: '';
}
}

View file

@ -17,7 +17,7 @@ use WooCommerce\PayPalCommerce\ApiClient\Helper\DccApplies;
use WooCommerce\PayPalCommerce\Onboarding\Environment;
use WooCommerce\PayPalCommerce\Onboarding\Render\OnboardingOptionsRenderer;
use WooCommerce\PayPalCommerce\Onboarding\State;
use WooCommerce\PayPalCommerce\WcGateway\Helper\FieldDisplayManager;
use WooCommerce\PayPalCommerce\WcGateway\Helper\DisplayManager;
return function ( ContainerInterface $container, array $fields ): array {
@ -40,8 +40,8 @@ return function ( ContainerInterface $container, array $fields ): array {
$module_url = $container->get( 'wcgateway.url' );
$fields_manager = $container->get( 'wcgateway.field-display-manager' );
assert( $fields_manager instanceof FieldDisplayManager );
$display_manager = $container->get( 'wcgateway.display-manager' );
assert( $display_manager instanceof DisplayManager );
$connection_fields = array(
'ppcp_onboarading_header' => array(
@ -510,10 +510,10 @@ return function ( ContainerInterface $container, array $fields ): array {
'custom_attributes' => array(
'data-ppcp-display' => wp_json_encode(
array(
$fields_manager
$display_manager
->rule()
->condition( 'subtotal_mismatch_behavior', 'equals', PurchaseUnitSanitizer::MODE_EXTRA_LINE )
->action( 'subtotal_mismatch_line_name', 'visible' )
->condition_element( 'subtotal_mismatch_behavior', PurchaseUnitSanitizer::MODE_EXTRA_LINE )
->action_visible( 'subtotal_mismatch_line_name' )
->to_array(),
)
),