__( 'This job no longer exists. It may have finished or been cancelled.', 'shiftbox' ), 'gone' => true, ); } if ( in_array( $job->data['step'], array( 'done', 'failed' ), true ) ) { $status = self::status( $job ); $job->destroy(); return $status; } if ( ! $job->lock() ) { $status = self::status( $job ); $status['busy'] = true; return $status; } $profile = Shiftbox_Settings::profile(); if ( function_exists( 'set_time_limit' ) ) { @set_time_limit( (int) $profile['budget'] + 60 ); } ignore_user_abort( true ); wp_raise_memory_limit( 'admin' ); $wpdb->suppress_errors( true ); self::$current = $job; register_shutdown_function( array( __CLASS__, 'on_shutdown' ) ); $deadline = microtime( true ) + (float) $profile['budget']; try { if ( 'export' === $job->data['type'] ) { Shiftbox_Exporter::tick( $job, $deadline ); } else { Shiftbox_Importer::tick( $job, $deadline ); } } catch ( Exception $e ) { self::fail( $job, $e->getMessage() ); } $job->data['fatal'] = 0; $done = in_array( $job->data['step'], array( 'done', 'failed' ), true ); $job->save(); $status = self::status( $job ); self::$current = null; if ( $done ) { $job->destroy(); } else { $job->unlock(); } return $status; } /** * Marks a job failed and cleans up partial work. * * @param Shiftbox_Job $job Job. * @param string $message Message. * @return void */ public static function fail( Shiftbox_Job $job, $message ) { $job->data['step'] = 'failed'; $job->data['error'] = $message; $job->log( 'Failed: ' . $message ); self::cleanup( $job ); } /** * Removes partial output of a job. * * @param Shiftbox_Job $job Job. * @return void */ public static function cleanup( Shiftbox_Job $job ) { if ( 'export' === $job->data['type'] && ! empty( $job->data['archive']['part'] ) ) { @unlink( $job->data['archive']['part'] ); } if ( 'import' === $job->data['type'] ) { Shiftbox_Importer::drop_prefixed( Shiftbox_Importer::TMP ); } } /** * Cancels a job. * * @param string $id Job id. * @return bool */ public static function cancel( $id ) { $job = Shiftbox_Job::load( $id ); if ( ! $job ) { return false; } if ( 'import' === $job->data['type'] && 'finalize' === $job->data['step'] ) { return false; } self::cleanup( $job ); $job->destroy(); return true; } /** * Public status of a job. * * @param Shiftbox_Job $job Job. * @return array */ public static function status( Shiftbox_Job $job ) { $d = $job->data; $bytes = 0; if ( 'export' === $d['type'] && isset( $d['archive']['done'] ) ) { $bytes = (float) $d['archive']['done']; } elseif ( 'import' === $d['type'] && isset( $d['extract'] ) ) { $e = $d['extract']; $bytes = (float) ( null === $e['cur'] ? $e['offset'] : $e['cur']['start'] + $e['cur']['done'] ); } return array( 'id' => $job->id, 'type' => $d['type'], 'step' => $d['step'], 'progress' => (int) min( 100, max( 0, isset( $d['progress'] ) ? $d['progress'] : 0 ) ), 'message' => isset( $d['message'] ) ? $d['message'] : '', 'bytes' => $bytes, 'done' => 'done' === $d['step'], 'error' => 'failed' === $d['step'] ? $d['error'] : ( ! empty( $d['fatal_msg'] ) && (int) $d['fatal'] >= 3 ? $d['fatal_msg'] : '' ), 'result' => isset( $d['result'] ) ? $d['result'] : null, ); } /** * Records PHP fatal errors so the next request can report them. * * @return void */ public static function on_shutdown() { $job = self::$current; if ( ! $job ) { return; } $err = error_get_last(); if ( ! $err || ! in_array( $err['type'], array( E_ERROR, E_PARSE, E_CORE_ERROR, E_COMPILE_ERROR ), true ) ) { return; } $fresh = Shiftbox_Job::load( $job->id ); if ( ! $fresh ) { return; } $fresh->data['fatal'] = (int) ( isset( $fresh->data['fatal'] ) ? $fresh->data['fatal'] : 0 ) + 1; $fresh->data['fatal_msg'] = sprintf( /* translators: %s: PHP error. */ __( 'The server stopped the process: %s. Try the Safe performance mode in Settings.', 'shiftbox' ), wp_strip_all_tags( $err['message'] ) ); if ( $fresh->data['fatal'] >= 3 ) { self::fail( $fresh, $fresh->data['fatal_msg'] ); } $fresh->save(); } }