Repository navigation
Expand file tree
/
Copy pathDocumentIndex.php
More file actions
174 lines (156 loc) · 5.45 KB
/
Copy pathDocumentIndex.php
File metadata and controls
174 lines (156 loc) · 5.45 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
<?php
/**
* Finding local document records.
*
* @package Assinafy\WP
*/
declare(strict_types=1);
namespace Assinafy\WP\Documents;
defined( 'ABSPATH' ) || exit;
/**
* The queries that locate document mirrors, as opposed to reading one.
*
* Separate from {@see DocumentRecord} because they are a different kind of work with a
* different cost: each one is a `get_posts()` over the whole post type with a meta join, and
* every one of them is deliberately bounded — by a `numberposts` limit, by an exact match on
* an indexed key, or by a caller-supplied page size. Reading a field off a record that is
* already in hand costs nothing and needs none of that care.
*
* There is no index on signer email: the privacy exporter is required to page anyway, and a
* serialised signer list cannot be searched reliably in SQL.
*/
final class DocumentIndex {
/**
* Find the local post mirroring a remote document, or 0.
*
* @param string $document_id Remote document id, 26-28 opaque hex characters.
*/
public function find_by_document_id( string $document_id ): int {
if ( ! DocumentRecord::is_valid_id( $document_id ) ) {
return 0;
}
$posts = get_posts(
array(
'post_type' => DocumentPostType::POST_TYPE,
'post_status' => 'any',
'numberposts' => 1,
'fields' => 'ids',
'no_found_rows' => true,
'update_post_meta_cache' => false,
'update_post_term_cache' => false,
'ignore_sticky_posts' => true,
// phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_key -- The mirror is addressed by remote id; there is no other way in.
'meta_key' => DocumentRecord::META_DOCUMENT_ID,
// phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_value -- Exact match on an indexed meta_key, one row.
'meta_value' => $document_id,
)
);
return isset( $posts[0] ) ? (int) $posts[0] : 0;
}
/**
* Find a reserved send, including one whose mirror has since been trashed.
*
* @param string $key Opaque send identity.
*/
public function find_by_send_key( string $key ): int {
if ( '' === $key ) {
return 0;
}
$posts = get_posts(
array(
'post_type' => DocumentPostType::POST_TYPE,
'post_status' => array_values( get_post_stati() ),
'numberposts' => 1,
'fields' => 'ids',
'no_found_rows' => true,
'update_post_meta_cache' => false,
'update_post_term_cache' => false,
// phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_key -- One reserved request is addressed by its send identity.
'meta_key' => DocumentRecord::META_SEND_KEY,
// phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_value -- Exact match on one indexed meta key, limited to one row.
'meta_value' => $key,
)
);
return isset( $posts[0] ) ? (int) $posts[0] : 0;
}
/**
* Open records, least recently synced first — the reconcile cron's work queue.
*
* @param int $limit Maximum records to return.
*
* @return array<int, int> Local post ids.
*/
public function stale_post_ids( int $limit ): array {
if ( $limit < 1 ) {
return array();
}
$posts = get_posts(
array(
'post_type' => DocumentPostType::POST_TYPE,
'post_status' => 'any',
'numberposts' => $limit,
'fields' => 'ids',
'no_found_rows' => true,
'update_post_term_cache' => false,
'ignore_sticky_posts' => true,
'orderby' => 'meta_value_num',
'order' => 'ASC',
// phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_key -- Ordering the queue by staleness requires the timestamp join.
'meta_key' => DocumentRecord::META_SYNCED_AT,
// phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_query -- Bounded by $limit and run once an hour from cron.
'meta_query' => array(
array(
'key' => DocumentRecord::META_DOCUMENT_ID,
'compare' => 'EXISTS',
),
array(
'relation' => 'OR',
array(
'key' => DocumentRecord::META_IS_CLOSED,
'value' => '1',
'compare' => '!=',
),
array(
'key' => DocumentRecord::META_IS_CLOSED,
'compare' => 'NOT EXISTS',
),
),
),
)
);
return array_map( 'intval', $posts );
}
/**
* A page of every record the plugin owns, newest first.
*
* The privacy exporter and eraser walk the mirror this way: there is no index on signer
* email, because an exporter is required to page anyway and a serialised signer list
* cannot be searched reliably in SQL.
*
* @param int $limit Page size.
* @param int $offset Records to skip.
*
* @return array<int, int> Local post ids.
*/
public function post_ids( int $limit, int $offset = 0 ): array {
if ( $limit < 1 ) {
return array();
}
$posts = get_posts(
array(
'post_type' => DocumentPostType::POST_TYPE,
'post_status' => array_values( get_post_stati() ),
'numberposts' => $limit,
'offset' => max( 0, $offset ),
'fields' => 'ids',
'orderby' => 'ID',
'order' => 'DESC',
'no_found_rows' => true,
'update_post_meta_cache' => false,
'update_post_term_cache' => false,
'ignore_sticky_posts' => true,
)
);
return array_map( 'intval', $posts );
}
}