Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
42.86% |
36 / 84 |
|
18.18% |
2 / 11 |
CRAP | |
0.00% |
0 / 1 |
Addresses_List_Table | |
42.86% |
36 / 84 |
|
18.18% |
2 / 11 |
121.71 | |
0.00% |
0 / 1 |
__construct | |
0.00% |
0 / 6 |
|
0.00% |
0 / 1 |
2 | |||
get_columns | |
0.00% |
0 / 14 |
|
0.00% |
0 / 1 |
20 | |||
get_cached_bitcoin_address_object | |
0.00% |
0 / 3 |
|
0.00% |
0 / 1 |
6 | |||
column_title | |
100.00% |
8 / 8 |
|
100.00% |
1 / 1 |
1 | |||
column_status | |
0.00% |
0 / 2 |
|
0.00% |
0 / 1 |
2 | |||
column_order_id | |
0.00% |
0 / 6 |
|
0.00% |
0 / 1 |
6 | |||
column_transactions_count | |
0.00% |
0 / 5 |
|
0.00% |
0 / 1 |
6 | |||
column_received | |
0.00% |
0 / 2 |
|
0.00% |
0 / 1 |
2 | |||
column_wallet | |
88.89% |
24 / 27 |
|
0.00% |
0 / 1 |
6.05 | |||
column_derive_path_sequence | |
100.00% |
4 / 4 |
|
100.00% |
1 / 1 |
1 | |||
edit_row_actions | |
0.00% |
0 / 7 |
|
0.00% |
0 / 1 |
6 |
1 | <?php |
2 | /** |
3 | * Display generated addresses, their status and related orders. |
4 | * |
5 | * TODO: Add filters for status, wallet address. |
6 | * TODO: Hijack Add New button to generate new addresses. |
7 | * |
8 | * @package brianhenryie/bh-wp-bitcoin-gateway |
9 | */ |
10 | |
11 | namespace BrianHenryIE\WP_Bitcoin_Gateway\Admin; |
12 | |
13 | use BrianHenryIE\WP_Bitcoin_Gateway\API\Addresses\Bitcoin_Address; |
14 | use BrianHenryIE\WP_Bitcoin_Gateway\API\Addresses\Bitcoin_Address_Factory; |
15 | use BrianHenryIE\WP_Bitcoin_Gateway\API\Addresses\Bitcoin_Wallet; |
16 | use BrianHenryIE\WP_Bitcoin_Gateway\API_Interface; |
17 | use BrianHenryIE\WP_Bitcoin_Gateway\WooCommerce\Bitcoin_Gateway; |
18 | use Exception; |
19 | use WP_Post; |
20 | |
21 | /** |
22 | * Hooks into standard WP_List_Table actions and filters. |
23 | * |
24 | * @see wp-admin/edit.php?post_type=bh-bitcoin-address |
25 | * @see WP_Posts_List_Table |
26 | */ |
27 | class Addresses_List_Table extends \WP_Posts_List_Table { |
28 | |
29 | /** |
30 | * |
31 | * |
32 | * @uses API_Interface::get_bitcoin_gateways() |
33 | */ |
34 | protected API_Interface $api; |
35 | |
36 | /** |
37 | * Constructor |
38 | * |
39 | * @see _get_list_table() |
40 | * |
41 | * @param array{screen?:\WP_Screen} $args The data passed by WordPress. |
42 | */ |
43 | public function __construct( $args = array() ) { |
44 | parent::__construct( $args ); |
45 | |
46 | $post_type_name = $this->screen->post_type; |
47 | |
48 | /** |
49 | * Since this object is instantiated because it was defined when registering the post type, it's |
50 | * extremely unlikely the post type will not exist. |
51 | * |
52 | * @var \WP_Post_Type $post_type_object |
53 | */ |
54 | $post_type_object = get_post_type_object( $post_type_name ); |
55 | $this->api = $post_type_object->plugin_objects['api']; |
56 | |
57 | add_filter( 'post_row_actions', array( $this, 'edit_row_actions' ), 10, 2 ); |
58 | } |
59 | |
60 | /** |
61 | * Cache to avoid repeatedly instantiating each Bitcoin_Address. |
62 | * |
63 | * @var array<int, Bitcoin_Address> |
64 | */ |
65 | protected array $addresses_cache = array(); |
66 | |
67 | /** |
68 | * Cache to avoid repeatedly instantiating each Bitcoin_Wallet. |
69 | * |
70 | * @var array<int, Bitcoin_Wallet> |
71 | */ |
72 | protected array $wallet_cache = array(); |
73 | |
74 | /** |
75 | * When rendering the wallet column, we will link to the gateways it is being used in. |
76 | * |
77 | * @var array<int, array<Bitcoin_Gateway>> |
78 | */ |
79 | protected array $wallet_id_to_gateways_map = array(); |
80 | |
81 | /** |
82 | * Define the custom columns for the post type. |
83 | * Status|Order|Transactions|Received|Wallet|Derivation path. |
84 | * |
85 | * @return array<string, string> Column name : HTML output. |
86 | */ |
87 | public function get_columns() { |
88 | $columns = parent::get_columns(); |
89 | |
90 | $new_columns = array(); |
91 | foreach ( $columns as $key => $column ) { |
92 | |
93 | // Omit the "comments" column. |
94 | if ( 'comments' === $key ) { |
95 | continue; |
96 | } |
97 | |
98 | // Add remaining columns after the Title column. |
99 | $new_columns[ $key ] = $column; |
100 | if ( 'title' === $key ) { |
101 | |
102 | $new_columns['status'] = 'Status'; |
103 | $new_columns['order_id'] = 'Order'; |
104 | $new_columns['transactions_count'] = 'Transactions'; |
105 | $new_columns['received'] = 'Received'; |
106 | $new_columns['wallet'] = 'Wallet'; |
107 | $new_columns['derive_path_sequence'] = 'Path'; |
108 | } |
109 | // The date column will be added last. |
110 | } |
111 | |
112 | return $new_columns; |
113 | } |
114 | |
115 | /** |
116 | * Given a WP_Post, get the corresponding Bitcoin_Address object, using a local array to cache for this request/object. |
117 | * |
118 | * @param WP_Post $post The post the address information is stored under. |
119 | * |
120 | * @return Bitcoin_Address |
121 | * @throws Exception When the post/post id does not match a bh-bitcoin-address cpt. |
122 | */ |
123 | protected function get_cached_bitcoin_address_object( WP_Post $post ): Bitcoin_Address { |
124 | if ( ! isset( $this->addresses_cache[ $post->ID ] ) ) { |
125 | $this->addresses_cache[ $post->ID ] = new Bitcoin_Address( $post->ID ); |
126 | } |
127 | return $this->addresses_cache[ $post->ID ]; |
128 | } |
129 | |
130 | /** |
131 | * For now, let's link to an external site when the address is clicked. |
132 | * That way we can skip working on a single post view, and also provide an authoritative view of the address information. |
133 | * |
134 | * @param WP_Post $post The post this row is being rendered for. |
135 | * |
136 | * @return void Echos HTML. |
137 | */ |
138 | public function column_title( $post ) { |
139 | ob_start(); |
140 | parent::column_title( $post ); |
141 | $render = (string) ob_get_clean(); |
142 | |
143 | $bitcoin_address = $this->get_cached_bitcoin_address_object( $post ); |
144 | |
145 | $link = esc_url( "https://www.blockchain.com/btc/address/{$bitcoin_address->get_raw_address()}" ); |
146 | |
147 | $render = (string) preg_replace( '/(.*<a.*)(href=")([^"]*)(".*>)/', '$1$2' . $link . '$4', $render, 1 ); |
148 | |
149 | $render = (string) preg_replace( '/<a\s/', '<a target="_blank" ', $render, 1 ); |
150 | |
151 | echo $render; |
152 | } |
153 | |
154 | /** |
155 | * |
156 | * @param WP_Post $item The post this row is being rendered for. |
157 | * |
158 | * @return void Echos HTML. |
159 | */ |
160 | public function column_status( WP_Post $item ) { |
161 | |
162 | $bitcoin_address = $this->get_cached_bitcoin_address_object( $item ); |
163 | |
164 | echo esc_html( $bitcoin_address->get_status() ); |
165 | } |
166 | |
167 | /** |
168 | * |
169 | * @param WP_Post $item The post this row is being rendered for. |
170 | * |
171 | * @return void Echos HTML. |
172 | */ |
173 | public function column_order_id( WP_Post $item ) { |
174 | |
175 | $bitcoin_address = $this->get_cached_bitcoin_address_object( $item ); |
176 | |
177 | $order_id = $bitcoin_address->get_order_id(); |
178 | if ( ! is_null( $order_id ) ) { |
179 | $url = admin_url( "post.php?post={$order_id}&action=edit" ); |
180 | $order_id = (string) $order_id; |
181 | echo '<a href="' . esc_url( $url ) . '">' . esc_html( $order_id ) . '</a>'; |
182 | } |
183 | } |
184 | |
185 | /** |
186 | * |
187 | * @param WP_Post $item The post this row is being rendered for. |
188 | * |
189 | * @return void Echos HTML. |
190 | */ |
191 | public function column_transactions_count( WP_Post $item ) { |
192 | |
193 | $bitcoin_address = $this->get_cached_bitcoin_address_object( $item ); |
194 | |
195 | $transactions = $bitcoin_address->get_blockchain_transactions(); |
196 | if ( is_array( $transactions ) ) { |
197 | echo count( $transactions ); |
198 | } else { |
199 | echo ''; |
200 | } |
201 | } |
202 | |
203 | /** |
204 | * |
205 | * @param WP_Post $item The post this row is being rendered for. |
206 | * |
207 | * @return void Echos HTML. |
208 | */ |
209 | public function column_received( WP_Post $item ) { |
210 | |
211 | $bitcoin_address = $this->get_cached_bitcoin_address_object( $item ); |
212 | |
213 | echo esc_html( $bitcoin_address->get_balance() ?? 'unknown' ); |
214 | } |
215 | |
216 | /** |
217 | * Print the HTML for the "wallet" column. |
218 | * |
219 | * Most sites will probably only ever use one wallet. Others might change wallet once or twice. Some will have |
220 | * multiple instances of the gateway running at the same time. |
221 | * |
222 | * TODO: Currently, this links to the WC_Payment_Gateway page. Maybe it should link externally like the |
223 | * title column? |
224 | * |
225 | * @param WP_Post $item The post this row is being rendered for. |
226 | * |
227 | * @return void Echos HTML. |
228 | */ |
229 | public function column_wallet( WP_Post $item ) { |
230 | |
231 | try { |
232 | $bitcoin_address = $this->get_cached_bitcoin_address_object( $item ); |
233 | } catch ( Exception $exception ) { |
234 | return; |
235 | } |
236 | |
237 | $wallet_post_id = $bitcoin_address->get_wallet_parent_post_id(); |
238 | $wallet_post = get_post( $wallet_post_id ); |
239 | if ( ! $wallet_post ) { |
240 | // TODO: echo/log error. |
241 | return; |
242 | } |
243 | $wallet_address = $wallet_post->post_excerpt; |
244 | $abbreviated = substr( $wallet_address, 0, 7 ) . '...' . substr( $wallet_address, -3 ); |
245 | |
246 | // Is this wallet being used by a gateway? |
247 | if ( ! isset( $this->wallet_id_to_gateways_map[ $wallet_post_id ] ) ) { |
248 | $this->wallet_id_to_gateways_map[ $wallet_post_id ] = array_filter( |
249 | $this->api->get_bitcoin_gateways(), |
250 | function ( Bitcoin_Gateway $gateway ) use ( $wallet_address ): bool { |
251 | return $gateway->get_xpub() === $wallet_address; |
252 | } |
253 | ); |
254 | } |
255 | $gateways = $this->wallet_id_to_gateways_map[ $wallet_post_id ]; |
256 | |
257 | $href_html = ''; |
258 | if ( 1 === count( $gateways ) ) { |
259 | $gateway = array_pop( $gateways ); |
260 | $href_html = '<a href="' . esc_url( admin_url( "admin.php?page=wc-settings&tab=checkout§ion={$gateway->id}" ) ) . '">'; |
261 | } |
262 | |
263 | echo '<span title="' . esc_attr( $wallet_address ) . '">'; |
264 | echo wp_kses_post( $href_html ); |
265 | echo esc_html( $abbreviated ); |
266 | if ( ! empty( $href_html ) ) { |
267 | echo '</a>'; } |
268 | echo '</span>'; |
269 | } |
270 | |
271 | /** |
272 | * TODO: This should be sortable, and in theory should match the ID asc/desc sequence. |
273 | * |
274 | * @param WP_Post $item The post this row is being rendered for. |
275 | * |
276 | * @return void Echos HTML. |
277 | */ |
278 | public function column_derive_path_sequence( WP_Post $item ) { |
279 | |
280 | $bitcoin_address = $this->get_cached_bitcoin_address_object( $item ); |
281 | |
282 | $nth = $bitcoin_address->get_derivation_path_sequence_number(); |
283 | $path = "0/$nth"; |
284 | echo esc_html( $path ); |
285 | } |
286 | |
287 | /** |
288 | * Remove edit and view actions, add an update action. |
289 | * |
290 | * TODO: add a click handler to the update (query for new transactions) action. |
291 | * |
292 | * @hooked post_row_actions |
293 | * @see \WP_Posts_List_Table::handle_row_actions() |
294 | * |
295 | * @param array<string,string> $actions Action id : HTML. |
296 | * @param WP_Post $post The post object. |
297 | * |
298 | * @return array<string,string> |
299 | */ |
300 | public function edit_row_actions( array $actions, WP_Post $post ): array { |
301 | |
302 | if ( Bitcoin_Address::POST_TYPE !== $post->post_type ) { |
303 | return $actions; |
304 | } |
305 | |
306 | unset( $actions['edit'] ); |
307 | unset( $actions['inline hide-if-no-js'] ); // "quick edit". |
308 | unset( $actions['view'] ); |
309 | |
310 | $actions['update_address'] = 'Update'; |
311 | |
312 | return $actions; |
313 | } |
314 | } |