OS : Linux
PHP Version : 7.4.33
Software : Apache/2.4.6 (CentOS) PHP/7.4.33
Information System :
urlencode($error_message)], $redirect_url);
wp_safe_redirect($redirect_url);
exit;
}
public function start_import(): void
{
// authorize user
if (!current_user_can('manage_koko_analytics')) {
return;
}
// verify nonce
check_admin_referer('koko_analytics_start_jetpack_import');
// save params
$params = [
'wpcom-api-key' => trim($_POST['wpcom-api-key'] ?? ''),
'wpcom-blog-uri' => trim($_POST['wpcom-blog-uri'] ?? ''),
'date-start' => trim($_POST['date-start'] ?? ''),
'date-end' => trim($_POST['date-end'] ?? ''),
];
// all params are required
if ($params['wpcom-api-key'] === '' || $params['wpcom-blog-uri'] === '' || $params['date-start'] === '' || $params['date-end'] === '') {
$this->redirect_with_error(admin_url('/index.php?page=koko-analytics&tab=jetpack_importer'), __('A required field was missing', 'koko-analytics'));
exit;
}
// first chunk is 30 days after date-start
try {
$date_start = new DateTimeImmutable($params['date-start']);
$date_end = new DateTimeImmutable($params['date-end']);
if ($date_end < $date_start) {
throw new Exception("End date must be after start date");
}
} catch (Exception $e) {
$this->redirect_with_error(admin_url('/index.php?page=koko-analytics&tab=jetpack_importer'), __('Invalid date fields', 'koko-analytics'));
exit;
}
// params are valid; let's go!
update_option('koko_analytics_jetpack_import_params', $params, false);
// work backwards from end date, so most recent stats first
$chunk_end = $date_end;
// determine size of each chunk to pull in and clamp it between 1 and supplied chunk size
$max_chunk_size = isset($_GET['chunk-size']) ? (int) $_GET['chunk-size'] : 30;
$chunk_size = \max(1, \min($max_chunk_size, $date_end->diff($date_start)->days));
// redirect to first chunk
wp_safe_redirect(add_query_arg(['koko_analytics_action' => 'jetpack_import_chunk', 'chunk_size' => $chunk_size, 'chunk_end' => $chunk_end->format('Y-m-d'), '_wpnonce' => wp_create_nonce('koko_analytics_jetpack_import_chunk')]));
exit;
}
public function import_chunk(): void
{
// authorize
if (!current_user_can('manage_koko_analytics')) {
return;
}
// verify nonce
check_admin_referer('koko_analytics_jetpack_import_chunk');
// get params
$params = get_option('koko_analytics_jetpack_import_params');
if (!$params) {
$error_message = __('Missing parameters.', 'koko-analytics');
$this->redirect_with_error(admin_url('/index.php?page=koko-analytics&tab=jetpack_importer'), $error_message);
exit;
}
$chunk_end = trim($_GET['chunk_end']);
$chunk_size = (int) trim($_GET['chunk_size']);
$date_start = new DateTimeImmutable($params['date-start']);
$chunk_end = new DateTimeImmutable($chunk_end);
// calculate next chunk end date and actual size of current chunk
$next_chunk_end = $chunk_end->modify("-{$chunk_size} days");
if ($next_chunk_end < $date_start) {
$chunk_size = max(1, $chunk_end->diff($date_start)->days);
}
// import this chunk
try {
$this->perform_chunk_import($params['wpcom-api-key'], $params['wpcom-blog-uri'], $chunk_end, $chunk_size);
} catch (Exception $e) {
// clean-up after ourselves
delete_option('koko_analytics_jetpack_import_params');
// redirect to form page
$this->redirect_with_error(admin_url('/index.php?page=koko-analytics&tab=jetpack_importer'), $e->getMessage());
exit;
}
// If we're done, redirect to success page
if ($next_chunk_end < $date_start) {
delete_option('koko_analytics_jetpack_import_params');
wp_safe_redirect(get_admin_url(null, '/index.php?page=koko-analytics&tab=jetpack_importer&success=1'));
exit;
}
$url = add_query_arg(['koko_analytics_action' => 'jetpack_import_chunk', 'chunk_size' => $chunk_size, 'chunk_end' => $next_chunk_end->format('Y-m-d'), '_wpnonce' => wp_create_nonce('koko_analytics_jetpack_import_chunk')]);
$chunk_start = $chunk_end->modify("-{$chunk_size} days");
$chunks_left = ceil($chunk_end->diff($date_start)->days / $chunk_size);
// we could do a wp_safe_redirect() here
// but instead we send some HTML to the client and perform a client-side redirect just so the user knows we're still alive and working
?>
' . $chunk_start->format('Y-m-d') . '',
'' . $chunk_end->format('Y-m-d') . ''
);?>
format('Y-m-d'));
$url = "https://stats.wordpress.com/csv.php?api_key={$api_key}&blog_uri={$blog_uri}&end={$end}&table=postviews&format=json&days={$chunk_size}&limit=-1";
$response = wp_remote_post($url, [
'timeout' => 90,
]);
if ($response instanceof WP_Error) {
$code = $response->get_error_code();
$message = $response->get_error_message();
throw new Exception(__('Error making remote request to the WordPress.com API:', 'koko-analytics') . " \n\n{$code} {$message}");
}
$status_code = wp_remote_retrieve_response_code($response);
if ($status_code >= 400) {
$message = wp_remote_retrieve_response_message($response);
$body = wp_remote_retrieve_body($response);
throw new Exception(__('Received error response from WordPress.com API:', 'koko-analytics') . " \n\n{$status_code} {$message}\n\n{$body}");
}
$body = wp_remote_retrieve_body($response);
try {
$data = json_decode($body, null, 512, JSON_THROW_ON_ERROR);
} catch (Exception $e) {
throw new Exception(__('Received non-JSON response from WordPress.com API:', 'koko-analytics') . "\n\n" . $body);
}
// API returns `null` for no data between two given dates
// Let's turn it into an array instead
if ($data === null) {
$data = [];
}
// We now have an array of days in the following format:
// [ [ "date" => "2020-10-31", "postviews" => [ [ "post_id" => 1, "views" => 2 ] ] ] ]
/** @var wpdb $wpdb */
global $wpdb;
foreach ($data as $item) {
$site_views = 0;
// if there were no stats for this date, simply skip
if (count($item->postviews) === 0) {
continue;
}
// update post stats for this date in a single bulk query
$placeholders = rtrim(str_repeat('(%s,%d,%d,%d),', count($item->postviews)), ',');
$values = [];
foreach ($item->postviews as $postviews) {
$site_views += $postviews->views;
array_push($values, $item->date, $postviews->post_id, $postviews->views, $postviews->views);
}
$query = $wpdb->prepare("INSERT INTO {$wpdb->prefix}koko_analytics_post_stats(date, id, visitors, pageviews) VALUES {$placeholders} ON DUPLICATE KEY UPDATE visitors = visitors + VALUES(visitors), pageviews = pageviews + VALUES(pageviews)", $values);
$wpdb->query($query);
if ($wpdb->last_error !== '') {
throw new Exception(__("A database error occurred: ", 'koko-analytics') . " {$wpdb->last_error}");
}
// update site stats
$query = $wpdb->prepare("INSERT INTO {$wpdb->prefix}koko_analytics_site_stats(date, visitors, pageviews) VALUES (%s, %d, %d) ON DUPLICATE KEY UPDATE visitors = visitors + VALUES(visitors), pageviews = pageviews + VALUES(pageviews)", [$item->date, $site_views, $site_views]);
$wpdb->query($query);
if ($wpdb->last_error !== '') {
throw new Exception(__("A database error occurred: ", 'koko-analytics') . " {$wpdb->last_error}");
}
}
}
}