| @@ -119,8 +119,23 @@ | ||
| 119 | 119 | 'required' => false, |
| 120 | 120 | 'type' => 'string', |
| 121 | 121 | 'sanitize_callback' => 'sanitize_text_field', |
| 122 | 122 | ], |
| 123 | + // The permalink field as it stands in the editor, so an unsaved | |
| 124 | + // slug edit scores immediately instead of matching the old URL. | |
| 125 | + 'live_slug' => [ | |
| 126 | + 'required' => false, | |
| 127 | + 'type' => 'string', | |
| 128 | + // NOT sanitize_title: REST calls a sanitize_callback as | |
| 129 | + // ($value, $request, $param), and sanitize_title()'s second | |
| 130 | + // parameter is $fallback_title — so an empty slug returned | |
| 131 | + // the WP_REST_Request object, which fataled on the string | |
| 132 | + // cast below. The editor sends an empty slug whenever the | |
| 133 | + // permalink field is blank (every draft), so this fired on | |
| 134 | + // ordinary use. Sanitized with a single-argument | |
| 135 | + // sanitize_title() where it is consumed instead. | |
| 136 | + 'sanitize_callback' => 'sanitize_text_field', | |
| 137 | + ], | |
| 123 | 138 | 'readability_score' => [ |
| 124 | 139 | 'required' => false, |
| 125 | 140 | 'type' => 'string', |
| 126 | 141 | 'sanitize_callback' => 'sanitize_text_field', |
| @@ -224,8 +239,17 @@ | ||
| 224 | 239 | // nothing about the field, which keeps the saved value. |
| 225 | 240 | $live_title = $request->get_param('live_title'); |
| 226 | 241 | $live_description = $request->get_param('live_description'); |
| 227 | 242 | |
| 243 | + // Score the slug the editor is showing. Omitted (null) keeps the | |
| 244 | + // saved slug; an empty string means the field was cleared, which | |
| 245 | + // falls back to the title-derived slug exactly as an unsaved draft | |
| 246 | + // does inside the calculator. | |
| 247 | + $live_slug = $request->get_param('live_slug'); | |
| 248 | + if ($live_slug !== null) { | |
| 249 | + $content_data['slug'] = sanitize_title((string) $live_slug); | |
| 250 | + } | |
| 251 | + | |
| 228 | 252 | $raw_title = $live_title !== null |
| 229 | 253 | ? (string) $live_title |
| 230 | 254 | : get_post_meta($post_id, '_thinkrank_seo_title', true); |
| 231 | 255 | $raw_description = $live_description !== null |
| @@ -262,13 +286,17 @@ | ||
| 262 | 286 | $metadata, |
| 263 | 287 | $score_options |
| 264 | 288 | ); |
| 265 | 289 | |
| 266 | - // Add readability_score and content_quality from frontend if provided | |
| 267 | - if (!empty($readability_score)) { | |
| 290 | + // Add readability_score and content_quality from frontend if | |
| 291 | + // provided. `!== null`, not `!empty()`: 0 is a legitimate score and | |
| 292 | + // empty() discarded it, so a post the editor scored as 0 kept | |
| 293 | + // whatever the calculator had produced instead (#394). Both params | |
| 294 | + // already default to null above, so null means "not sent". | |
| 295 | + if (null !== $readability_score) { | |
| 268 | 296 | $score_data['readability_score'] = $readability_score; |
| 269 | 297 | } |
| 270 | - if (!empty($content_quality)) { | |
| 298 | + if (null !== $content_quality) { | |
| 271 | 299 | $score_data['content_quality'] = $content_quality; |
| 272 | 300 | } |
| 273 | 301 | |
| 274 | 302 | // Save score if requested |