| 1 |
<?php |
| 2 |
|
| 3 |
namespace WPGraphQL\Mutation; |
| 4 |
|
| 5 |
use GraphQL\Error\UserError; |
| 6 |
use WPGraphQL\AppContext; |
| 7 |
use WPGraphQL\Data\DataSource; |
| 8 |
use WPGraphQL\Registry\TypeRegistry; |
| 9 |
use WPGraphQL\Utils\Utils; |
| 10 |
|
| 11 |
/** |
| 12 |
* Class UpdateSettings |
| 13 |
* |
| 14 |
* @package WPGraphQL\Mutation |
| 15 |
*/ |
| 16 |
class UpdateSettings { |
| 17 |
|
| 18 |
/** |
| 19 |
* Registers the CommentCreate mutation. |
| 20 |
* |
| 21 |
* @param \WPGraphQL\Registry\TypeRegistry $type_registry The WPGraphQL TypeRegistry |
| 22 |
* |
| 23 |
* @return void |
| 24 |
*/ |
| 25 |
public static function register_mutation( TypeRegistry $type_registry ) { |
| 26 |
$output_fields = self::get_output_fields( $type_registry ); |
| 27 |
$input_fields = self::get_input_fields( $type_registry ); |
| 28 |
|
| 29 |
if ( empty( $output_fields ) || empty( $input_fields ) ) { |
| 30 |
return; |
| 31 |
} |
| 32 |
|
| 33 |
register_graphql_mutation( |
| 34 |
'updateSettings', |
| 35 |
[ |
| 36 |
'inputFields' => $input_fields, |
| 37 |
'outputFields' => $output_fields, |
| 38 |
'mutateAndGetPayload' => static function ( $input, AppContext $context ) use ( $type_registry ) { |
| 39 |
$payload = self::mutate_and_get_payload( $input, $type_registry ); |
| 40 |
|
| 41 |
// Options were just written; drop any setting-group models loaded |
| 42 |
// earlier in this request so payload fields resolve fresh values. |
| 43 |
$context->get_loader( 'setting_group' )->clear_all(); |
| 44 |
|
| 45 |
return $payload; |
| 46 |
}, |
| 47 |
] |
| 48 |
); |
| 49 |
} |
| 50 |
|
| 51 |
/** |
| 52 |
* Defines the mutation input field configuration. |
| 53 |
* |
| 54 |
* @param \WPGraphQL\Registry\TypeRegistry $type_registry The WPGraphQL TypeRegistry |
| 55 |
* |
| 56 |
* @return array<string,array<string,mixed>> |
| 57 |
*/ |
| 58 |
public static function get_input_fields( TypeRegistry $type_registry ) { |
| 59 |
$allowed_settings = DataSource::get_allowed_settings( $type_registry ); |
| 60 |
|
| 61 |
$input_fields = []; |
| 62 |
|
| 63 |
if ( ! empty( $allowed_settings ) ) { |
| 64 |
|
| 65 |
/** |
| 66 |
* Loop through the $allowed_settings and build fields |
| 67 |
* for the individual settings |
| 68 |
*/ |
| 69 |
foreach ( $allowed_settings as $setting ) { |
| 70 |
if ( ! isset( $setting['type'] ) || ! $type_registry->get_type( $setting['type'] ) ) { |
| 71 |
continue; |
| 72 |
} |
| 73 |
|
| 74 |
/** |
| 75 |
* Readonly settings are not writable, so they are not added to the |
| 76 |
* mutation input, UNLESS they carry a `graphql_deprecated_input` reason. |
| 77 |
* In that case the input field is kept (marked deprecated) so existing |
| 78 |
* schemas that already exposed it don't break; it still rejects the write |
| 79 |
* at runtime in mutate_and_get_payload(). |
| 80 |
*/ |
| 81 |
$deprecation_reason = ! empty( $setting['graphql_deprecated_input'] ) ? (string) $setting['graphql_deprecated_input'] : null; |
| 82 |
|
| 83 |
if ( ! empty( $setting['graphql_readonly'] ) && null === $deprecation_reason ) { |
| 84 |
continue; |
| 85 |
} |
| 86 |
|
| 87 |
/** |
| 88 |
* The flat (group-prefixed) input field name is derived once in the |
| 89 |
* normalized settings map so the input and payload surfaces agree. |
| 90 |
*/ |
| 91 |
$individual_setting_key = isset( $setting['graphql_settings_field_name'] ) ? (string) $setting['graphql_settings_field_name'] : ''; |
| 92 |
|
| 93 |
if ( empty( $individual_setting_key ) ) { |
| 94 |
continue; |
| 95 |
} |
| 96 |
|
| 97 |
/** |
| 98 |
* Dynamically build the individual setting, |
| 99 |
* then add it to the $input_fields |
| 100 |
*/ |
| 101 |
$input_fields[ $individual_setting_key ] = [ |
| 102 |
'type' => $setting['type'], |
| 103 |
'description' => $setting['description'] ?? null, |
| 104 |
]; |
| 105 |
|
| 106 |
if ( null !== $deprecation_reason ) { |
| 107 |
$input_fields[ $individual_setting_key ]['deprecationReason'] = $deprecation_reason; |
| 108 |
} |
| 109 |
} |
| 110 |
} |
| 111 |
|
| 112 |
return $input_fields; |
| 113 |
} |
| 114 |
|
| 115 |
/** |
| 116 |
* Defines the mutation output field configuration. |
| 117 |
* |
| 118 |
* @param \WPGraphQL\Registry\TypeRegistry $type_registry The WPGraphQL TypeRegistry |
| 119 |
* |
| 120 |
* @return array<string,array<string,mixed>> |
| 121 |
*/ |
| 122 |
public static function get_output_fields( TypeRegistry $type_registry ) { |
| 123 |
|
| 124 |
/** |
| 125 |
* Get the allowed setting groups and their fields |
| 126 |
*/ |
| 127 |
$allowed_setting_groups = DataSource::get_allowed_settings_by_group( $type_registry ); |
| 128 |
|
| 129 |
$output_fields = []; |
| 130 |
|
| 131 |
/** |
| 132 |
* Get all of the settings, regardless of group |
| 133 |
*/ |
| 134 |
$output_fields['allSettings'] = [ |
| 135 |
'type' => 'Settings', |
| 136 |
'description' => static function () { |
| 137 |
return __( 'Update all settings.', 'wp-graphql' ); |
| 138 |
}, |
| 139 |
'resolve' => static function () { |
| 140 |
return true; |
| 141 |
}, |
| 142 |
]; |
| 143 |
|
| 144 |
if ( ! empty( $allowed_setting_groups ) && is_array( $allowed_setting_groups ) ) { |
| 145 |
foreach ( $allowed_setting_groups as $group => $setting_type ) { |
| 146 |
$group_key = DataSource::format_group_name( $group ); |
| 147 |
$setting_type_name = Utils::format_type_name( $group_key . 'Settings' ); |
| 148 |
|
| 149 |
$output_fields[ Utils::format_field_name( $setting_type_name ) ] = [ |
| 150 |
'type' => $setting_type_name, |
| 151 |
// translators: %s is the setting type name |
| 152 |
'description' => static function () use ( $setting_type_name ) { |
| 153 |
// translators: %s is the setting type name |
| 154 |
return sprintf( __( 'Update the %s setting.', 'wp-graphql' ), $setting_type_name ); |
| 155 |
}, |
| 156 |
'resolve' => static function ( $root, array $args, AppContext $context ) use ( $group_key ) { |
| 157 |
return $context->get_loader( 'setting_group' )->load_deferred( $group_key ); |
| 158 |
}, |
| 159 |
]; |
| 160 |
} |
| 161 |
} |
| 162 |
return $output_fields; |
| 163 |
} |
| 164 |
|
| 165 |
/** |
| 166 |
* Defines the mutation data modification closure. |
| 167 |
* |
| 168 |
* @param array<string,mixed> $input The mutation input |
| 169 |
* @param \WPGraphQL\Registry\TypeRegistry $type_registry The WPGraphQL TypeRegistry |
| 170 |
* |
| 171 |
* @return array<string,array<string,mixed>> |
| 172 |
* |
| 173 |
* @throws \GraphQL\Error\UserError |
| 174 |
*/ |
| 175 |
public static function mutate_and_get_payload( array $input, TypeRegistry $type_registry ): array { |
| 176 |
/** |
| 177 |
* Check that the user can manage setting options |
| 178 |
*/ |
| 179 |
if ( ! current_user_can( 'manage_options' ) ) { |
| 180 |
throw new UserError( esc_html__( 'Sorry, you are not allowed to edit settings as this user.', 'wp-graphql' ) ); |
| 181 |
} |
| 182 |
|
| 183 |
/** |
| 184 |
* The $updatable_settings_options will store all of the allowed |
| 185 |
* settings in a WP ready format |
| 186 |
*/ |
| 187 |
$updatable_settings_options = []; |
| 188 |
|
| 189 |
$allowed_settings = DataSource::get_allowed_settings( $type_registry ); |
| 190 |
|
| 191 |
/** |
| 192 |
* Loop through the $allowed_settings and build the insert options array |
| 193 |
*/ |
| 194 |
foreach ( $allowed_settings as $key => $setting ) { |
| 195 |
|
| 196 |
/** |
| 197 |
* The flat input field name is derived once in the normalized settings |
| 198 |
* map, so this matches the name registered on the input type. |
| 199 |
*/ |
| 200 |
$individual_setting_key = isset( $setting['graphql_settings_field_name'] ) ? (string) $setting['graphql_settings_field_name'] : ''; |
| 201 |
|
| 202 |
if ( empty( $individual_setting_key ) ) { |
| 203 |
continue; |
| 204 |
} |
| 205 |
|
| 206 |
/** |
| 207 |
* Dynamically build the individual setting, |
| 208 |
* then add it to $updatable_settings_options |
| 209 |
*/ |
| 210 |
$updatable_settings_options[ Utils::format_field_name( $individual_setting_key ) ] = [ |
| 211 |
'option' => $key, |
| 212 |
'group' => $setting['group'], |
| 213 |
'readonly' => ! empty( $setting['graphql_readonly'] ), |
| 214 |
]; |
| 215 |
} |
| 216 |
|
| 217 |
foreach ( $input as $key => $value ) { |
| 218 |
/** |
| 219 |
* Check to see that the input field exists in settings, if so grab the option |
| 220 |
* name and update the option |
| 221 |
*/ |
| 222 |
if ( ! array_key_exists( $key, $updatable_settings_options ) ) { |
| 223 |
continue; |
| 224 |
} |
| 225 |
|
| 226 |
/** |
| 227 |
* Throw an error if the setting is readonly, e.g. the site url, |
| 228 |
* as we do not want users changing it and breaking all the things |
| 229 |
*/ |
| 230 |
if ( ! empty( $updatable_settings_options[ $key ]['readonly'] ) ) { |
| 231 |
throw new UserError( |
| 232 |
sprintf( |
| 233 |
/* translators: %s is the name of the readonly settings input field. */ |
| 234 |
esc_html__( 'Sorry, the %s setting cannot be changed with this mutation, speak with your site administrator to change it.', 'wp-graphql' ), |
| 235 |
esc_html( $key ) |
| 236 |
) |
| 237 |
); |
| 238 |
} |
| 239 |
|
| 240 |
update_option( $updatable_settings_options[ $key ]['option'], $value ); |
| 241 |
} |
| 242 |
|
| 243 |
return $updatable_settings_options; |
| 244 |
} |
| 245 |
} |
| 246 |
|