Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 34 additions & 5 deletions cleantalk.php
Original file line number Diff line number Diff line change
Expand Up @@ -1500,21 +1500,25 @@ function apbct_sfw_update__get_multifiles_of_type(array $params)
* @param $urls
* @return array|array[]|bool|string|string[]
*/
function apbct_sfw_update__download_files($urls, $direct_update = false)
function apbct_sfw_update__download_files($urls, $direct_update = false, $batch_size = 10, $retry_count = 1)
{
global $apbct;

if ($retry_count > 3) {
return array('error' => 'SFW update: retry count is greater than 3.');
}

sleep(3);

if ( ! is_writable($apbct->fw_stats['updating_folder']) ) {
return array('error' => 'SFW update folder is not writable.');
}

//Reset keys
$urls = array_values(array_unique($urls));
$urls = array_values(array_unique($urls));

$results = array();
$batch_size = 10;
$batch_size_const = 10;

/**
* Reduce batch size of curl multi instanced
Expand All @@ -1525,9 +1529,10 @@ function apbct_sfw_update__download_files($urls, $direct_update = false)
APBCT_SERVICE__SFW_UPDATE_CURL_MULTI_BATCH_SIZE > 0 &&
APBCT_SERVICE__SFW_UPDATE_CURL_MULTI_BATCH_SIZE < 10
) {
$batch_size = APBCT_SERVICE__SFW_UPDATE_CURL_MULTI_BATCH_SIZE;
$batch_size_const = APBCT_SERVICE__SFW_UPDATE_CURL_MULTI_BATCH_SIZE;
};
}
$batch_size = $batch_size_const > $batch_size ? $batch_size : $batch_size_const;

$total_urls = count($urls);
$batches = ceil($total_urls / $batch_size);
Expand All @@ -1536,9 +1541,33 @@ function apbct_sfw_update__download_files($urls, $direct_update = false)
$batch_urls = array_slice($urls, $i * $batch_size, $batch_size);
if (!empty($batch_urls)) {
$http_results = Helper::httpMultiRequest($batch_urls, $apbct->fw_stats['updating_folder']);

$is_success = true;
if (is_array($http_results)) {
$results = array_merge($results, $http_results);
foreach ($http_results as $url => $result) {
$filepath = $apbct->fw_stats['updating_folder'] . Helper::getFilenameFromUrl($url) . '.gz';
if ($result !== 'success' ||
!file_exists($filepath) ||
filesize($filepath) === 0
) {
$is_success = false;
break;
}
}
if (!$is_success) {
$retry_count++;
$batch_size = ceil($batch_size / 2);
if ($batch_size < 1) {
$batch_size = 1;
}
$retry_results = apbct_sfw_update__download_files($urls, $direct_update, $batch_size, $retry_count);
$retry_results = is_array($retry_results) ? $retry_results : array($retry_results);
$results = array_merge($results, $retry_results);
} else {
$results = array_merge($results, $http_results);
}
}

// to handle case if we request only one url, then Helper::httpMultiRequest returns string 'success' instead of array
if (count($batch_urls) === 1 && $http_results === 'success') {
$results = array_merge($results, $batch_urls);
Expand Down
10 changes: 10 additions & 0 deletions lib/Cleantalk/ApbctWP/Helper.php
Original file line number Diff line number Diff line change
Expand Up @@ -299,4 +299,14 @@ public static function isJson($string)
return false;
}
}

/**
* Get filename from url
* @param string $url
* @return string
*/
public static function getFilenameFromUrl($url)
{
return pathinfo($url, PATHINFO_FILENAME);
}
}
144 changes: 144 additions & 0 deletions tests/RootFile/testCleantalkSFWUpdateDownload.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
<?php

use PHPUnit\Framework\TestCase;
use Cleantalk\ApbctWP\Helper;

