60,
'display' => __( 'Every minute (Sirius Press export)', 'sirius-press' ),
);
return $schedules;
}
public static function schedule() {
if ( ! wp_next_scheduled( self::CRON_HOOK ) ) {
wp_schedule_event( time() + 60, 'sirius_minute', self::CRON_HOOK );
}
}
public static function unschedule() {
$timestamp = wp_next_scheduled( self::CRON_HOOK );
if ( $timestamp ) {
wp_unschedule_event( $timestamp, self::CRON_HOOK );
}
}
/**
* Process a batch.
*
* @param int $limit Maximum rows to attempt.
* @return array{done:int,failed:int,skipped:int,left:int}
*/
public static function run_batch( $limit = 10 ) {
$result = array(
'done' => 0,
'failed' => 0,
'skipped' => 0,
'left' => 0,
);
if ( SP_Settings::MODE_SERVER !== SP_Settings::mode() ) {
// Manual mode: the server has no key, so there is nothing for cron
// to do. Rows wait for a browser to sign them.
$result['left'] = SPE_Queue::counts()[ SPE_Queue::STATUS_PENDING ];
return $result;
}
if ( ! SP_Settings::is_configured() ) {
return $result;
}
$deadline = microtime( true ) + self::TIME_BUDGET;
foreach ( SPE_Queue::claim( $limit ) as $row ) {
if ( microtime( true ) > $deadline ) {
break;
}
$outcome = self::process( $row );
if ( 'done' === $outcome ) {
$result['done']++;
} elseif ( 'skipped' === $outcome ) {
$result['skipped']++;
} else {
$result['failed']++;
}
}
$result['left'] = SPE_Queue::counts()[ SPE_Queue::STATUS_PENDING ];
return $result;
}
/**
* One row.
*
* @return string 'done', 'skipped' or 'failed'.
*/
public static function process( $row ) {
$body = self::build( $row );
if ( is_wp_error( $body ) ) {
SPE_Queue::mark_failed( $row->id, $body->get_error_message() );
return 'failed';
}
$hash = hash( 'sha256', $body );
if ( '' !== $row->content_hash && hash_equals( $row->content_hash, $hash ) ) {
// Byte-identical to what is already published.
SPE_Queue::mark_done( $row->id, $hash );
return 'skipped';
}
$upload = SP_Gateway::put( $row->path, $body );
if ( empty( $upload['ok'] ) ) {
SPE_Queue::mark_failed( $row->id, $upload['error'] );
self::report_failure( $row, $upload['error'] );
return 'failed';
}
SPE_Queue::mark_done( $row->id, $hash );
/**
* Fires after a path is published to the name's bucket.
*
* @param string $path
* @param int $bytes
*/
do_action( 'sirius_press_exported', $row->path, strlen( $body ) );
return 'done';
}
/**
* Produce the bytes for a row.
*
* Assets are fetched and passed through untouched. Pages are fetched,
* rewritten, and the assets they mention are queued behind them — which
* is how a bulk export discovers the theme's stylesheet without anybody
* having to list it.
*
* @return string|WP_Error
*/
public static function build( $row ) {
$fetched = SPE_Renderer::fetch( $row->source_url );
if ( empty( $fetched['ok'] ) ) {
return new WP_Error( 'sirius_export_fetch', $fetched['error'] );
}
if ( 'asset' === $row->kind || SPE_Mapper::is_asset( $row->path ) ) {
return $fetched['body'];
}
$rewritten = SPE_Renderer::rewrite( $fetched['body'], $row->path );
foreach ( $rewritten['assets'] as $path => $url ) {
// Only queue an asset the first time it is seen; re-queueing it on
// every page that links the stylesheet would reset its row to
// pending forever and the queue would never empty.
if ( '' === SPE_Queue::hash_at( $path ) ) {
SPE_Queue::add( $path, $url, 'asset' );
}
}
return $rewritten['html'];
}
/**
* Tell somebody when an export stops working.
*
* Only on the attempt that exhausts the retries, and only once per hour:
* a misconfigured key fails on every row in the queue, and an inbox with
* four hundred copies of the same message is indistinguishable from no
* message at all.
*/
private static function report_failure( $row, $error ) {
if ( (int) $row->attempts + 1 < SPE_Queue::MAX_ATTEMPTS ) {
return;
}
if ( get_transient( 'sirius_export_failure_notified' ) ) {
return;
}
set_transient( 'sirius_export_failure_notified', 1, HOUR_IN_SECONDS );
SP_Inbox::add(
0,
__( 'A page could not be published to your name', 'sirius-press' ),
sprintf(
/* translators: 1: bucket path, 2: error message, 3: URL of the export screen. */
wp_kses_post( __( '%1$s failed to upload: %2$s
Other pages may be failing for the same reason. Open the export screen to see the queue.', 'sirius-press' ) ),
esc_html( $row->path ),
esc_html( $error ),
esc_url( admin_url( 'admin.php?page=sirius-press-export' ) )
),
'export'
);
}
/** Queue every exportable page on the site. */
public static function queue_full_site() {
$queued = 0;
foreach ( SPE_Mapper::full_site() as $path => $target ) {
if ( SPE_Queue::add( $path, $target['url'], $target['kind'] ) ) {
$queued++;
}
}
return $queued;
}
}