Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
80.00% covered (warning)
80.00%
12 / 15
75.00% covered (warning)
75.00%
3 / 4
CRAP
0.00% covered (danger)
0.00%
0 / 1
Lock
92.31% covered (success)
92.31%
12 / 13
75.00% covered (warning)
75.00%
3 / 4
9.04
0.00% covered (danger)
0.00%
0 / 1
 acquire
100.00% covered (success)
100.00%
10 / 10
100.00% covered (success)
100.00%
1 / 1
6
 release
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 delete_all
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 option_name
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
1<?php
2/**
3 * Short locks between requests.
4 *
5 * @package automattic/jetpack-send
6 */
7
8namespace Automattic\Jetpack\Send;
9
10if ( ! defined( 'ABSPATH' ) ) {
11    exit( 0 );
12}
13
14// phpcs:disable WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- A lock must read the database, never a cache.
15
16/**
17 * One options row per lock, taken with `INSERT IGNORE` the way core's upgrader takes its own.
18 * `add_option()` isn't a lock: it's an upsert, so two requests could both get it.
19 */
20class Lock {
21
22    /**
23     * Option name prefix; the lock's name is appended.
24     *
25     * @var string
26     */
27    const PREFIX = 'jetpack_send_lock_';
28
29    /**
30     * Take a lock, or replace one older than its stale time.
31     *
32     * @since $$next-version$$
33     *
34     * @param string $name        Lock name, e.g. `upgrade` or `resend_12`.
35     * @param int    $stale_after Seconds after which a lock left by a request that died can be taken over.
36     * @return bool Whether this request holds the lock now.
37     */
38    public static function acquire( $name, $stale_after ) {
39        global $wpdb;
40        $option = self::option_name( $name );
41        $now    = time();
42
43        $result = $wpdb->query( $wpdb->prepare( "INSERT IGNORE INTO %i ( option_name, option_value, autoload ) VALUES ( %s, %s, 'off' )", $wpdb->options, $option, (string) $now ) );
44        if ( false !== $result && (int) $wpdb->rows_affected > 0 ) {
45            return true;
46        }
47
48        $held = $wpdb->get_var( $wpdb->prepare( 'SELECT option_value FROM %i WHERE option_name = %s', $wpdb->options, $option ) );
49        if ( null === $held || (int) $held > $now - (int) $stale_after ) {
50            return false;
51        }
52        // Only one request can swap out the stale time it read.
53        $result = $wpdb->query( $wpdb->prepare( 'UPDATE %i SET option_value = %s WHERE option_name = %s AND option_value = %s', $wpdb->options, (string) $now, $option, (string) $held ) );
54        return false !== $result && (int) $wpdb->rows_affected > 0;
55    }
56
57    /**
58     * Give a lock back.
59     *
60     * @since $$next-version$$
61     *
62     * @param string $name Lock name.
63     * @return void
64     */
65    public static function release( $name ) {
66        global $wpdb;
67        $wpdb->delete( $wpdb->options, array( 'option_name' => self::option_name( $name ) ) );
68    }
69
70    /**
71     * Remove every Send lock (uninstall).
72     *
73     * @since $$next-version$$
74     *
75     * @return void
76     */
77    public static function delete_all() {
78        global $wpdb;
79        $wpdb->query( $wpdb->prepare( 'DELETE FROM %i WHERE option_name LIKE %s', $wpdb->options, $wpdb->esc_like( self::PREFIX ) . '%' ) );
80    }
81
82    /**
83     * The option that holds a lock.
84     *
85     * @param string $name Lock name.
86     * @return string
87     */
88    public static function option_name( $name ) {
89        return self::PREFIX . preg_replace( '/[^a-z0-9_]/', '', strtolower( (string) $name ) );
90    }
91}