Skip to content

Comments

Improve post type registration logic#269

Merged
superdav42 merged 5 commits intoUltimate-Multisite:mainfrom
tamkendigital:patch-2
Nov 18, 2025
Merged

Improve post type registration logic#269
superdav42 merged 5 commits intoUltimate-Multisite:mainfrom
tamkendigital:patch-2

Conversation

@tamkendigital
Copy link
Contributor

@tamkendigital tamkendigital commented Nov 15, 2025

Fix undefined property warnings and improve data validation in register_emulated_post_types() method.

This commit addresses several issues:

  1. Fixed undefined property warnings: Previously, the code attempted to access $pt->post_type and $pt->label properties without verifying that the data structure was valid, causing "Undefined property: stdClass::$post_type" warnings when corrupted data existed in the database.

  2. Added automatic data cleanup: The method now automatically cleans up corrupted entries (non-array items or entries missing required keys) and saves the cleaned data back to the database.

  3. Improved validation: Added proper validation to ensure that:

    • Each entry is a valid array
    • Required keys (post_type and label) exist
    • Empty values are allowed for new entries (to support the UI workflow where users add empty entries first, then fill them in)
    • Only entries with non-empty values are registered as post types
  4. Enhanced security: Added sanitization using sanitize_key() and sanitize_text_field() for all user input before processing and registration.

  5. Better error handling: The method now gracefully handles corrupted data by skipping invalid entries instead of throwing errors, allowing users to delete post types without issues.

The fix ensures that:

  • Users can add new post types without errors
  • Users can delete post types without warnings
  • Corrupted data is automatically cleaned up
  • The registration process only attempts to register valid, non-empty post types

Fixes issues where users were unable to delete post types after adding them, and prevents undefined property warnings in the debug log.

Summary by CodeRabbit

  • Bug Fixes
    • Improved post-type data handling: validates and sanitizes saved entries, skips invalid or empty definitions, and exits early when none are present.
    • Automatically cleans and persists corrected settings when corrupted or incomplete entries are found.
    • Continues to preserve and skip already-registered valid post types to maintain consistent behavior.

Fix undefined property warnings and improve data validation in 
register_emulated_post_types() method.

This commit addresses several issues:

1. **Fixed undefined property warnings**: Previously, the code 
   attempted to access $pt->post_type and $pt->label properties 
   without verifying that the data structure was valid, causing 
   "Undefined property: stdClass::$post_type" warnings when 
   corrupted data existed in the database.

2. **Added automatic data cleanup**: The method now automatically 
   cleans up corrupted entries (non-array items or entries missing 
   required keys) and saves the cleaned data back to the database.

3. **Improved validation**: Added proper validation to ensure that:
   - Each entry is a valid array
   - Required keys (post_type and label) exist
   - Empty values are allowed for new entries (to support the UI 
     workflow where users add empty entries first, then fill them in)
   - Only entries with non-empty values are registered as post types

4. **Enhanced security**: Added sanitization using sanitize_key() 
   and sanitize_text_field() for all user input before processing 
   and registration.

5. **Better error handling**: The method now gracefully handles 
   corrupted data by skipping invalid entries instead of throwing 
   errors, allowing users to delete post types without issues.

The fix ensures that:
- Users can add new post types without errors
- Users can delete post types without warnings
- Corrupted data is automatically cleaned up
- The registration process only attempts to register valid, 
  non-empty post types

Fixes issues where users were unable to delete post types after 
adding them, and prevents undefined property warnings in the 
debug log.
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Nov 15, 2025

Caution

Review failed

The pull request is closed.

Walkthrough

Replaces direct emulated post type registration with validation and cleaning: early return if input missing, normalize entries to arrays, require and sanitize post_type and label, persist cleaned settings when issues found, and register only valid, non-empty, sanitized post types (skipping already-registered).

Changes

Cohort / File(s) Summary
Post Type Validation & Cleaning
inc/limits/class-post-type-limits.php
Adds early exit when emulated_post_types is not a non-empty array; normalizes non-array entries to arrays; validates required keys; sanitizes post_type and label; builds and persists cleaned_post_types if corruption detected; filters out invalid/empty entries; registers only sanitized, non-empty post types and skips those already registered.

