a8,#dad0ec)}:root .has-subdued-olive-gradient-background{background:linear-gradient(135deg,#fafae1,#67a671)}:root .has-atomic-cream-gradient-background{background:linear-gradient(135deg,#fdd79a,#004a59)}:root .has-nightshade-gradient-background{background:linear-gradient(135deg,#330968,#31cdcf)}:root .has-midnight-gradient-background{background:linear-gradient(135deg,#020381,#2874fc)}.has-regular-font-size{font-size:1em}.has-larger-font-size{font-size:2.625em}.has-normal-font-size{font-size:var(--wp--preset--font-size--normal)}.has-huge-font-size{font-size:var(--wp--preset--font-size--huge)}.has-text-align-center{text-align:center}.has-text-align-left{text-align:left}.has-text-align-right{text-align:right}#end-resizable-editor-section{display:none}.aligncenter{clear:both}.items-justified-left{justify-content:flex-start}.items-justified-center{justify-content:center}.items-justified-right{justify-content:flex-end}.items-justified-space-between{justify-content:space-between}.screen-reader-text{border:0;clip:rect(1px,1px,1px,1px);-webkit-clip-path:inset(50%);clip-path:inset(50%);height:1px;margin:-1px;overflow:hidden;padding:0;position:absolute;width:1px;word-wrap:normal!important}.screen-reader-text:focus{background-color:#ddd;clip:auto!important;-webkit-clip-path:none;clip-path:none;color:#444;display:block;font-size:1em;height:auto;left:5px;line-height:normal;padding:15px 23px 14px;text-decoration:none;top:5px;width:auto;z-index:100000}html :where(.has-border-color){border-style:solid}html :where([style*=border-top-color]){border-top-style:solid}html :where([style*=border-right-color]){border-right-style:solid}html :where([style*=border-bottom-color]){border-bottom-style:solid}html :where([style*=border-left-color]){border-left-style:solid}html :where([style*=border-width]){border-style:solid}html :where([style*=border-top-width]){border-top-style:solid}html :where([style*=border-right-width]){border-right-style:solid}html :where([style*=border-bottom-width]){border-bottom-style:solid}html :where([style*=border-left-width]){border-left-style:solid}html :where(img[class*=wp-image-]){height:auto;max-width:100%}:where(figure){margin:0 0 1em}html :where(.is-position-sticky){--wp-admin--admin-bar--position-offset:var(--wp-admin--admin-bar--height,0px)}@media screen and (max-width:600px){html :where(.is-position-sticky){--wp-admin--admin-bar--position-offset:0px}}
is_rejected( $resource['url'] ) || get_transient( 'wp_rocket_updating' ) ) {
return false;
}
// check the database if those resources added before.
$rows = $this->query(
[
'url' => $url,
],
false
);
if ( count( $rows ) === 0 ) {
// Create this new row in DB.
$resource_id = $this->add_item(
[
'url' => $url,
'status' => key_exists( 'status', $resource ) ? $resource['status'] : 'pending',
'is_locked' => key_exists( 'is_locked', $resource ) ? $resource['is_locked'] : false,
'last_accessed' => current_time( 'mysql', true ),
]
);
if ( $resource_id ) {
return $resource_id;
}
$this->logger->error( "Cannot insert {$resource['url']} into {$this->table_name}" );
return false;
}
$db_row = array_pop( $rows );
$data = [
'url' => $url,
'status' => key_exists( 'status', $resource ) ? $resource['status'] : $db_row->status,
'is_locked' => key_exists( 'is_locked', $resource ) ? $resource['is_locked'] : $db_row->is_locked,
'modified' => current_time( 'mysql', true ),
];
if ( key_exists( 'last_accessed', $resource ) && (bool) $resource['last_accessed'] ) {
$data['last_accessed'] = current_time( 'mysql', true );
}
// Update this row with the new content.
$this->update_item(
$db_row->id,
$data
);
return $db_row->id;
}
/**
* Create new resource row or update its contents if not created before.
*
* @since 3.9
*
* @param array $resource Resource array.
*
* @return bool
*/
public function create_or_nothing( array $resource ) { // phpcs:ignore Universal.NamingConventions.NoReservedKeywordParameterNames.resourceFound
if ( $this->is_rejected( $resource['url'] ) ) {
return false;
}
/**
* Format the url.
*
* @param string $url url to format.
* @return string
*/
$url = apply_filters( 'rocket_preload_format_url', $resource['url'] );
$url = strtok( $url, '?' );
// check the database if those resources added before.
$rows = $this->query(
[
'url' => untrailingslashit( $url ),
],
false
);
if ( count( $rows ) > 0 ) {
return false;
}
// Create this new row in DB.
$resource_id = $this->add_item(
[
'url' => untrailingslashit( $url ),
'status' => key_exists( 'status', $resource ) ? $resource['status'] : 'pending',
'is_locked' => key_exists( 'is_locked', $resource ) ? $resource['is_locked'] : false,
'last_accessed' => current_time( 'mysql', true ),
]
);
if ( $resource_id ) {
return $resource_id;
}
$this->logger->error( "Cannot insert {$resource['url']} into {$this->table_name}" );
return false;
}
/**
* Get all rows with the same url (desktop and mobile versions).
*
* @param string $url Page url.
*
* @return array|false
*/
public function get_rows_by_url( string $url ) {
$url = strtok( $url, '?' );
$query = $this->query(
[
'url' => untrailingslashit( $url ),
]
);
if ( empty( $query ) ) {
return false;
}
return $query;
}
/**
* Delete DB row by url.
*
* @param string $url Page url to be deleted.
*
* @return bool
*/
public function delete_by_url( string $url ) {
$items = $this->get_rows_by_url( $url );
if ( ! $items ) {
return false;
}
$deleted = true;
foreach ( $items as $item ) {
if ( ! is_bool( $item ) ) {
$deleted = $deleted && $this->delete_item( $item->id );
}
}
return $deleted;
}
/**
* Get all preload caches which were not accessed in the last month.
*
* @param float $delay delay before the not accessed row is deleted.
* @param string $unit unit from the delay.
* @return array
*/
public function get_old_cache( float $delay = 1, string $unit = 'month' ): array {
// Get the database interface.
$db = $this->get_db();
// Bail if no database interface is available.
if ( empty( $db ) ) {
return [];
}
$prefixed_table_name = $db->prefix . $this->table_name;
$query = "SELECT id FROM `$prefixed_table_name` WHERE `last_accessed` <= date_sub(now(), interval $delay $unit)";
$rows_affected = $db->get_results( $query );
return $rows_affected;
}
/**
* Remove all completed rows one by one.
*
* @param float $delay delay before the not accessed row is deleted.
* @param string $unit unit from the delay.
* @return void
*/
public function remove_all_not_accessed_rows( float $delay = 1, string $unit = 'month' ) {
$rows = $this->get_old_cache( $delay, $unit );
foreach ( $rows as $row ) {
if ( ! is_bool( $row ) ) {
$this->delete_item( $row->id );
}
}
}
/**
* Fetch pending jobs.
*
* @param int $total total of jobs to fetch.
* @return array
*/
public function get_pending_jobs( int $total = 45 ) {
$inprogress_count = $this->query(
[
'count' => true,
'status' => 'in-progress',
'is_locked' => false,
],
false
);
if ( $total <= 0 || $inprogress_count >= $total ) {
return [];
}
$orderby = 'modified';
/**
* Filter order for preloading pending urls.
*
* @param bool $orderby order for preloading pending urls.
*
* @returns bool
*/
if ( apply_filters( 'rocket_preload_order', false ) ) {
$orderby = 'id';
}
return $this->query(
[
'number' => ( $total - $inprogress_count ),
'status' => 'pending',
'fields' => [
'id',
'url',
],
'job_id__not_in' => [
'not_in' => '',
],
'is_locked' => false,
'orderby' => $orderby,
'order' => 'asc',
]
);
}
/**
* Change the status from the task to inprogress.
*
* @param int $id id from the task.
* @return bool
*/
public function make_status_inprogress( int $id ) {
return $this->update_item(
$id,
[
'status' => 'in-progress',
'modified' => current_time( 'mysql', true ),
]
);
}
/**
* Make the status from the task to complete.
*
* @param string $url url from the task.
* @return bool
*/
public function make_status_complete( string $url ) {
$tasks = $this->query(
[
'url' => $url,
]
);
if ( count( $tasks ) === 0 ) {
return false;
}
$task = array_pop( $tasks );
return $this->update_item(
$task->id,
[
'status' => 'completed',
'modified' => current_time( 'mysql', true ),
]
);
}
/**
* Check if pending jobs are remaining.
*
* @return bool
*/
public function has_pending_jobs() {
$pending_count = $this->query(
[
'count' => true,
'status' => 'pending',
]
);
return 0 !== $pending_count;
}
/**
* Revert in-progress urls.
*
* @return void
*/
public function revert_in_progress() {
$in_progress_list = $this->query(
[
'status' => 'in-progress',
]
);
foreach ( $in_progress_list as $in_progress ) {
$this->update_item(
$in_progress->id,
[
'status' => 'pending',
'modified' => current_time( 'mysql', true ),
]
);
}
}
/**
* Revert old in-progress rows
*
* @depecated
*/
public function revert_old_in_progress() {
// Get the database interface.
$db = $this->get_db();
// Bail if no database interface is available.
if ( empty( $db ) ) {
return false;
}
$prefixed_table_name = $db->prefix . $this->table_name;
$db->query( "UPDATE `$prefixed_table_name` SET status = 'pending', modified = '" . current_time( 'mysql', true ) . "' WHERE status = 'in-progress' AND `modified` <= date_sub(now(), interval 12 hour)" );
}
/**
* Revert old failed rows.
*/
public function revert_old_failed() {
// Get the database interface.
$db = $this->get_db();
// Bail if no database interface is available.
if ( empty( $db ) ) {
return false;
}
$prefixed_table_name = $db->prefix . $this->table_name;
return $db->query( "UPDATE `$prefixed_table_name` SET status = 'pending', modified = '" . current_time( 'mysql', true ) . "' WHERE status = 'failed' AND `modified` <= date_sub(now(), interval 12 hour)" );
}
/**
* Set all rows to pending.
*/
public function set_all_to_pending() {
// Get the database interface.
$db = $this->get_db();
// Bail if no database interface is available.
if ( empty( $db ) ) {
return false;
}
$prefixed_table_name = $db->prefix . $this->table_name;
/**
* Filter condition for cleaning URLS in the database.
*
* @param string $condition condition for cleaning URLS in the database.
* @returns string
*/
$condition = apply_filters( 'rocket_preload_all_to_pending_condition', ' WHERE 1 = 1' );
$db->query( "UPDATE `$prefixed_table_name` SET status = 'pending', modified = '" . current_time( 'mysql', true ) . "'$condition" );
}
/**
* Check if the page is preloaded.
*
* @param string $url url from the page to check.
* @return bool
*/
public function is_preloaded( string $url ): bool {
$pending_count = $this->query(
[
'count' => true,
'status' => 'in-progress',
'url' => untrailingslashit( $url ),
]
);
return 0 !== $pending_count;
}
/**
* Check if the page is pending.
*
* @param string $url url from the page to check.
* @return bool
*/
public function is_pending( string $url ): bool {
$pending_count = $this->query(
[
'count' => true,
'status' => 'pending',
'url' => untrailingslashit( $url ),
]
);
return 0 !== $pending_count;
}
/**
* Remove all entries from the table.
*
* @return false|void
*/
public function remove_all() {
// Get the database interface.
$db = $this->get_db();
// Bail if no database interface is available.
if ( empty( $db ) ) {
return false;
}
$prefixed_table_name = $db->prefix . $this->table_name;
$db->query( "DELETE FROM `$prefixed_table_name` WHERE 1 = 1" );
}
/**
* Lock a URL.
*
* @param string $url URL to lock.
*
* @return void
*/
public function lock( string $url ) {
$this->create_or_update(
[
'url' => $url,
'is_locked' => true,
]
);
}
/**
* Unlock all URLs.
*
* @return false|void
*/
public function unlock_all() {
// Get the database interface.
$db = $this->get_db();
// Bail if no database interface is available.
if ( empty( $db ) ) {
return false;
}
$prefixed_table_name = $db->prefix . $this->table_name;
$db->query( "UPDATE `$prefixed_table_name` SET is_locked = false;" );
}
/**
* Unlock a URL.
*
* @param string $url URL to unlock.
*
* @return void
*/
public function unlock( string $url ) {
$this->create_or_update(
[
'url' => $url,
'is_locked' => false,
]
);
}
/**
* Check if the url is rejected.
*
* @param string $url url to check.
* @return bool
*/
protected function is_rejected( string $url ): bool {
$extensions = [
'php' => 1,
'xml' => 1,
'xsl' => 1,
'kml' => 1,
];
$extension = pathinfo( $url, PATHINFO_EXTENSION );
return $extension && isset( $extensions[ $extension ] );
}
/**
* Make the status from the task to failed.
*
* @param int $id id from the task.
* @return bool
*/
public function make_status_failed( int $id ) {
return $this->update_item(
$id,
[
'status' => 'failed',
'modified' => current_time( 'mysql', true ),
]
);
}
/**
* Update last accessed from the row.
*
* @param int $id id from the row.
* @return bool
*/
public function update_last_access( int $id ) {
return $this->update_item(
$id,
[
'last_accessed' => current_time( 'mysql', true ),
]
);
}
/**
* Return outdated in-progress jobs.
*
* @param int $delay delay to delete.
* @return array|int
*/
public function get_outdated_in_progress_jobs( int $delay = 3 ) {
return $this->query(
[
'status' => 'in-progress',
'is_locked' => false,
'date_query' => [
[
'column' => 'modified',
'before' => "$delay minute ago",
],
],
],
false
);
}
}