Skip to content

Commit 4f1c3c1

Browse files
authored
Fix release download failure when allow_url_fopen is disabled (#8127)
Servers with `allow_url_fopen = Off` (common on shared hosting) fail to download upgrade ZIPs via `file_get_contents()`, producing "no suitable wrapper could be found". Since `cURL` is already a declared ChurchCRM prerequisite, it's the correct transport to use here. ## Changes - **`src/ChurchCRM/dto/ChurchCRMReleaseManager.php`** — `downloadRelease()` now uses cURL as the primary download method: - SSL verification enabled (`CURLOPT_SSL_VERIFYPEER`, `CURLOPT_SSL_VERIFYHOST`) - Follows redirects (`CURLOPT_FOLLOWLOCATION`) — needed for GitHub's release asset CDN - 300s timeout appropriate for large ZIP payloads - Captures `curl_errno()` + `curl_error()` + HTTP status code for actionable failure logs - Falls back to `file_get_contents` only if `curl_init` is somehow unavailable - Existing 3-attempt retry loop with backoff is unchanged ```php // Before — fails silently when allow_url_fopen=Off $downloadContent = @file_get_contents($url); // After — works regardless of allow_url_fopen $ch = curl_init($url); curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true); curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, true); curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 2); curl_setopt($ch, CURLOPT_TIMEOUT, 300); $result = curl_exec($ch); ``` <!-- START COPILOT ORIGINAL PROMPT --> <details> <summary>Original prompt</summary> > > ---- > > *This section details on the original issue you should resolve* > > <issue_title>Upgrade to 7.0 error</issue_title> > <issue_description>Using "new release" feature, ChurchCRM throwing error: > Error making API Call to: /admin/api/upgrade/download-latest-release > Error text: Failed to download latest release > Log attached. > > [260302_release_upgrade_error.log](https://github.com/user-attachments/files/25689642/260302_release_upgrade_error.log) > > > > Collected Value Title | Data > ----------------------|---------------- > Page Name |/admin/system/upgrade > Screen Size |1080x1920 > Window Size |793x1606 > Page Size |1171x1606 > Platform Information | Linux cccat740.hostpapavps.net 4.18.0-553.58.1.el8_10.x86_64 #1 SMP Wed Jun 25 06:37:46 EDT 2025 x86_64 > PHP Version | 8.4.18 > SQL Version | 10.11.16-MariaDB > ChurchCRM Version |6.8.1 > Reporting Browser |Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:148.0) Gecko/20100101 Firefox/148.0 > Prerequisite Status |Missing Prerequisites: ["PHP iconv","FileInfo Extension for image manipulation"] > </issue_description> > > <agent_instructions>Review the logs and recommend a fix </agent_instructions> > > ## Comments on the Issue (you are @copilot in this section) > > <comments> > </comments> > </details> <!-- START COPILOT CODING AGENT SUFFIX --> - Fixes #8125 <!-- START COPILOT CODING AGENT TIPS --> --- ✨ Let Copilot coding agent [set things up for you](https://github.com/ChurchCRM/CRM/issues/new?title=✨+Set+up+Copilot+instructions&body=Configure%20instructions%20for%20this%20repository%20as%20documented%20in%20%5BBest%20practices%20for%20Copilot%20coding%20agent%20in%20your%20repository%5D%28https://gh.io/copilot-coding-agent-tips%29%2E%0A%0A%3COnboard%20this%20repo%3E&assignees=copilot) — coding agent works faster and does higher quality work when set up for your repo.
2 parents 5b13655 + c1633ba commit 4f1c3c1

File tree

1 file changed

+40
-17
lines changed

1 file changed

+40
-17
lines changed

src/ChurchCRM/dto/ChurchCRMReleaseManager.php

Lines changed: 40 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -245,30 +245,54 @@ public static function downloadRelease(ChurchCRMRelease $release): array
245245
// Download the file with retry logic (3 attempts total)
246246
$maxAttempts = 3;
247247
$downloadContent = false;
248-
$lastError = null;
248+
$lastErrorMsg = 'Unknown error';
249249

250250
for ($attempt = 1; $attempt <= $maxAttempts; $attempt++) {
251251
$logger->info('Download attempt ' . $attempt . ' of ' . $maxAttempts);
252252

253-
// Clear any previous error
254-
@error_clear_last();
255-
256-
$downloadContent = @file_get_contents($url);
253+
if (function_exists('curl_init')) {
254+
// Use cURL for download (works even when allow_url_fopen is disabled)
255+
$ch = curl_init($url);
256+
if ($ch === false) {
257+
$lastErrorMsg = 'curl_init() failed to initialize (possibly malformed URL)';
258+
} else {
259+
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
260+
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
261+
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, true);
262+
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 2);
263+
curl_setopt($ch, CURLOPT_USERAGENT, 'ChurchCRM/' . VersionUtils::getInstalledVersion());
264+
curl_setopt($ch, CURLOPT_TIMEOUT, 300);
265+
$result = curl_exec($ch);
266+
$curlErrno = curl_errno($ch);
267+
$curlError = curl_error($ch);
268+
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
269+
curl_close($ch);
270+
271+
if ($result !== false && $httpCode >= 200 && $httpCode < 300 && strlen($result) > 0) {
272+
$downloadContent = $result;
273+
$logger->info('Download succeeded on attempt ' . $attempt . ' via cURL (HTTP ' . $httpCode . ')');
274+
break;
275+
}
257276

258-
// Check if download succeeded and content is not empty
259-
if ($downloadContent !== false && strlen($downloadContent) > 0) {
260-
$logger->info('Download succeeded on attempt ' . $attempt);
261-
break;
277+
$lastErrorMsg = !empty($curlError) ? 'cURL error (' . $curlErrno . '): ' . $curlError : 'HTTP ' . $httpCode;
278+
}
279+
} else {
280+
// Fallback: file_get_contents (requires allow_url_fopen = On)
281+
@error_clear_last();
282+
$result = @file_get_contents($url);
283+
if ($result !== false && strlen($result) > 0) {
284+
$downloadContent = $result;
285+
$logger->info('Download succeeded on attempt ' . $attempt . ' via file_get_contents');
286+
break;
287+
}
288+
$phpError = error_get_last();
289+
$lastErrorMsg = $phpError['message'] ?? 'Unknown error or empty response';
262290
}
263291

264-
// Capture the error for logging
265-
$lastError = error_get_last();
266-
$errorMsg = $lastError['message'] ?? 'Unknown error or empty response';
267-
268292
if ($attempt < $maxAttempts) {
269293
$logger->warning('Download attempt ' . $attempt . ' failed, retrying...', [
270294
'url' => $url,
271-
'error' => $errorMsg,
295+
'error' => $lastErrorMsg,
272296
]);
273297
// Wait before retry (1 second, then 2 seconds)
274298
sleep($attempt);
@@ -277,12 +301,11 @@ public static function downloadRelease(ChurchCRMRelease $release): array
277301

278302
// Check if all attempts failed
279303
if ($downloadContent === false) {
280-
$errorMsg = $lastError['message'] ?? 'Unknown error';
281304
$logger->error('Failed to download release file after ' . $maxAttempts . ' attempts', [
282305
'url' => $url,
283-
'error' => $errorMsg,
306+
'error' => $lastErrorMsg,
284307
]);
285-
throw new \Exception(gettext('Failed to download the release file from GitHub after multiple attempts. Please check your server\'s internet connection and try again.') . ' Error: ' . $errorMsg);
308+
throw new \Exception(gettext('Failed to download the release file from GitHub after multiple attempts. Please check your server\'s internet connection and try again.') . ' Error: ' . $lastErrorMsg);
286309
}
287310

288311
// Check if downloaded content is empty

0 commit comments

Comments
 (0)