| @@ -10,8 +10,9 @@ | ||
| 10 | 10 | |
| 11 | 11 | namespace Parsely\UI; |
| 12 | 12 | |
| 13 | 13 | use Parsely\Content_Helper\Excerpt_Suggestions; |
| 14 | +use Parsely\Content_Helper\Suggestion_Defaults; | |
| 14 | 15 | use Parsely\Parsely; |
| 15 | 16 | use Parsely\Permissions; |
| 16 | 17 | use Parsely\Utils\Utils; |
| 17 | 18 | use Parsely\Validator; |
| @@ -77,9 +78,12 @@ | ||
| 77 | 78 | * } |
| 78 | 79 | * |
| 79 | 80 | * @phpstan-type Parsely_Settings_Options_Content_Helper_Feature array{ |
| 80 | 81 | * enabled?: bool, |
| 81 | - * allowed_user_roles?: array<string, string>|array<string, bool> | |
| 82 | + * allowed_user_roles?: array<string, string>|array<string, bool>, | |
| 83 | + * default_length?: mixed, | |
| 84 | + * default_tone?: mixed, | |
| 85 | + * default_persona?: mixed | |
| 82 | 86 | * } |
| 83 | 87 | * |
| 84 | 88 | * @phpstan-import-type Parsely_Options from Parsely |
| 85 | 89 | * @phpstan-import-type Parsely_Options_Headline_Testing from Parsely |
| @@ -110,8 +114,21 @@ | ||
| 110 | 114 | */ |
| 111 | 115 | private $managed_options_badge = array(); |
| 112 | 116 | |
| 113 | 117 | /** |
| 118 | + * The Content Intelligence features that expose site-wide generation | |
| 119 | + * defaults in the settings page. | |
| 120 | + * | |
| 121 | + * @since 3.24.0 | |
| 122 | + * | |
| 123 | + * @var string[] | |
| 124 | + */ | |
| 125 | + private const FEATURES_WITH_DEFAULTS = array( | |
| 126 | + 'title_suggestions', | |
| 127 | + 'excerpt_suggestions', | |
| 128 | + ); | |
| 129 | + | |
| 130 | + /** | |
| 114 | 131 | * The Content Intelligence features that can be configured in the settings |
| 115 | 132 | * page. |
| 116 | 133 | * |
| 117 | 134 | * @since 3.16.0 |
| @@ -1121,12 +1138,110 @@ | ||
| 1121 | 1138 | } |
| 1122 | 1139 | |
| 1123 | 1140 | echo '</fieldset>'; |
| 1124 | 1141 | |
| 1142 | + if ( in_array( $feature_id, self::FEATURES_WITH_DEFAULTS, true ) ) { | |
| 1143 | + $this->print_suggestion_defaults( $args, $options, $feature_id ); | |
| 1144 | + } | |
| 1145 | + | |
| 1125 | 1146 | $this->print_filter_text( $args ); |
| 1126 | 1147 | } |
| 1127 | 1148 | |
| 1128 | 1149 | /** |
| 1150 | + * Prints out a feature's site-wide generation defaults. | |
| 1151 | + * | |
| 1152 | + * These apply to users who have not yet set their own values in the editor. | |
| 1153 | + * The desired length is printed for Excerpt Suggestions only, as Title | |
| 1154 | + * Suggestions has no equivalent setting. | |
| 1155 | + * | |
| 1156 | + * @since 3.24.0 | |
| 1157 | + * | |
| 1158 | + * @param Setting_Arguments $args The arguments for the fieldset. | |
| 1159 | + * @param Parsely_Options $options The plugin's options. | |
| 1160 | + * @param string $feature_id The feature's name. | |
| 1161 | + */ | |
| 1162 | + private function print_suggestion_defaults( $args, $options, string $feature_id ): void { | |
| 1163 | + /** @var array<string, mixed> $feature_options */ | |
| 1164 | + $feature_options = $options['content_helper'][ $feature_id ] ?? array(); | |
| 1165 | + $is_managed = key_exists( 'content_helper', $this->parsely->managed_options ); | |
| 1166 | + | |
| 1167 | + echo '<fieldset class="', esc_attr( str_replace( '_', '-', $feature_id ) ), '-defaults"', | |
| 1168 | + $is_managed ? ' disabled>' : '>'; | |
| 1169 | + echo '<legend class="screen-reader-text"><span>'; | |
| 1170 | + printf( | |
| 1171 | + /* translators: %s: Feature name */ | |
| 1172 | + esc_html__( '%s Default Settings', 'wp-parsely' ), | |
| 1173 | + esc_html( $args['legend'] ?? __( 'Feature', 'wp-parsely' ) ) | |
| 1174 | + ); | |
| 1175 | + echo '</span></legend>'; | |
| 1176 | + | |
| 1177 | + if ( Excerpt_Suggestions::get_feature_name() === $feature_id ) { | |
| 1178 | + $length_key = $args['option_key'] . '[default_length]'; | |
| 1179 | + printf( | |
| 1180 | + '<p><label for="%1$s">%2$s</label><br />' . | |
| 1181 | + '<input type="number" name="%3$s" id="%1$s" value="%4$d" min="%5$d" max="%6$d" step="1" class="small-text" /></p>', | |
| 1182 | + esc_attr( $length_key ), | |
| 1183 | + esc_html__( 'Default length (characters)', 'wp-parsely' ), | |
| 1184 | + esc_attr( $this->get_html_name_attribute( $length_key ) ), | |
| 1185 | + absint( Suggestion_Defaults::get_default_length( $feature_options ) ), | |
| 1186 | + absint( Suggestion_Defaults::MIN_LENGTH ), | |
| 1187 | + absint( Suggestion_Defaults::MAX_LENGTH ) | |
| 1188 | + ); | |
| 1189 | + } | |
| 1190 | + | |
| 1191 | + $this->print_suggestion_defaults_select( | |
| 1192 | + $args['option_key'] . '[default_tone]', | |
| 1193 | + __( 'Default tone', 'wp-parsely' ), | |
| 1194 | + Suggestion_Defaults::get_tones(), | |
| 1195 | + Suggestion_Defaults::get_default_tone( $feature_options ) | |
| 1196 | + ); | |
| 1197 | + | |
| 1198 | + $this->print_suggestion_defaults_select( | |
| 1199 | + $args['option_key'] . '[default_persona]', | |
| 1200 | + __( 'Default persona', 'wp-parsely' ), | |
| 1201 | + Suggestion_Defaults::get_personas(), | |
| 1202 | + Suggestion_Defaults::get_default_persona( $feature_options ) | |
| 1203 | + ); | |
| 1204 | + | |
| 1205 | + echo '</fieldset>'; | |
| 1206 | + } | |
| 1207 | + | |
| 1208 | + /** | |
| 1209 | + * Prints out a select tag for a feature's default generation setting. | |
| 1210 | + * | |
| 1211 | + * @since 3.24.0 | |
| 1212 | + * | |
| 1213 | + * @param string $option_key The nested option key. | |
| 1214 | + * @param string $label The visible label. | |
| 1215 | + * @param array<string, string> $choices The choices, as value => label pairs. | |
| 1216 | + * @param string $selected The currently selected value. | |
| 1217 | + */ | |
| 1218 | + private function print_suggestion_defaults_select( | |
| 1219 | + string $option_key, | |
| 1220 | + string $label, | |
| 1221 | + array $choices, | |
| 1222 | + string $selected | |
| 1223 | + ): void { | |
| 1224 | + printf( | |
| 1225 | + '<p><label for="%1$s">%2$s</label><br /><select name="%3$s" id="%1$s">', | |
| 1226 | + esc_attr( $option_key ), | |
| 1227 | + esc_html( $label ), | |
| 1228 | + esc_attr( $this->get_html_name_attribute( $option_key ) ) | |
| 1229 | + ); | |
| 1230 | + | |
| 1231 | + foreach ( $choices as $value => $choice_label ) { | |
| 1232 | + printf( | |
| 1233 | + '<option value="%s"%s>%s</option>', | |
| 1234 | + esc_attr( $value ), | |
| 1235 | + selected( $selected, $value, false ), | |
| 1236 | + esc_html( $choice_label ) | |
| 1237 | + ); | |
| 1238 | + } | |
| 1239 | + | |
| 1240 | + echo '</select></p>'; | |
| 1241 | + } | |
| 1242 | + | |
| 1243 | + /** | |
| 1129 | 1244 | * Prints out the select tags |
| 1130 | 1245 | * |
| 1131 | 1246 | * @param Setting_Arguments $args The arguments for the select dropdowns. |
| 1132 | 1247 | */ |
| @@ -1557,11 +1672,55 @@ | ||
| 1557 | 1672 | } |
| 1558 | 1673 | |
| 1559 | 1674 | // Produce the final array. |
| 1560 | 1675 | $options = $this->parsely->get_options()['content_helper']; |
| 1561 | - $merged = array_merge( $options, $input['content_helper'] ); | |
| 1562 | 1676 | |
| 1677 | + // Validate the generation defaults separately, as the sanitizer above | |
| 1678 | + // coerces every scalar into a boolean. Values that were not submitted | |
| 1679 | + // fall back to the stored ones, and anything invalid falls back to the | |
| 1680 | + // shipped default. | |
| 1681 | + $validated_defaults = array(); | |
| 1682 | + foreach ( self::FEATURES_WITH_DEFAULTS as $feature_id ) { | |
| 1683 | + /** @var array<string, mixed> $submitted */ | |
| 1684 | + $submitted = $input['content_helper'][ $feature_id ] ?? array(); | |
| 1685 | + /** @var array<string, mixed> $stored */ | |
| 1686 | + $stored = $options[ $feature_id ]; | |
| 1687 | + | |
| 1688 | + $defaults = array( | |
| 1689 | + 'default_tone' => Suggestion_Defaults::get_default_tone( | |
| 1690 | + array( 'default_tone' => $submitted['default_tone'] ?? $stored['default_tone'] ?? null ) | |
| 1691 | + ), | |
| 1692 | + 'default_persona' => Suggestion_Defaults::get_default_persona( | |
| 1693 | + array( 'default_persona' => $submitted['default_persona'] ?? $stored['default_persona'] ?? null ) | |
| 1694 | + ), | |
| 1695 | + ); | |
| 1696 | + | |
| 1697 | + // Only Excerpt Suggestions has a desired length. | |
| 1698 | + if ( Excerpt_Suggestions::get_feature_name() === $feature_id ) { | |
| 1699 | + $length = $submitted['default_length'] ?? $stored['default_length'] ?? null; | |
| 1700 | + | |
| 1701 | + // Anything that is not an integer is invalid, rather than | |
| 1702 | + // something to coerce into one. | |
| 1703 | + $length = is_scalar( $length ) ? filter_var( $length, FILTER_VALIDATE_INT ) : false; | |
| 1704 | + | |
| 1705 | + $defaults['default_length'] = Suggestion_Defaults::get_default_length( | |
| 1706 | + array( 'default_length' => false === $length ? null : $length ) | |
| 1707 | + ); | |
| 1708 | + } | |
| 1709 | + | |
| 1710 | + $validated_defaults[ $feature_id ] = $defaults; | |
| 1711 | + } | |
| 1712 | + | |
| 1713 | + $merged = array_merge( $options, $input['content_helper'] ); | |
| 1714 | + | |
| 1563 | 1715 | $input['content_helper'] = $sanitize( $merged ); |
| 1716 | + | |
| 1717 | + foreach ( $validated_defaults as $feature_id => $defaults ) { | |
| 1718 | + $input['content_helper'][ $feature_id ] = array_merge( | |
| 1719 | + $input['content_helper'][ $feature_id ], | |
| 1720 | + $defaults | |
| 1721 | + ); | |
| 1722 | + } | |
| 1564 | 1723 | |
| 1565 | 1724 | return $input; |
| 1566 | 1725 | } |
| 1567 | 1726 | |