|
| 1 | +<?php |
| 2 | +/** |
| 3 | + * Starter Kit importer file. |
| 4 | + * |
| 5 | + * @package Activitypub |
| 6 | + */ |
| 7 | + |
| 8 | +namespace Activitypub\WP_Admin\Import; |
| 9 | + |
| 10 | +use function Activitypub\follow; |
| 11 | +use function Activitypub\is_actor; |
| 12 | +use function Activitypub\object_to_uri; |
| 13 | +use function Activitypub\is_user_type_disabled; |
| 14 | + |
| 15 | +/** |
| 16 | + * Starter Kit importer class. |
| 17 | + */ |
| 18 | +class Starter_Kit { |
| 19 | + /** |
| 20 | + * Import file attachment ID. |
| 21 | + * |
| 22 | + * @var int |
| 23 | + */ |
| 24 | + private static $import_id; |
| 25 | + |
| 26 | + /** |
| 27 | + * Author ID. |
| 28 | + * |
| 29 | + * @var int |
| 30 | + */ |
| 31 | + private static $author; |
| 32 | + |
| 33 | + /** |
| 34 | + * Starter Kit file. |
| 35 | + * |
| 36 | + * @var string |
| 37 | + */ |
| 38 | + private static $file; |
| 39 | + |
| 40 | + /** |
| 41 | + * Starter Kit JSON. |
| 42 | + * |
| 43 | + * @var object |
| 44 | + */ |
| 45 | + private static $starter_kit; |
| 46 | + |
| 47 | + /** |
| 48 | + * Dispatch |
| 49 | + */ |
| 50 | + public static function dispatch() { |
| 51 | + // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 52 | + $step = \absint( $_GET['step'] ?? 0 ); |
| 53 | + |
| 54 | + self::header(); |
| 55 | + |
| 56 | + switch ( $step ) { |
| 57 | + case 0: |
| 58 | + self::greet(); |
| 59 | + break; |
| 60 | + |
| 61 | + case 1: |
| 62 | + \check_admin_referer( 'import-upload' ); |
| 63 | + if ( self::handle_upload() ) { |
| 64 | + self::import_options(); |
| 65 | + } |
| 66 | + break; |
| 67 | + |
| 68 | + case 2: |
| 69 | + \check_admin_referer( 'import-starter-kit' ); |
| 70 | + self::$import_id = \absint( $_POST['import_id'] ?? 0 ); |
| 71 | + self::$author = \absint( $_POST['author'] ?? \get_current_user_id() ); |
| 72 | + |
| 73 | + \set_time_limit( 0 ); |
| 74 | + self::import(); |
| 75 | + break; |
| 76 | + } |
| 77 | + |
| 78 | + self::footer(); |
| 79 | + } |
| 80 | + |
| 81 | + /** |
| 82 | + * Handle upload. |
| 83 | + */ |
| 84 | + public static function handle_upload() { |
| 85 | + $error_message = \__( 'Sorry, there has been an error.', 'activitypub' ); |
| 86 | + |
| 87 | + \check_admin_referer( 'import-upload' ); |
| 88 | + |
| 89 | + if ( ! isset( $_FILES['import']['name'] ) ) { |
| 90 | + echo '<p><strong>' . \esc_html( $error_message ) . '</strong><br />'; |
| 91 | + \printf( |
| 92 | + /* translators: 1: php.ini, 2: post_max_size, 3: upload_max_filesize */ |
| 93 | + \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' ), |
| 94 | + 'php.ini', |
| 95 | + 'post_max_size', |
| 96 | + 'upload_max_filesize' |
| 97 | + ); |
| 98 | + echo '</p>'; |
| 99 | + return false; |
| 100 | + } |
| 101 | + |
| 102 | + $file_info = \wp_check_filetype( \sanitize_file_name( $_FILES['import']['name'] ), array( 'json' => 'application/json' ) ); |
| 103 | + if ( 'application/json' !== $file_info['type'] ) { |
| 104 | + \printf( '<p><strong>%s</strong><br />%s</p>', \esc_html( $error_message ), \esc_html__( 'The uploaded file must be a JSON file. Please try again with the correct file format.', 'activitypub' ) ); |
| 105 | + return false; |
| 106 | + } |
| 107 | + |
| 108 | + $overrides = array( |
| 109 | + 'test_form' => false, |
| 110 | + 'test_type' => false, |
| 111 | + ); |
| 112 | + |
| 113 | + $upload = \wp_handle_upload( $_FILES['import'], $overrides ); |
| 114 | + |
| 115 | + if ( isset( $upload['error'] ) ) { |
| 116 | + \printf( '<p><strong>%s</strong><br />%s</p>', \esc_html( $error_message ), \esc_html( $upload['error'] ) ); |
| 117 | + return false; |
| 118 | + } |
| 119 | + |
| 120 | + // Construct the attachment array. |
| 121 | + $attachment = array( |
| 122 | + 'post_title' => \wp_basename( $upload['file'] ), |
| 123 | + 'post_content' => $upload['url'], |
| 124 | + 'post_mime_type' => $upload['type'], |
| 125 | + 'guid' => $upload['url'], |
| 126 | + 'context' => 'import', |
| 127 | + 'post_status' => 'private', |
| 128 | + ); |
| 129 | + |
| 130 | + // Save the data. |
| 131 | + self::$import_id = \wp_insert_attachment( $attachment, $upload['file'] ); |
| 132 | + |
| 133 | + // Schedule a cleanup for one day from now in case of failed import or missing wp_import_cleanup() call. |
| 134 | + \wp_schedule_single_event( time() + DAY_IN_SECONDS, 'importer_scheduled_cleanup', array( self::$import_id ) ); |
| 135 | + |
| 136 | + return true; |
| 137 | + } |
| 138 | + |
| 139 | + /** |
| 140 | + * Import options. |
| 141 | + */ |
| 142 | + public static function import_options() { |
| 143 | + $activitypub_users = function ( $users ) { |
| 144 | + // Add blog user to the html output if enabled. |
| 145 | + $users = \preg_replace( '/<\/select>/', '<option value="0">' . \__( 'Blog User', 'activitypub' ) . '</option></select>', $users ); |
| 146 | + return $users; |
| 147 | + }; |
| 148 | + |
| 149 | + if ( ! is_user_type_disabled( 'blog' ) ) { |
| 150 | + \add_filter( |
| 151 | + 'wp_dropdown_users', |
| 152 | + $activitypub_users |
| 153 | + ); |
| 154 | + } |
| 155 | + ?> |
| 156 | + <form action="<?php echo \esc_url( \admin_url( 'admin.php?import=starter-kit&step=2' ) ); ?>" method="post"> |
| 157 | + <?php \wp_nonce_field( 'import-starter-kit' ); ?> |
| 158 | + <input type="hidden" name="import_id" value="<?php echo esc_attr( self::$import_id ); ?>" /> |
| 159 | + <h3><?php \esc_html_e( 'Assign Author', 'activitypub' ); ?></h3> |
| 160 | + <p> |
| 161 | + <label for="author"><?php \esc_html_e( 'Author:', 'activitypub' ); ?></label> |
| 162 | + <?php |
| 163 | + \wp_dropdown_users( |
| 164 | + array( |
| 165 | + 'name' => 'author', |
| 166 | + 'id' => 'author', |
| 167 | + 'show' => 'display_name_with_login', |
| 168 | + 'selected' => \get_current_user_id(), |
| 169 | + 'capability' => 'activitypub', |
| 170 | + ) |
| 171 | + ); |
| 172 | + ?> |
| 173 | + </p> |
| 174 | + <p class="submit"> |
| 175 | + <input type="submit" class="button button-primary" value="<?php \esc_attr_e( 'Import', 'activitypub' ); ?>" /> |
| 176 | + </p> |
| 177 | + </form> |
| 178 | + <?php |
| 179 | + \remove_filter( 'wp_dropdown_users', $activitypub_users ); |
| 180 | + } |
| 181 | + |
| 182 | + /** |
| 183 | + * Import. |
| 184 | + */ |
| 185 | + public static function import() { |
| 186 | + $error_message = \__( 'Sorry, there has been an error.', 'activitypub' ); |
| 187 | + $file = \get_attached_file( self::$import_id ); |
| 188 | + |
| 189 | + \WP_Filesystem(); |
| 190 | + |
| 191 | + global $wp_filesystem; |
| 192 | + |
| 193 | + $file_contents = $wp_filesystem->get_contents( $file ); |
| 194 | + if ( false === $file_contents ) { |
| 195 | + \printf( '<p><strong>%s</strong><br />%s</p>', \esc_html( $error_message ), \esc_html__( 'Could not read the uploaded file.', 'activitypub' ) ); |
| 196 | + return; |
| 197 | + } |
| 198 | + |
| 199 | + self::$starter_kit = \json_decode( $file_contents, true ); |
| 200 | + if ( null === self::$starter_kit ) { |
| 201 | + \printf( '<p><strong>%s</strong><br />%s</p>', \esc_html( $error_message ), \esc_html__( 'Invalid JSON format in the uploaded file.', 'activitypub' ) ); |
| 202 | + return; |
| 203 | + } |
| 204 | + |
| 205 | + \wp_suspend_cache_invalidation(); |
| 206 | + \wp_defer_term_counting( true ); |
| 207 | + \wp_defer_comment_counting( true ); |
| 208 | + |
| 209 | + /** |
| 210 | + * Fires when the Starter Kit import starts. |
| 211 | + */ |
| 212 | + \do_action( 'import_start' ); |
| 213 | + |
| 214 | + $result = self::follow(); |
| 215 | + |
| 216 | + \wp_suspend_cache_invalidation( false ); |
| 217 | + \wp_defer_term_counting( false ); |
| 218 | + \wp_defer_comment_counting( false ); |
| 219 | + |
| 220 | + \wp_import_cleanup( self::$import_id ); |
| 221 | + |
| 222 | + if ( \is_wp_error( $result ) ) { |
| 223 | + \printf( '<p><strong>%s</strong><br />%s</p>', \esc_html( $error_message ), \esc_html( $result->get_error_message() ) ); |
| 224 | + } else { |
| 225 | + \printf( '<p>%s</p>', \esc_html__( 'All done.', 'activitypub' ) ); |
| 226 | + } |
| 227 | + |
| 228 | + /** |
| 229 | + * Fires when the Starter Kit import ends. |
| 230 | + */ |
| 231 | + \do_action( 'import_end' ); |
| 232 | + } |
| 233 | + |
| 234 | + /** |
| 235 | + * Process posts. |
| 236 | + * |
| 237 | + * @return true|\WP_Error True on success, WP_Error on failure. |
| 238 | + */ |
| 239 | + public static function follow() { |
| 240 | + $skipped = 0; |
| 241 | + $followed = 0; |
| 242 | + |
| 243 | + $items = self::$starter_kit['items'] ?? array(); |
| 244 | + |
| 245 | + foreach ( $items as $item ) { |
| 246 | + if ( ! is_actor( $item ) ) { |
| 247 | + ++$skipped; |
| 248 | + continue; |
| 249 | + } |
| 250 | + |
| 251 | + $result = follow( object_to_uri( $item ), self::$author ); |
| 252 | + |
| 253 | + if ( \is_wp_error( $result ) ) { |
| 254 | + ++$skipped; |
| 255 | + } else { |
| 256 | + /* translators: %s: Account ID */ |
| 257 | + \printf( '<p>' . \esc_html__( 'Followed %s', 'activitypub' ) . '</p>', \esc_html( $item['id'] ) ); |
| 258 | + ++$followed; |
| 259 | + } |
| 260 | + } |
| 261 | + |
| 262 | + echo '<hr />'; |
| 263 | + |
| 264 | + /* translators: %d: Number of followed actors */ |
| 265 | + \printf( '<p>%s</p>', \esc_html( \sprintf( \_n( 'Followed %s Actor.', 'Followed %s Actors.', $followed, 'activitypub' ), \number_format_i18n( $followed ) ) ) ); |
| 266 | + /* translators: %d: Number of skipped items */ |
| 267 | + \printf( '<p>%s</p>', \esc_html( \sprintf( \_n( 'Skipped %s Item.', 'Skipped %s Items.', $skipped, 'activitypub' ), \number_format_i18n( $skipped ) ) ) ); |
| 268 | + |
| 269 | + return true; |
| 270 | + } |
| 271 | + |
| 272 | + /** |
| 273 | + * Intro. |
| 274 | + */ |
| 275 | + public static function greet() { |
| 276 | + echo '<div class="narrow">'; |
| 277 | + echo '<p>' . \esc_html__( 'Starter Kits use the ActivityPub protocol with custom extensions to automate tasks such as following accounts, blocking unwanted content, and applying default configurations. The importer will automatically follow every user listed in the kit, helping users connect right away. Support for additional actions and features will be added over time.', 'activitypub' ) . '</p>'; |
| 278 | + |
| 279 | + \wp_import_upload_form( 'admin.php?import=starter-kit&step=1' ); |
| 280 | + |
| 281 | + echo '</div>'; |
| 282 | + } |
| 283 | + |
| 284 | + /** |
| 285 | + * Header. |
| 286 | + */ |
| 287 | + public static function header() { |
| 288 | + echo '<div class="wrap">'; |
| 289 | + echo '<h2>' . \esc_html__( 'Import a Fediverse Starter Kit (Beta)', 'activitypub' ) . '</h2>'; |
| 290 | + } |
| 291 | + |
| 292 | + /** |
| 293 | + * Footer. |
| 294 | + */ |
| 295 | + public static function footer() { |
| 296 | + echo '</div>'; |
| 297 | + } |
| 298 | +} |
0 commit comments