| 1 |
<?php |
| 2 |
/** |
| 3 |
* Closed product snapshot collector. |
| 4 |
* |
| 5 |
* @package signls-sdk |
| 6 |
* @license GPL-3.0-or-later |
| 7 |
*/ |
| 8 |
|
| 9 |
namespace Signls\Sdk\V1; |
| 10 |
|
| 11 |
defined( 'ABSPATH' ) || exit; |
| 12 |
|
| 13 |
final class Collector { |
| 14 |
|
| 15 |
private const FAILURE_CLASSES = array( |
| 16 |
'transport_dns', |
| 17 |
'transport_tls', |
| 18 |
'transport_connect', |
| 19 |
'transport_timeout', |
| 20 |
'http_4xx', |
| 21 |
'http_429', |
| 22 |
'http_5xx', |
| 23 |
'auth', |
| 24 |
'configuration', |
| 25 |
'validation', |
| 26 |
'remote_rejected', |
| 27 |
'unknown', |
| 28 |
); |
| 29 |
|
| 30 |
public static function collect( ProductAdapterInterface $adapter ): array { |
| 31 |
$contract = $adapter->contract(); |
| 32 |
$source = $adapter->snapshot(); |
| 33 |
$allowed = self::allowlists( $contract ); |
| 34 |
|
| 35 |
$payload = array( |
| 36 |
'versions' => self::versions( isset( $source['versions'] ) ? $source['versions'] : array(), $adapter ), |
| 37 |
'is_multisite' => Sanitizer::bool( isset( $source['is_multisite'] ) ? $source['is_multisite'] : false ), |
| 38 |
'configured_units' => Sanitizer::counter( isset( $source['configured_units'] ) ? $source['configured_units'] : 0 ), |
| 39 |
'active_units' => Sanitizer::counter( isset( $source['active_units'] ) ? $source['active_units'] : 0 ), |
| 40 |
'integrations' => self::integrations( isset( $source['integrations'] ) ? $source['integrations'] : array(), $allowed['integrations'] ), |
| 41 |
'features' => self::features( isset( $source['features'] ) ? $source['features'] : array(), $allowed['features'] ), |
| 42 |
'operation_health' => self::operations( isset( $source['operation_health'] ) ? $source['operation_health'] : array(), $allowed ), |
| 43 |
'companions' => self::companions( isset( $source['companions'] ) ? $source['companions'] : array(), $allowed['companions'] ), |
| 44 |
); |
| 45 |
$schema_version = isset( $contract['snapshot_schema_version'] ) ? (int) $contract['snapshot_schema_version'] : 1; |
| 46 |
if ( 2 === $schema_version ) { |
| 47 |
$observation_source = isset( $source['observations'] ) && is_array( $source['observations'] ) ? $source['observations'] : array(); |
| 48 |
$observation_schema = isset( $contract['observation_schema'] ) && is_array( $contract['observation_schema'] ) ? $contract['observation_schema'] : array(); |
| 49 |
$payload['observations'] = ObservationSanitizer::sanitize( $observation_source, $observation_schema ); |
| 50 |
} |
| 51 |
|
| 52 |
$payload['active_units'] = min( $payload['active_units'], $payload['configured_units'] ); |
| 53 |
return $payload; |
| 54 |
} |
| 55 |
|
| 56 |
private static function allowlists( array $contract ): array { |
| 57 |
$result = array(); |
| 58 |
foreach ( array( 'integrations', 'features', 'operations', 'companions' ) as $key ) { |
| 59 |
$result[ $key ] = array(); |
| 60 |
$values = isset( $contract[ $key ] ) && is_array( $contract[ $key ] ) ? $contract[ $key ] : array(); |
| 61 |
foreach ( $values as $value ) { |
| 62 |
$slug = Sanitizer::slug( $value ); |
| 63 |
if ( '' !== $slug ) { |
| 64 |
$result[ $key ][ $slug ] = true; |
| 65 |
} |
| 66 |
} |
| 67 |
} |
| 68 |
return $result; |
| 69 |
} |
| 70 |
|
| 71 |
private static function versions( $source, ProductAdapterInterface $adapter ): array { |
| 72 |
$source = is_array( $source ) ? $source : array(); |
| 73 |
return array( |
| 74 |
'product' => Sanitizer::version( $adapter->product_version() ), |
| 75 |
'wordpress' => Sanitizer::version( isset( $source['wordpress'] ) ? $source['wordpress'] : get_bloginfo( 'version' ) ), |
| 76 |
'php' => Sanitizer::version( PHP_VERSION ), |
| 77 |
'cf7' => Sanitizer::version( isset( $source['cf7'] ) ? $source['cf7'] : 'unknown' ), |
| 78 |
); |
| 79 |
} |
| 80 |
|
| 81 |
private static function integrations( $rows, array $allowlist ): array { |
| 82 |
$result = array(); |
| 83 |
$rows = is_array( $rows ) ? $rows : array(); |
| 84 |
foreach ( $rows as $row ) { |
| 85 |
if ( ! is_array( $row ) ) { |
| 86 |
continue; |
| 87 |
} |
| 88 |
$slug = Sanitizer::slug( isset( $row['slug'] ) ? $row['slug'] : '' ); |
| 89 |
if ( '' === $slug || ! isset( $allowlist[ $slug ] ) || isset( $result[ $slug ] ) ) { |
| 90 |
continue; |
| 91 |
} |
| 92 |
$clean = array( 'slug' => $slug ); |
| 93 |
foreach ( array( 'configured_units', 'credential_units', 'oauth_units', 'api_key_units', 'destination_units', 'mapping_count', 'attempts', 'successes', 'failures' ) as $key ) { |
| 94 |
$clean[ $key ] = Sanitizer::counter( isset( $row[ $key ] ) ? $row[ $key ] : 0 ); |
| 95 |
} |
| 96 |
$result[ $slug ] = $clean; |
| 97 |
} |
| 98 |
return array_values( $result ); |
| 99 |
} |
| 100 |
|
| 101 |
private static function features( $rows, array $allowlist ): array { |
| 102 |
$result = array(); |
| 103 |
foreach ( is_array( $rows ) ? $rows : array() as $row ) { |
| 104 |
if ( ! is_array( $row ) ) { |
| 105 |
continue; |
| 106 |
} |
| 107 |
$slug = Sanitizer::slug( isset( $row['slug'] ) ? $row['slug'] : '' ); |
| 108 |
if ( '' === $slug || ! isset( $allowlist[ $slug ] ) || isset( $result[ $slug ] ) ) { |
| 109 |
continue; |
| 110 |
} |
| 111 |
$result[ $slug ] = array( |
| 112 |
'slug' => $slug, |
| 113 |
'configured_units' => Sanitizer::counter( isset( $row['configured_units'] ) ? $row['configured_units'] : 0 ), |
| 114 |
'source' => Sanitizer::slug( isset( $row['source'] ) ? $row['source'] : '', 48, 'unknown' ), |
| 115 |
); |
| 116 |
} |
| 117 |
return array_values( $result ); |
| 118 |
} |
| 119 |
|
| 120 |
private static function operations( $rows, array $allowlists ): array { |
| 121 |
$result = array(); |
| 122 |
foreach ( is_array( $rows ) ? $rows : array() as $row ) { |
| 123 |
if ( ! is_array( $row ) ) { |
| 124 |
continue; |
| 125 |
} |
| 126 |
$integration = Sanitizer::slug( isset( $row['integration'] ) ? $row['integration'] : '' ); |
| 127 |
$operation = Sanitizer::slug( isset( $row['operation'] ) ? $row['operation'] : '' ); |
| 128 |
$key = $integration . '|' . $operation; |
| 129 |
if ( ! isset( $allowlists['integrations'][ $integration ], $allowlists['operations'][ $operation ] ) || isset( $result[ $key ] ) ) { |
| 130 |
continue; |
| 131 |
} |
| 132 |
$classes = array(); |
| 133 |
$source = isset( $row['failure_classes'] ) && is_array( $row['failure_classes'] ) ? $row['failure_classes'] : array(); |
| 134 |
foreach ( self::FAILURE_CLASSES as $class ) { |
| 135 |
$classes[ $class ] = Sanitizer::counter( isset( $source[ $class ] ) ? $source[ $class ] : 0 ); |
| 136 |
} |
| 137 |
$result[ $key ] = array( |
| 138 |
'integration' => $integration, |
| 139 |
'operation' => $operation, |
| 140 |
'last_success_age' => Sanitizer::age( isset( $row['last_success_age'] ) ? $row['last_success_age'] : null ), |
| 141 |
'last_failure_age' => Sanitizer::age( isset( $row['last_failure_age'] ) ? $row['last_failure_age'] : null ), |
| 142 |
'failure_classes' => $classes, |
| 143 |
); |
| 144 |
} |
| 145 |
return array_values( $result ); |
| 146 |
} |
| 147 |
|
| 148 |
private static function companions( $rows, array $allowlist ): array { |
| 149 |
$result = array(); |
| 150 |
$states = array( 'active', 'expired', 'inactive', 'legacy_activated', 'not_present', 'unknown' ); |
| 151 |
foreach ( is_array( $rows ) ? $rows : array() as $row ) { |
| 152 |
if ( ! is_array( $row ) ) { |
| 153 |
continue; |
| 154 |
} |
| 155 |
$slug = Sanitizer::slug( isset( $row['slug'] ) ? $row['slug'] : '' ); |
| 156 |
$state = Sanitizer::slug( isset( $row['license_state'] ) ? $row['license_state'] : '', 48, 'unknown' ); |
| 157 |
if ( '' === $slug || ! isset( $allowlist[ $slug ] ) || isset( $result[ $slug ] ) ) { |
| 158 |
continue; |
| 159 |
} |
| 160 |
$result[ $slug ] = array( |
| 161 |
'slug' => $slug, |
| 162 |
'installed' => Sanitizer::bool( isset( $row['installed'] ) ? $row['installed'] : false ), |
| 163 |
'active' => Sanitizer::bool( isset( $row['active'] ) ? $row['active'] : false ), |
| 164 |
'version' => Sanitizer::version( isset( $row['version'] ) ? $row['version'] : 'unknown' ), |
| 165 |
'license_state' => in_array( $state, $states, true ) ? $state : 'unknown', |
| 166 |
'source' => Sanitizer::slug( isset( $row['source'] ) ? $row['source'] : '', 48, 'unknown' ), |
| 167 |
'observation_started_at' => Sanitizer::counter( isset( $row['observation_started_at'] ) ? $row['observation_started_at'] : time() ), |
| 168 |
); |
| 169 |
} |
| 170 |
return array_values( $result ); |
| 171 |
} |
| 172 |
} |
| 173 |
|