Opened 6 weeks ago
Last modified 6 weeks ago
#65439 new defect (bug)
wp_dashboard_quota() throws an uncaught DivisionByZeroError (fatal "critical error") when a multisite subsite's upload quota is 0 MB
| Reported by: | tyaty1 | Owned by: | |
|---|---|---|---|
| Priority: | normal | Milestone: | Awaiting Review |
| Component: | Administration | Version: | 3.0 |
| Severity: | normal | Keywords: | has-patch has-tests has-unit-tests |
| Cc: | Focuses: | administration, multisite |
Description
DivisionByZeroError in wp_dashboard_quota() when a multisite subsite has a 0 MB upload quota
Component: Administration (Dashboard)
Type: defect (bug)
Severity: normal
Summary
wp_dashboard_quota() throws an uncaught DivisionByZeroError (fatal "critical error") when a multisite subsite's upload quota is 0 MB and used space is 0.
Description
On a multisite subsite whose upload quota (blog_upload_space option) is 0 and whose used space is 0, the Dashboard's "Storage Space" / "At a Glance" widget fatals with an uncaught DivisionByZeroError on PHP 8.0+. The admin sees only There has been a critical error on this website. with a white screen, and — because Recovery Mode is disabled on multisite — receives no recovery email and no on-screen details. With WP_DEBUG_LOG off, nothing is written to wp-content/debug.log; the fatal is only visible in the server's PHP/Apache error log.
Root cause
wp_dashboard_quota() in wp-admin/includes/dashboard.php computes the percentage used like this:
$quota = get_space_allowed(); // returns the raw blog_upload_space option value $used = get_space_used(); if ( $used > $quota ) { $percentused = '100'; } else { $percentused = ( $used / $quota ) * 100; // <-- 0 / 0 when quota == 0 and used == 0 }
When $quota === 0 and $used === 0, the condition 0 > 0 is false, so the else branch runs 0 / 0. On PHP 8.0+ the / operator throws an uncaught DivisionByZeroError (on PHP 7.x this was a non-fatal warning that returned false/INF, so the bug only became fatal with PHP 8). WordPress trunk's minimum is PHP 7.4 and it actively supports/recommends PHP 8.x, so this is squarely in the supported range.
get_space_allowed() (in wp-includes/ms-functions.php) returns the option verbatim when it is numeric:
$space_allowed = get_option( 'blog_upload_space' ); if ( ! is_numeric( $space_allowed ) ) { $space_allowed = get_site_option( 'blog_upload_space' ); } if ( ! is_numeric( $space_allowed ) ) { $space_allowed = 100; } // fallback only for NON-numeric values return apply_filters( 'get_space_allowed', $space_allowed );
Because is_numeric( 0 ) / is_numeric( '0' ) is true, a stored value of 0 passes straight through as 0 — the 100 fallback only applies to empty/non-numeric values. So a subsite explicitly set to a 0 MB quota yields $quota = 0. (Related: #34037 made a 0 quota "respected" rather than silently bumped to a default, which makes a real 0 reachable.)
Parallel instance — display_space_usage() in wp-admin/includes/ms.php
The same unguarded division exists in display_space_usage():
$space_allowed = get_space_allowed(); $space_used = get_space_used(); $percent_used = ( $space_used / $space_allowed ) * 100; // <-- divides by zero whenever quota == 0
This one has no $used > $quota short-circuit at all, so it throws for any zero quota regardless of usage. In current core (verified on WP 7.0 and trunk 7.1-alpha) display_space_usage() is not wired to a default admin screen, but it is a public, documented API function (@since MU (3.0.0), listed in the developer reference) that themes/plugins may call. It should receive the same guard for consistency and to protect callers.
Environment
| Item | Value |
| Reproduced on | WordPress 7.0, PHP 8.4.17, multisite (subdirectory), local DDEV |
| Code confirmed on | trunk 7.1-alpha-62161-src (identical code/line as 7.0) |
| Trigger config | Multisite; network "Site upload space" check ENABLED (upload_space_check_disabled is off/false); subsite blog_upload_space option = 0; subsite has no uploads (get_space_used() returns 0)
|
| Affected versions | Confirmed: WP 7.0 on PHP 8.x. Probably affected (same code, not separately reproduced): 6.9.x and earlier on PHP 8.0+ |
Note on the guard at the top of the function: wp_dashboard_quota() returns early unless the site is multisite and the current user can( 'upload_files' ) and upload_space_check_disabled is not set. All three must hold to reach the division, which is why the bug is configuration-specific.
Steps to reproduce
- Install WordPress as multisite on PHP 8.0+ and create (or use) a subsite.
- Ensure the network upload-space check is enabled: Network Admin → Settings → "Site upload space" checked, i.e. the
upload_space_check_disabledsite option is off. (This is the default.) - Set the subsite's upload quota to 0 MB: Network Admin → Sites → (subsite) → Settings → set Site Upload Space Quota to
0. Equivalent via WP-CLI:wp --url=<subsite> option update blog_upload_space 0
- Make sure the subsite has no uploads (
get_space_used()returns0) — true for a fresh subsite. - Log in to that subsite's Dashboard (
/wp-admin/) as a user with theupload_filescapability (e.g. an administrator).
Result: the Dashboard renders the fatal "There has been a critical error on this website." The PHP error log shows:
PHP Fatal error: Uncaught DivisionByZeroError: Division by zero in .../wp-admin/includes/dashboard.php:1667
Stack trace:
#0 .../wp-admin/includes/dashboard.php(...): wp_dashboard_quota()
#1 ... do_action('activity_box_end') -> wp_dashboard_right_now()
Reproduction verified (2026-06-08, WP 7.0 / PHP 8.4.17, multisite subsite, blog_upload_space=0, get_space_used()=0, upload_space_check_disabled='0', acting as a super admin):
get_space_allowed()='0' get_space_used()=0 upload_space_check_disabled='0' wp_dashboard_quota() → DivisionByZeroError: Division by zero @ wp-admin/includes/dashboard.php:1667 display_space_usage() → DivisionByZeroError: Division by zero @ wp-admin/includes/ms.php:259
Both functions throw at the cited lines. In a real HTTP dashboard request the wp_dashboard_quota() throw propagates up through wp_dashboard_right_now() → do_action( 'activity_box_end' ) and is caught by the fatal-error handler, producing the "critical error" page.
Expected vs actual
- Expected: the Storage Space widget renders, showing 100% used (or 0%) for a 0 MB quota — no fatal error.
- Actual: uncaught
DivisionByZeroError→ fatal "critical error" page; on multisite, no Recovery Mode email and no details for the admin.
Proposed fix
Guard the denominator, mirroring the existing $used > $quota → 100 special case (a 0 MB allowance is effectively "full"):
wp-admin/includes/dashboard.php, wp_dashboard_quota():
} else { $percentused = $quota > 0 ? ( $used / $quota ) * 100 : 100; }
wp-admin/includes/ms.php, display_space_usage():
$percent_used = $space_allowed > 0 ? ( $space_used / $space_allowed ) * 100 : 100;
(If reviewers prefer showing 0 instead of 100 for a zero allowance, only the fallback constant changes; the divide-by-zero guard is the substantive fix.)
Patch (unified diff against trunk WordPress/wordpress-develop)
--- a/src/wp-admin/includes/dashboard.php
+++ b/src/wp-admin/includes/dashboard.php
@@ -1664,7 +1664,7 @@
if ( $used > $quota ) {
$percentused = '100';
} else {
- $percentused = ( $used / $quota ) * 100;
+ $percentused = $quota > 0 ? ( $used / $quota ) * 100 : 100;
}
$used_class = ( $percentused >= 70 ) ? ' warning' : '';
--- a/src/wp-admin/includes/ms.php
+++ b/src/wp-admin/includes/ms.php
@@ -256,7 +256,7 @@
$space_allowed = get_space_allowed();
$space_used = get_space_used();
- $percent_used = ( $space_used / $space_allowed ) * 100;
+ $percent_used = $space_allowed > 0 ? ( $space_used / $space_allowed ) * 100 : 100;
$space = size_format( $space_allowed * MB_IN_BYTES );
?>
Line numbers (1667 in dashboard.php, 259 in ms.php) are identical in WP 7.0 and trunk 7.1-alpha but shift between releases — the function names wp_dashboard_quota() and display_space_usage() are the stable references.
Attachments (2)
Change History (6)
@
6 weeks ago
Tests: PHPUnit coverage for zero-quota cases in wp_dashboard_quota() and display_space_usage()
#1
@
6 weeks ago
- Keywords has-tests added
Independently reproduced on trunk + PHP 8.4 / Docker (wordpress-develop).
Confirmed both throws:
- wp_dashboard_quota() → DivisionByZeroError @ dashboard.php:1667
- display_space_usage() → DivisionByZeroError @ ms.php:259
Grepped all callsites of get_space_allowed() across src/ — only these two
functions perform an unguarded division. The other callers (ms-deprecated.php,
media.php, xmlrpc-server.php, get_upload_space_available()) use multiplication
or subtraction only and are not affected.
Fix approach:
- wp_dashboard_quota(): early return when $quota <= 0, consistent with the existing early-return pattern at the top of that function (is_multisite check, capability check, upload_space_check_disabled check). A site with 0 MB quota has nothing meaningful to display in the Storage Space widget.
- display_space_usage(): ternary guard with 0 as the fallback. This function outputs HTML directly with no early-return pattern, and 0% used is more semantically honest than 100% for a zero allocation.
Patches and PHPUnit tests (7 tests covering zero quota, negative quota,
nonzero regression, and no-output assertion) attached.
This ticket was mentioned in PR #12137 on WordPress/wordpress-develop by @vedantere.
6 weeks ago
#2
- Keywords has-unit-tests added
Fixes https://core.trac.wordpress.org/ticket/65439
## Problem
On a multisite subsite with a 0 MB upload quota (blog_upload_space = 0),
both wp_dashboard_quota() and display_space_usage() throw an uncaught
DivisionByZeroError on PHP 8.0+.
get_space_allowed() returns 0 verbatim because is_numeric(0) is true,
bypassing the 100 MB fallback. The division then executes with a zero denominator.
## Fix
wp_dashboard_quota(): early return when$quota <= 0, consistent with the existing early-return guards at the top of the function.display_space_usage(): ternary guard returning0when$space_allowedis not positive.
## Testing
PHPUnit tests added in tests/phpunit/tests/multisite/wpDashboardQuota.php
covering zero quota, negative quota, normal quota regression, and output assertions.
## Use of AI Tools
AI assistance: Yes
Tool(s): Claude
Model(s): Claude Sonnet
Contribution workflow guidance, code review, implementation assistance, and test structure suggestions; verification and testing were performed in my local environment.
#3
@
6 weeks ago
The root cause has been identified: both wp_dashboard_quota and display_space_usage divide by $quota without properly checking for division by zero. When a subsite has the quota set to exactly 0, is_numeric(0) evaluates to true and get_space_allowed does not fall back to the 100 static value but rather returns 0. In PHP 8.0 or newer, this will result in a DivisionByZeroError exception being thrown as opposed to just a non-fatal error message being written.
The fix to this issue is appropriate and continues the use of the existing pattern ($used greater than $quota = 100) for both functions. display_space_usage, being a public API, makes it equally important to add the guard for this function as well.
The patch looks good.
#4
@
6 weeks ago
- Version 7.0 → 3.0
Hi @tyaty1, welcome to WordPress Core Trac.
Why did you decide to completely hide the information about quota rather than have it display that the quota is currently set to 0? It seems like it would be useful to keep the storage info.
This division was in 3.0 when Multisite was merged, so updating the version to that.
![(please configure the [header_logo] section in trac.ini)](/chrome/site/your_project_logo.png)
Fix: guard against division by zero in wp_dashboard_quota() and display_space_usage()