Sequence Diagram(s)

sequenceDiagram
    participant Caller
    participant Limits as class-post-type-limits
    participant Settings

    Caller->>Limits: register_emulated_post_types()
    Limits->>Limits: read emulated_post_types
    alt not a non-empty array
        Limits-->>Caller: return (no registration)
    else array
        Limits->>Limits: for each entry
        Limits->>Limits: ensure entry is array
        Limits->>Limits: check required keys (post_type,label)
        Limits->>Limits: sanitize post_type & label
        alt any entries cleaned/invalid
            Limits->>Settings: save cleaned emulated_post_types
        end
        Limits->>Limits: filter out empty/invalid entries
        loop register valid types
            Limits->>Limits: if not already registered -> register_post_type()
        end
        Limits-->>Caller: done
    end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

  • Pay attention to:
    • Sanitization logic for post_type and label
    • Correct detection of "corruption" and when settings are persisted
    • Behavior when converting non-array entries and filtering results
    • Interaction with existing registrations and skip conditions

Possibly related PRs

  • Improve post type registration logic #269 — Modifies the same inc/limits/class-post-type-limits.php flow to add validation/cleaning, early-exit, sanitization, persisting cleaned settings, and skipping invalid entries.

Poem

🐇 I hop through arrays and nudge each line,

I scrub the labels till the post types shine.
Corrupt bits chased and tidy lists saved,
Registrars skip duplicates, neat and brave.
A rabbit’s small cheer for clean code today!

Pre-merge checks and finishing touches

✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The PR title 'Improve post type registration logic' directly and accurately summarizes the main change: refactoring the post type registration logic with validation, sanitization, cleanup, and improved error handling.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.

📜 Recent review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between b8c40c9 and 05fd59a.

📒 Files selected for processing (1)
  • inc/limits/class-post-type-limits.php (1 hunks)

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 7

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between a4d6ab1 and 7e0bb0b.

