PluginProbe
ActivityPub / trunk
ActivityPub vtrunk
9.3.1 9.3.0 9.2.2 9.2.1 9.2.0 9.1.0 9.0.2 9.0.1 9.0.0 8.3.0 8.2.1 8.2.0 8.1.1 1.0.5 1.0.6 1.0.7 1.0.8 1.0.9 1.1.0 1.2.0 1.3.0 2.0.0 2.0.1 2.1.0 2.1.1 All 160 releases
activitypub / includes / wp-admin / import / class-blocklist.php

class-blocklist.php in ActivityPub trunk, at includes/wp-admin/import/class-blocklist.php

323 lines 9.8 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Blocklist importer file.
4 *
5 * @package Activitypub
6 */
7
8 namespace Activitypub\WP_Admin\Import;
9
10 use Activitypub\Blocklist_Subscriptions;
11 use Activitypub\Moderation;
12
13 /**
14 * Blocklist importer class.
15 *
16 * Imports domain blocklists in CSV format (Mastodon, IFTAS DNI, etc.)
17 */
18 class Blocklist {
19
20 /**
21 * Dispatch the importer based on current step.
22 */
23 public static function dispatch() {
24 // phpcs:ignore WordPress.Security.NonceVerification.Recommended
25 $step = \absint( $_GET['step'] ?? 0 );
26
27 self::header();
28
29 switch ( $step ) {
30 case 0:
31 self::greet();
32 break;
33
34 case 1:
35 \check_admin_referer( 'import-upload' );
36 self::handle_upload();
37 break;
38
39 case 2:
40 \check_admin_referer( 'import-blocklist-url' );
41 self::handle_url_import();
42 break;
43 }
44
45 self::footer();
46 }
47
48 /**
49 * Display the importer header.
50 */
51 private static function header() {
52 echo '<div class="wrap">';
53 echo '<h2>' . \esc_html__( 'Import Domain Blocklist', 'activitypub' ) . '</h2>';
54 }
55
56 /**
57 * Display the importer footer.
58 */
59 private static function footer() {
60 echo '</div>';
61 }
62
63 /**
64 * Display the greeting/intro screen.
65 */
66 private static function greet() {
67 echo '<div class="narrow">';
68 echo '<p>' . \esc_html__( 'Import a domain blocklist to block multiple ActivityPub instances at once. Supported formats:', 'activitypub' ) . '</p>';
69 echo '<ul>';
70 echo '<li>' . \esc_html__( 'Mastodon CSV export (with #domain header)', 'activitypub' ) . '</li>';
71 echo '<li>' . \esc_html__( 'Simple text file with one domain per line', 'activitypub' ) . '</li>';
72 echo '</ul>';
73
74 // File upload option.
75 \printf( '<h3>%s</h3>', \esc_html__( 'Option 1: Upload a File', 'activitypub' ) );
76 \wp_import_upload_form( 'admin.php?import=blocklist&amp;step=1' );
77
78 // URL import option.
79 \printf( '<h3>%s</h3>', \esc_html__( 'Option 2: Import from URL', 'activitypub' ) );
80 ?>
81 <form id="import-url-form" method="post" action="<?php echo \esc_url( \admin_url( 'admin.php?import=blocklist&amp;step=2' ) ); ?>">
82 <?php \wp_nonce_field( 'import-blocklist-url' ); ?>
83 <p>
84 <label for="import_url"><?php \esc_html_e( 'Blocklist URL:', 'activitypub' ); ?><br />
85 <input type="url" id="import_url" name="import_url" size="50" class="code" placeholder="https://example.com/blocklist.csv" required />
86 </label>
87 </p>
88 <p>
89 <label>
90 <input type="checkbox" name="subscribe" value="1" />
91 <?php \esc_html_e( 'Subscribe for automatic weekly updates', 'activitypub' ); ?>
92 </label>
93 </p>
94 <p class="submit">
95 <input type="submit" name="submit" id="submit" class="button" value="<?php \esc_attr_e( 'Import from URL', 'activitypub' ); ?>" />
96 </p>
97 </form>
98
99 <h4><?php \esc_html_e( 'Quick Import', 'activitypub' ); ?></h4>
100 <p><?php \esc_html_e( 'Import from a well-known blocklist:', 'activitypub' ); ?></p>
101 <form method="post" action="<?php echo \esc_url( \admin_url( 'admin.php?import=blocklist&amp;step=2' ) ); ?>">
102 <?php \wp_nonce_field( 'import-blocklist-url' ); ?>
103 <input type="hidden" name="import_url" value="<?php echo \esc_attr( Blocklist_Subscriptions::IFTAS_DNI_URL ); ?>" />
104 <p>
105 <label>
106 <input type="checkbox" name="subscribe" value="1" />
107 <?php \esc_html_e( 'Subscribe for automatic weekly updates', 'activitypub' ); ?>
108 </label>
109 </p>
110 <p>
111 <button type="submit" class="button">
112 <?php \esc_html_e( 'Import IFTAS DNI List', 'activitypub' ); ?>
113 </button>
114 <span class="description">
115 <?php \esc_html_e( 'Curated list of high-risk domains.', 'activitypub' ); ?>
116 </span>
117 </p>
118 </form>
119
120 <?php
121 echo '</div>';
122 }
123
124 /**
125 * Handle file upload and import.
126 */
127 private static function handle_upload() {
128 $error_message = \__( 'Sorry, there has been an error.', 'activitypub' );
129
130 // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Nonce verified in dispatch().
131 if ( ! isset( $_FILES['import']['name'] ) ) {
132 echo '<p><strong>' . \esc_html( $error_message ) . '</strong><br />';
133 \printf(
134 /* translators: 1: php.ini, 2: post_max_size, 3: upload_max_filesize */
135 \esc_html__( 'File is empty. Please upload something more substantial. This error could also be caused by uploads being disabled in your %1$s file or by %2$s being defined as smaller than %3$s in %1$s.', 'activitypub' ),
136 'php.ini',
137 'post_max_size',
138 'upload_max_filesize'
139 );
140 echo '</p>';
141 return;
142 }
143
144 // Allow CSV and TXT files.
145 $allowed_types = array(
146 'csv' => 'text/csv',
147 'txt' => 'text/plain',
148 );
149 // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Nonce verified in dispatch().
150 $file_info = \wp_check_filetype( \sanitize_file_name( $_FILES['import']['name'] ), $allowed_types );
151
152 if ( ! $file_info['type'] ) {
153 \printf(
154 '<p><strong>%s</strong><br />%s</p>',
155 \esc_html( $error_message ),
156 \esc_html__( 'The uploaded file must be a CSV or TXT file. Please try again with the correct file format.', 'activitypub' )
157 );
158 return;
159 }
160
161 // phpcs:ignore WordPress.Security.NonceVerification.Missing, WordPress.Security.ValidatedSanitizedInput -- Nonce verified in dispatch(), tmp_name is a server path.
162 $file_path = $_FILES['import']['tmp_name'] ?? '';
163
164 if ( empty( $file_path ) ) {
165 \printf( '<p><strong>%s</strong><br />%s</p>', \esc_html( $error_message ), \esc_html__( 'Upload failed. Please try again.', 'activitypub' ) );
166 return;
167 }
168
169 $domains = self::parse_csv( $file_path );
170
171 if ( empty( $domains ) ) {
172 \printf( '<p><strong>%s</strong><br />%s</p>', \esc_html( $error_message ), \esc_html__( 'No valid domains found in the file.', 'activitypub' ) );
173 return;
174 }
175
176 self::import( $domains );
177 }
178
179 /**
180 * Handle URL import.
181 */
182 private static function handle_url_import() {
183 $error_message = \__( 'Sorry, there has been an error.', 'activitypub' );
184
185 // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Nonce verified in dispatch().
186 $url = \sanitize_url( \wp_unslash( $_POST['import_url'] ?? '' ) );
187
188 if ( empty( $url ) ) {
189 \printf( '<p><strong>%s</strong><br />%s</p>', \esc_html( $error_message ), \esc_html__( 'Please provide a valid URL.', 'activitypub' ) );
190 return;
191 }
192
193 if ( ! \filter_var( $url, FILTER_VALIDATE_URL ) ) {
194 \printf( '<p><strong>%s</strong><br />%s</p>', \esc_html( $error_message ), \esc_html__( 'The provided URL is not valid.', 'activitypub' ) );
195 return;
196 }
197
198 $result = Blocklist_Subscriptions::sync( $url );
199
200 if ( false === $result ) {
201 \printf( '<p><strong>%s</strong><br />%s</p>', \esc_html( $error_message ), \esc_html__( 'Failed to fetch or parse the blocklist URL.', 'activitypub' ) );
202 return;
203 }
204
205 // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Nonce verified in dispatch().
206 $subscribe = ! empty( $_POST['subscribe'] );
207
208 // Add subscription if requested (no need to sync again, just did it).
209 $subscribed = $subscribe && Blocklist_Subscriptions::add( $url );
210
211 self::show_url_import_results( $result, $subscribed );
212 }
213
214 /**
215 * Execute the import for file uploads.
216 *
217 * @param array $domains Array of domains to import.
218 */
219 private static function import( $domains ) {
220 \set_time_limit( 0 );
221
222 /**
223 * Fires when the blocklist import starts.
224 */
225 \do_action( 'import_start' );
226
227 $existing = Moderation::get_site_blocks()[ Moderation::TYPE_DOMAIN ] ?? array();
228 $new_domains = \array_diff( $domains, $existing );
229 $imported = \count( $new_domains );
230 $skipped = \count( $domains ) - $imported;
231
232 Moderation::add_site_blocks( Moderation::TYPE_DOMAIN, $new_domains );
233
234 /**
235 * Fires when the blocklist import ends.
236 */
237 \do_action( 'import_end' );
238
239 echo '<h3>' . \esc_html__( 'Import Complete', 'activitypub' ) . '</h3>';
240
241 \printf(
242 '<p>%s</p>',
243 \esc_html(
244 \sprintf(
245 /* translators: %s: Number of domains */
246 \_n( 'Imported %s domain.', 'Imported %s domains.', $imported, 'activitypub' ),
247 \number_format_i18n( $imported )
248 )
249 )
250 );
251
252 if ( $skipped > 0 ) {
253 \printf(
254 '<p>%s</p>',
255 \esc_html(
256 \sprintf(
257 /* translators: %s: Number of domains */
258 \_n( 'Skipped %s domain (already blocked).', 'Skipped %s domains (already blocked).', $skipped, 'activitypub' ),
259 \number_format_i18n( $skipped )
260 )
261 )
262 );
263 }
264
265 \printf(
266 '<p><a href="%s">%s</a></p>',
267 \esc_url( \admin_url( 'options-general.php?page=activitypub&tab=settings' ) ),
268 \esc_html__( 'View blocked domains in settings', 'activitypub' )
269 );
270 }
271
272 /**
273 * Show results for URL import.
274 *
275 * @param int $imported Number of domains imported.
276 * @param bool $subscribed Whether the URL was subscribed to.
277 */
278 private static function show_url_import_results( $imported, $subscribed ) {
279 echo '<h3>' . \esc_html__( 'Import Complete', 'activitypub' ) . '</h3>';
280
281 \printf(
282 '<p>%s</p>',
283 \esc_html(
284 \sprintf(
285 /* translators: %s: Number of domains */
286 \_n( 'Imported %s new domain.', 'Imported %s new domains.', $imported, 'activitypub' ),
287 \number_format_i18n( $imported )
288 )
289 )
290 );
291
292 if ( $subscribed ) {
293 echo '<p>' . \esc_html__( 'Subscribed for automatic weekly updates.', 'activitypub' ) . '</p>';
294 }
295
296 \printf(
297 '<p><a href="%s">%s</a></p>',
298 \esc_url( \admin_url( 'options-general.php?page=activitypub&tab=settings' ) ),
299 \esc_html__( 'View blocked domains in settings', 'activitypub' )
300 );
301 }
302
303 /**
304 * Parse a CSV file and extract domain names.
305 *
306 * @param string $file_path Path to the CSV file.
307 * @return array Array of unique, valid domain names.
308 */
309 public static function parse_csv( $file_path ) {
310 if ( ! \file_exists( $file_path ) || ! \is_readable( $file_path ) ) {
311 return array();
312 }
313
314 // phpcs:ignore WordPress.WP.AlternativeFunctions.file_get_contents_file_get_contents -- Reading local file.
315 $content = \file_get_contents( $file_path );
316 if ( false === $content ) {
317 return array();
318 }
319
320 return Blocklist_Subscriptions::parse_csv_string( $content );
321 }
322 }
323