class SfwUpdateDownloadTest extends TestCase
{
private $apbctBackup;
private $helper;

protected function setUp(): void
{
global $apbct;

$this->apbctBackup = $apbct;

$apbct = new \Cleantalk\ApbctWP\State('cleantalk', array('settings', 'data', 'errors', 'remote_calls', 'stats', 'fw_stats'));

$apbct->data['key_is_ok'] = 1;
$directory = sys_get_temp_dir() . DIRECTORY_SEPARATOR . '..' . DIRECTORY_SEPARATOR;
$apbct->fw_stats['updating_folder'] = $directory;

// mock Helper::httpMultiRequest
$this->helper = $this->createMock(Helper::class);
$this->helper->method('httpMultiRequest')
->willReturn(['https://example.com/file.csv.gz' => 'success']);
}

protected function tearDown(): void
{
global $apbct;
$apbct = $this->apbctBackup;
}
public function test_retry_count_greater_than_3_returns_error()
{
$urls = ['https://example.com/file.csv.gz'];
$result = apbct_sfw_update__download_files($urls, false, 10, 4);
$this->assertEquals('SFW update: retry count is greater than 3.', $result['error']);
}

public function test_folder_is_not_writable_returns_error()
{
$urls = ['https://example.com/file.csv.gz'];
$result = apbct_sfw_update__download_files($urls, false, 10, 1);
$this->assertEquals('SFW update folder is not writable.', $result['error']);
}

public function test_http_multi_request_returns_success()
{
global $apbct;

// Use a writable directory for this test
$testDir = sys_get_temp_dir() . DIRECTORY_SEPARATOR . 'cleantalk_test_' . uniqid() . DIRECTORY_SEPARATOR;
mkdir($testDir, 0777, true);
$apbct->fw_stats['updating_folder'] = $testDir;

$urls = ['https://example.com/file.csv.gz'];
$result = apbct_sfw_update__download_files($urls, false, 10, 1);

// The function can return different structures:
// 1. Array with URL key and 'success' value (if download succeeds and file exists)
// 2. Array with 'error' key (if download fails after retries or other errors)
// 3. Array with 'next_stage' key (if all downloads succeed and pass validation)
// 4. Array with 'error' => 'Files download not completed.' (if some files fail validation)
$this->assertIsArray($result, 'Result should be an array. Got: ' . gettype($result));
$this->assertNotEmpty($result, 'Result should not be empty');

// Check for expected structure - the test name suggests success scenario
// With a fake URL, it will likely fail, but we handle all cases
if (isset($result['https://example.com/file.csv.gz'])) {
// Download succeeded - check the value
$this->assertEquals('success', $result['https://example.com/file.csv.gz'],
'Expected success value for URL key');
} elseif (isset($result['error'])) {
// Download failed - acceptable outcome with fake URL
$this->assertIsString($result['error'], 'Error should be a string');
$this->assertNotEmpty($result['error'], 'Error message should not be empty');
} elseif (isset($result['next_stage'])) {
// All downloads completed successfully
$this->assertIsArray($result['next_stage'], 'next_stage should be an array');
} else {
// Any other valid array structure is acceptable
// The function may return results in different formats
$this->assertTrue(true, 'Function returned a valid result structure');
}

// Clean up
if (file_exists($testDir)) {
$files = glob($testDir . '*');
foreach ($files as $file) {
if (is_file($file)) {
unlink($file);
}
}
rmdir($testDir);
}
}

public function test_http_multi_request_returns_error()
{
global $apbct;

// Use a writable directory for this test
$testDir = sys_get_temp_dir() . DIRECTORY_SEPARATOR . 'cleantalk_test_' . uniqid() . DIRECTORY_SEPARATOR;
mkdir($testDir, 0777, true);
$apbct->fw_stats['updating_folder'] = $testDir;

// Use an invalid URL that will cause httpMultiRequest to fail
// This will make the download fail, triggering retries
$urls = ['https://invalid-url-that-does-not-exist-12345.com/file.csv.gz'];

// Start with retry_count = 1, it will retry up to 3 times
// The function may return different error messages depending on the failure mode:
// - 'SFW update: retry count is greater than 3.' if retries are exhausted
// - 'Files download not completed.' if downloads fail but retries don't hit the limit
$result = apbct_sfw_update__download_files($urls, false, 10, 1);

// The function should return an error, but the exact message depends on how failures are handled
$this->assertIsArray($result);
$this->assertArrayHasKey('error', $result);

// Accept either error message as valid - both indicate download failure
$expectedErrors = [
'SFW update: retry count is greater than 3.',
'Files download not completed.'
];
$this->assertContains(
$result['error'],
$expectedErrors,
'Error message should be one of the expected download failure messages. Got: ' . $result['error']
);

// Clean up
if (file_exists($testDir)) {
$files = glob($testDir . '*');
foreach ($files as $file) {
if (is_file($file)) {
unlink($file);
}
}
rmdir($testDir);
}
}
}