📒 Files selected for processing (1)
  • inc/limits/class-post-type-limits.php (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
inc/limits/class-post-type-limits.php (1)
inc/functions/settings.php (1)
  • wu_save_setting (51-54)
🪛 GitHub Check: Code Quality Checks
inc/limits/class-post-type-limits.php

[failure] 97-97:
Expected 1 space after "!"; 0 found


[failure] 97-97:
Expected 1 space after "!"; 0 found


[failure] 91-91:
Expected 1 space after "!"; 0 found


[warning] 87-87:
Equals sign not aligned with surrounding assignments; expected 7 spaces but found 1 space


[failure] 81-81:
Expected 1 space after "!"; 0 found

🪛 PHPMD (2.15.0)
inc/limits/class-post-type-limits.php

89-89: Avoid unused local variables such as '$index'. (undefined)

(UnusedLocalVariable)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: cypress (8.2, chrome)
  • GitHub Check: cypress (8.1, chrome)
🔇 Additional comments (1)
inc/limits/class-post-type-limits.php (1)

138-149: Post type registration arguments are appropriate for emulated post types.

Only one register_post_type() call exists in the codebase. The settings correctly configure the post type as internal and non-discoverable: public visibility with show_in_menu and exclude_from_search disabled prevents admin menu clutter, while has_archive and can_export set to false align with internal-only usage. The delete_with_user flag set to false correctly preserves system post type integrity.

$cleaned_post_types = [];
$needs_update = false;

foreach ($emulated_post_types as $index => $pt) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Remove unused loop variable.

The $index variable is declared but never used in the loop body.

Apply this diff:

-		foreach ($emulated_post_types as $index => $pt) {
+		foreach ($emulated_post_types as $pt) {

Based on static analysis hints.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
foreach ($emulated_post_types as $index => $pt) {
foreach ($emulated_post_types as $pt) {
🧰 Tools
🪛 PHPMD (2.15.0)

89-89: Avoid unused local variables such as '$index'. (undefined)

(UnusedLocalVariable)

🤖 Prompt for AI Agents
In inc/limits/class-post-type-limits.php around line 89, the foreach declares an
unused $index variable; remove it so the loop reads foreach
($emulated_post_types as $pt) (or use a throwaway variable) to eliminate the
unused variable warning and keep the loop body unchanged.

Comment on lines +103 to +106
$cleaned_post_types[] = [
'post_type' => isset($pt['post_type']) ? sanitize_key($pt['post_type']) : '',
'label' => isset($pt['label']) ? sanitize_text_field($pt['label']) : '',
];
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion | 🟠 Major

Remove redundant isset checks.

Lines 104-105 check isset($pt['post_type']) and isset($pt['label']) again, but line 97 already verified these keys exist. The ternary operators are unnecessary here.

Apply this diff to simplify:

 			// Add data (even if empty, to allow users to fill in new post types)
 			$cleaned_post_types[] = [
-				'post_type' => isset($pt['post_type']) ? sanitize_key($pt['post_type']) : '',
-				'label'     => isset($pt['label']) ? sanitize_text_field($pt['label']) : '',
+				'post_type' => sanitize_key($pt['post_type']),
+				'label'     => sanitize_text_field($pt['label']),
 			];

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In inc/limits/class-post-type-limits.php around lines 103 to 106, the array
assignment redundantly checks isset($pt['post_type']) and isset($pt['label')
even though those keys were validated earlier (line ~97); remove the ternary
isset checks and directly use sanitize_key($pt['post_type']) and
sanitize_text_field($pt['label']) when building $cleaned_post_types so the code
is simpler and still safe given the prior validation.

Comment on lines +109 to +113
// Save cleaned data if there were any changes
if ($needs_update) {
wu_save_setting('emulated_post_types', $cleaned_post_types);
$emulated_post_types = $cleaned_post_types;
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

Consider verifying save operation success.

The code continues with $cleaned_post_types regardless of whether wu_save_setting() succeeds. If the save fails silently, corrupted data will be cleaned in memory but persist in the database, requiring repeated cleaning on each request.

Consider adding error handling or logging:

if ($needs_update) {
    $saved = wu_save_setting('emulated_post_types', $cleaned_post_types);
    if ($saved) {
        $emulated_post_types = $cleaned_post_types;
    } else {
        // Log error or handle failure
    }
}

Check if wu_save_setting returns a boolean indicating success:


🏁 Script executed:

#!/bin/bash
# Description: Check the return value and behavior of wu_save_setting

# Find the implementation of wu_save_setting
ast-grep --pattern 'function wu_save_setting($$$) {
  $$$
}'

# Also check the settings class save_setting method
ast-grep --pattern 'function save_setting($$$) {
  $$$
}'

Length of output: 230



---

🏁 Script executed:

```shell
#!/bin/bash
# Description: Find the save_setting method implementation in the settings class

# Search for the save_setting method definition
ast-grep --pattern 'save_setting($$$) {
  $$$
}'

# Also search more broadly for this method
rg -n "save_setting" --type=php -B 2 -A 10 | head -100

Length of output: 9800



---

🏁 Script executed:

```shell
#!/bin/bash
# Description: Find the Settings class and save_setting method implementation

# Search for class Settings
rg -n "class Settings" --type=php -A 2

# Search for the save_setting function definition (not calls)
rg -n "function save_setting\(" --type=php -A 15 | head -150

Length of output: 2154



---

🏁 Script executed:

```shell
#!/bin/bash
# Description: Get the complete save_setting method with return statement

rg -n "public function save_setting" --type=php -A 25 inc/class-settings.php

Length of output: 1610



---

🏁 Script executed:

```shell
#!/bin/bash
# Description: Find wu_save_option implementation to confirm return value

rg -n "function wu_save_option" --type=php -A 15

Length of output: 852


Add return value handling for wu_save_setting() to prevent silent save failures.

The call to wu_save_setting() at line 112 returns a boolean indicating success, but this value is ignored. If the save fails, corrupted data will be cleaned in memory while stale data persists in the database, requiring repeated cleanup on each request.

Update lines 109-113 to check the return value:

if ($needs_update) {
    if (wu_save_setting('emulated_post_types', $cleaned_post_types)) {
        $emulated_post_types = $cleaned_post_types;
    } else {
        // Handle save failure
    }
}

Alternatively, log the failure or trigger an admin notice to alert users of potential data consistency issues.

🤖 Prompt for AI Agents
In inc/limits/class-post-type-limits.php around lines 109 to 113, the call to
wu_save_setting('emulated_post_types', $cleaned_post_types) currently ignores
its boolean return value which can hide save failures and leave the DB stale;
change the block to check the return: only set $emulated_post_types =
$cleaned_post_types when wu_save_setting returns true, and if it returns false
handle the failure (e.g., log an error with relevant context and/or trigger an
admin notice) so callers are informed and data consistency issues are surfaced.

Comment on lines +122 to +123
$post_type = sanitize_key($pt['post_type']);
$label = sanitize_text_field($pt['label']);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion | 🟠 Major

Remove redundant sanitization.

These lines sanitize post_type and label again, but they were already sanitized in the cleaning loop (lines 104-105). When $needs_update is true, $emulated_post_types is replaced with $cleaned_post_types on line 112, which contains sanitized values. When $needs_update is false, the data passed validation but wasn't modified—yet sanitizing again is still defensive duplication.

Since the empty checks on lines 118-120 already guard against empty values, you can simplify lines 122-128 to:

 		// Register only valid post types (skip empty ones)
 		foreach ($emulated_post_types as $pt) {
 			// Skip if post_type or label is empty
 			if (empty($pt['post_type']) || empty($pt['label'])) {
 				continue;
 			}
 
-			$post_type = sanitize_key($pt['post_type']);
-			$label     = sanitize_text_field($pt['label']);
-
-			// Skip if sanitization resulted in empty values
-			if (empty($post_type) || empty($label)) {
-				continue;
-			}
-
 			// Check if post type is already registered
-			$existing_pt = get_post_type_object($post_type);
+			$existing_pt = get_post_type_object($pt['post_type']);
 
 			if ($existing_pt) {
 				continue;
 			}
 
 			// Register the post type
 			register_post_type(
-				$post_type,
+				$pt['post_type'],
 				[
-					'label'               => $label,
+					'label'               => $pt['label'],
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
$post_type = sanitize_key($pt['post_type']);
$label = sanitize_text_field($pt['label']);
// Register only valid post types (skip empty ones)
foreach ($emulated_post_types as $pt) {
// Skip if post_type or label is empty
if (empty($pt['post_type']) || empty($pt['label'])) {
continue;
}
// Check if post type is already registered
$existing_pt = get_post_type_object($pt['post_type']);
if ($existing_pt) {
continue;
}
// Register the post type
register_post_type(
$pt['post_type'],
[
'label' => $pt['label'],
🤖 Prompt for AI Agents
In inc/limits/class-post-type-limits.php around lines 122-123, the code
redundantly re-sanitizes post_type and label even though $cleaned_post_types
already contains sanitized values; remove the sanitize_key/sanitize_text_field
calls and use the existing $pt['post_type'] and $pt['label'] directly (e.g.
assign $post_type = $pt['post_type']; $label = $pt['label'];), keeping the
existing empty checks on lines 118-120 and the subsequent logic unchanged so
behavior and validation remain identical.

superdav42 and others added 2 commits November 17, 2025 17:07
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

♻️ Duplicate comments (3)
inc/limits/class-post-type-limits.php (3)

103-106: Simplify cleaned_post_types construction by removing redundant isset checks.

Because the preceding isset($pt['post_type']) / isset($pt['label']) condition guarantees both keys exist and are non-null, the extra isset() checks in the assignment are never false and can be dropped for clarity.

 			// Add data (even if empty, to allow users to fill in new post types)
 			$cleaned_post_types[] = [
-				'post_type' => isset($pt['post_type']) ? sanitize_key($pt['post_type']) : '',
-				'label'     => isset($pt['label']) ? sanitize_text_field($pt['label']) : '',
+				'post_type' => sanitize_key($pt['post_type']),
+				'label'     => sanitize_text_field($pt['label']),
 			];

Based on static analysis hints.


89-97: Remove unused $index and fix ! spacing to satisfy coding standards.

The loop index is never used and the ! operators are missing the required space. Cleaning these up will also clear the static-analysis warnings.

-		foreach ($emulated_post_types as $index => $pt) {
+		foreach ($emulated_post_types as $pt) {
 			// Verify that $pt is an array
-			if (!is_array($pt)) {
+			if ( ! is_array($pt)) {
 				$needs_update = true;
 				continue;
 			}
 
 			// Verify that required keys exist (allow empty values for new entries)
-			if (!isset($pt['post_type']) || !isset($pt['label'])) {
+			if ( ! isset($pt['post_type']) || ! isset($pt['label'])) {
 				$needs_update = true;
 				continue;
 			}

Based on static analysis hints.


109-113: Consider handling wu_save_setting() failures instead of ignoring the return value.

wu_save_setting('emulated_post_types', $cleaned_post_types) returns a status from the settings layer, but the result is currently discarded. If the save fails, the database keeps corrupted entries while this request proceeds with cleaned data, and the cleanup will be repeated on every request without any visibility.

You can at least surface failures while preserving current behavior:

 		// Save cleaned data if there were any changes
 		if ($needs_update) {
-			wu_save_setting('emulated_post_types', $cleaned_post_types);
-			$emulated_post_types = $cleaned_post_types;
+			if ( ! wu_save_setting('emulated_post_types', $cleaned_post_types) ) {
+				// Optional: log or otherwise handle a failed settings save here.
+			}
+
+			$emulated_post_types = $cleaned_post_types;
 		}

This keeps registration using the cleaned entries while giving you a hook to diagnose persistence issues when the settings write fails.

🧹 Nitpick comments (1)
inc/limits/class-post-type-limits.php (1)

85-107: Optional: consider always using $cleaned_post_types for registration.

You already build $cleaned_post_types for every valid entry, but when $needs_update is false you continue to feed the registration loop from the original $emulated_post_types. You could unconditionally use $cleaned_post_types for registration (keeping $needs_update only to decide whether to persist back to the DB) to avoid re-reading raw settings and to centralize use of the validated data. Not required for correctness, but worth considering for clarity.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 7e0bb0b and b8c40c9.

📒 Files selected for processing (1)
  • inc/limits/class-post-type-limits.php (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
inc/limits/class-post-type-limits.php (1)
inc/functions/settings.php (1)
  • wu_save_setting (51-54)
🪛 GitHub Check: Code Quality Checks
inc/limits/class-post-type-limits.php

[failure] 97-97:
Expected 1 space after "!"; 0 found


[failure] 97-97:
Expected 1 space after "!"; 0 found


[failure] 91-91:
Expected 1 space after "!"; 0 found

🪛 PHPMD (2.15.0)
inc/limits/class-post-type-limits.php

89-89: Avoid unused local variables such as '$index'. (undefined)

(UnusedLocalVariable)

🔇 Additional comments (2)
inc/limits/class-post-type-limits.php (2)

81-83: Guard clause for empty/invalid settings is appropriate.

Short-circuiting when emulated_post_types is not a non-empty array avoids unnecessary work and prevents downstream access on unexpected types; this matches the intended “do nothing if nothing configured” behavior.


115-149: Registration loop correctly filters and registers only valid, non-duplicate post types.

The second pass over $emulated_post_types:

  • skips entries with empty post_type or label,
  • sanitizes both fields before use,
  • avoids re-registering existing post types via get_post_type_object(), and
  • calls register_post_type() with the expected options for these emulated types.

This matches the PR goal of registering only valid, non-empty, non-duplicate emulated post types.

@superdav42 superdav42 merged commit 48f5256 into Ultimate-Multisite:main Nov 18, 2025
9 of 10 checks passed
@superdav42
Copy link
Collaborator

@tamkendigital Thanks for the PR! It works perfectly. I just made some code style changes and merged it. It'll be in the next release.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants