Ticket #5247: hosting-handbook-importer.diff
File hosting-handbook-importer.diff, 18.3 KB (added by , 3 years ago) |
---|
-
new file wordpress.org/public_html/wp-content/plugins/wporg-hosting-handbook/assets/images/github-mark.svg
diff --git wordpress.org/public_html/wp-content/plugins/wporg-hosting-handbook/assets/images/github-mark.svg wordpress.org/public_html/wp-content/plugins/wporg-hosting-handbook/assets/images/github-mark.svg new file mode 100644 index 000000000..99b0878c1
- + 1 <svg height="1024" width="1024" xmlns="http://www.w3.org/2000/svg"> 2 <path class="github-mark" d="M512 0C229.25 0 0 229.25 0 512c0 226.25 146.688 418.125 350.156 485.812 25.594 4.688 34.938-11.125 34.938-24.625 0-12.188-0.469-52.562-0.719-95.312C242 908.812 211.906 817.5 211.906 817.5c-23.312-59.125-56.844-74.875-56.844-74.875-46.531-31.75 3.53-31.125 3.53-31.125 51.406 3.562 78.47 52.75 78.47 52.75 45.688 78.25 119.875 55.625 149 42.5 4.654-33 17.904-55.625 32.5-68.375C304.906 725.438 185.344 681.5 185.344 485.312c0-55.938 19.969-101.562 52.656-137.406-5.219-13-22.844-65.094 5.062-135.562 0 0 42.938-13.75 140.812 52.5 40.812-11.406 84.594-17.031 128.125-17.219 43.5 0.188 87.312 5.875 128.188 17.281 97.688-66.312 140.688-52.5 140.688-52.5 28 70.531 10.375 122.562 5.125 135.5 32.812 35.844 52.625 81.469 52.625 137.406 0 196.688-119.75 240-233.812 252.688 18.438 15.875 34.75 47 34.75 94.75 0 68.438-0.688 123.625-0.688 140.5 0 13.625 9.312 29.562 35.25 24.562C877.438 930 1024 738.125 1024 512 1024 229.25 794.75 0 512 0z" /> 3 </svg> -
new file wordpress.org/public_html/wp-content/plugins/wporg-hosting-handbook/inc/class-handbook.php
diff --git wordpress.org/public_html/wp-content/plugins/wporg-hosting-handbook/inc/class-handbook.php wordpress.org/public_html/wp-content/plugins/wporg-hosting-handbook/inc/class-handbook.php new file mode 100644 index 000000000..3884ff220
- + 1 <?php 2 3 namespace WPOrg_Hosting_Handbook; 4 5 class Handbook { 6 7 /** 8 * Append a "Edit on GitHub" link to Handbook document titles 9 */ 10 public static function filter_the_title_edit_link( $title, $id = null ) { 11 // Only apply to the main title for the document 12 if ( ! is_singular( 'handbook' ) 13 || ! is_main_query() 14 || ! in_the_loop() 15 || is_embed() 16 || $id !== get_queried_object_id() ) { 17 return $title; 18 } 19 20 $markdown_source = self::get_markdown_edit_link( get_the_ID() ); 21 if ( ! $markdown_source ) { 22 return $title; 23 } 24 25 return $title . ' <a class="github-edit" href="' . esc_url( $markdown_source ) . '"><img src="' . esc_url( plugins_url( 'assets/images/github-mark.svg', dirname( __FILE__ ) ) ) . '"> <span>Edit</span></a>'; 26 } 27 28 /** 29 * Hosting Handbook pages are maintained in the GitHub repo, so the edit 30 * link should ridirect to there. 31 */ 32 public static function redirect_edit_link_to_github( $link, $post_id, $context ) { 33 if ( is_admin() ) { 34 return $link; 35 } 36 $post = get_post( $post_id ); 37 if ( ! $post ) { 38 return $link; 39 } 40 41 if ( 'handbook' !== $post->post_type ) { 42 return $link; 43 } 44 45 $markdown_source = self::get_markdown_edit_link( $post_id ); 46 if ( ! $markdown_source ) { 47 return $link; 48 } 49 50 if ( 'display' === $context ) { 51 $markdown_source = esc_url( $markdown_source ); 52 } 53 54 return $markdown_source; 55 } 56 57 /** 58 * o2 does inline editing, so we also need to remove the class name that it looks for. 59 * 60 * o2 obeys the edit_post capability for displaying the edit link, so we also need to manually 61 * add the edit link if it isn't there - it always redirects to GitHub, so it doesn't need to 62 * obey the edit_post capability in this instance. 63 */ 64 public static function redirect_o2_edit_link_to_github( $actions, $post_id ) { 65 $post = get_post( $post_id ); 66 if ( ! $post ) { 67 return $actions; 68 } 69 70 if ( 'handbook' !== $post->post_type ) { 71 return $actions; 72 } 73 74 $markdown_source = self::get_markdown_edit_link( $post_id ); 75 if ( ! $markdown_source ) { 76 return $actions; 77 } 78 79 /* 80 * Define our own edit post action for o2. 81 * 82 * Notable differences from the original are: 83 * - the 'href' parameter always goes to the GitHub source. 84 * - the 'o2-edit' class is missing, so inline editing is disabled. 85 */ 86 $edit_action = array( 87 'action' => 'edit', 88 'href' => $markdown_source, 89 'classes' => array( 'edit-post-link' ), 90 'rel' => $post_id, 91 'initialState' => 'default' 92 ); 93 94 // Find and replace the existing edit action. 95 $replaced = false; 96 foreach( $actions as &$action ) { 97 if ( 'edit' === $action['action'] ) { 98 $action = $edit_action; 99 $replaced = true; 100 break; 101 } 102 } 103 unset( $action ); 104 105 // If there was no edit action replaced, add it in manually. 106 if ( ! $replaced ) { 107 $actions[30] = $edit_action; 108 } 109 110 return $actions; 111 } 112 113 private static function get_markdown_edit_link( $post_id ) { 114 $markdown_source = Markdown_Import::get_markdown_source( $post_id ); 115 if ( is_wp_error( $markdown_source ) ) { 116 return ''; 117 } 118 if ( 'github.com' !== parse_url( $markdown_source, PHP_URL_HOST ) 119 || false !== stripos( $markdown_source, '/edit/master/' ) ) { 120 return $markdown_source; 121 } 122 $markdown_source = str_replace( '/blob/master/', '/edit/master/', $markdown_source ); 123 return $markdown_source; 124 } 125 } -
new file wordpress.org/public_html/wp-content/plugins/wporg-hosting-handbook/inc/class-markdown-import.php
diff --git wordpress.org/public_html/wp-content/plugins/wporg-hosting-handbook/inc/class-markdown-import.php wordpress.org/public_html/wp-content/plugins/wporg-hosting-handbook/inc/class-markdown-import.php new file mode 100644 index 000000000..59287b5c1
- + 1 <?php 2 3 namespace WPOrg_Hosting_Handbook; 4 5 use WP_Error; 6 use WP_Query; 7 8 class Markdown_Import { 9 10 private static $handbook_manifest = 'https://raw.githubusercontent.com/wordpress/hosting-handbook/master/bin/handbook-manifest.json'; 11 private static $input_name = 'wporg-hosting-handbook-markdown-source'; 12 private static $meta_key = 'wporg-hosting-handbook-markdown-source'; 13 private static $nonce_name = 'wporg-hosting-handbook-markdown-source-nonce'; 14 private static $submit_name = 'wporg-hosting-handbook-markdown-import'; 15 private static $supported_post_types = array( 'handbook' ); 16 private static $posts_per_page = 100; 17 18 /** 19 * Register our cron task if it doesn't already exist 20 */ 21 public static function action_init() { 22 if ( ! wp_next_scheduled( 'wporg_hosting_handbook_manifest_import' ) ) { 23 wp_schedule_event( time(), '15_minutes', 'wporg_hosting_handbook_manifest_import' ); 24 } 25 if ( ! wp_next_scheduled( 'wporg_hosting_handbook_markdown_import' ) ) { 26 wp_schedule_event( time(), '15_minutes', 'wporg_hosting_handbook_markdown_import' ); 27 } 28 } 29 30 public static function action_wporg_hosting_handbook_manifest_import() { 31 $response = wp_remote_get( self::$handbook_manifest ); 32 if ( is_wp_error( $response ) ) { 33 return $response; 34 } elseif ( 200 !== wp_remote_retrieve_response_code( $response ) ) { 35 return new WP_Error( 'invalid-http-code', 'Markdown source returned non-200 http code.' ); 36 } 37 $manifest = json_decode( wp_remote_retrieve_body( $response ), true ); 38 if ( ! $manifest ) { 39 return new WP_Error( 'invalid-manifest', 'Manifest did not unfurl properly.' );; 40 } 41 // Fetch all handbook posts for comparison 42 $q = new WP_Query( array( 43 'post_type' => self::$supported_post_types, 44 'post_status' => 'publish', 45 'posts_per_page' => self::$posts_per_page, 46 ) ); 47 $existing = $q->posts; 48 $created = 0; 49 foreach( $manifest as $doc ) { 50 // Already exists 51 if ( wp_filter_object_list( $existing, array( 'post_name' => $doc['slug'] ) ) ) { 52 continue; 53 } 54 $post_parent = null; 55 if ( ! empty( $doc['parent'] ) ) { 56 // Find the parent in the existing set 57 $parents = wp_filter_object_list( $existing, array( 'post_name' => $doc['parent'] ) ); 58 if ( ! empty( $parents ) ) { 59 $parent = array_shift( $parents ); 60 } else { 61 // Create the parent and add it to the stack 62 if ( isset( $manifest[ $doc['parent'] ] ) ) { 63 $parent_doc = $manifest[ $doc['parent'] ]; 64 $parent = self::create_post_from_manifest_doc( $parent_doc ); 65 if ( $parent ) { 66 $created++; 67 $existing[] = $parent; 68 } else { 69 continue; 70 } 71 } else { 72 continue; 73 } 74 } 75 $post_parent = $parent->ID; 76 } 77 $post = self::create_post_from_manifest_doc( $doc, $post_parent ); 78 if ( $post ) { 79 $created++; 80 $existing[] = $post; 81 } 82 } 83 if ( class_exists( 'WP_CLI' ) ) { 84 \WP_CLI::success( "Successfully created {$created} handbook pages." ); 85 } 86 } 87 88 /** 89 * Create a new handbook page from the manifest document 90 */ 91 private static function create_post_from_manifest_doc( $doc, $post_parent = null ) { 92 $post_data = array( 93 'post_type' => 'handbook', 94 'post_status' => 'publish', 95 'post_parent' => $post_parent, 96 'post_title' => sanitize_text_field( wp_slash( $doc['title'] ) ), 97 'post_name' => sanitize_title_with_dashes( $doc['slug'] ), 98 ); 99 $post_id = wp_insert_post( $post_data ); 100 if ( ! $post_id ) { 101 return false; 102 } 103 if ( class_exists( 'WP_CLI' ) ) { 104 \WP_CLI::log( "Created post {$post_id} for {$doc['title']}." ); 105 } 106 update_post_meta( $post_id, self::$meta_key, esc_url_raw( $doc['markdown_source'] ) ); 107 return get_post( $post_id ); 108 } 109 110 public static function action_wporg_hosting_handbook_markdown_import() { 111 $q = new WP_Query( array( 112 'post_type' => self::$supported_post_types, 113 'post_status' => 'publish', 114 'fields' => 'ids', 115 'posts_per_page' => self::$posts_per_page, 116 ) ); 117 $ids = $q->posts; 118 $success = 0; 119 foreach( $ids as $id ) { 120 $ret = self::update_post_from_markdown_source( $id ); 121 if ( class_exists( 'WP_CLI' ) ) { 122 if ( is_wp_error( $ret ) ) { 123 \WP_CLI::warning( $ret->get_error_message() ); 124 } else { 125 \WP_CLI::log( "Updated {$id} from markdown source" ); 126 $success++; 127 } 128 } 129 } 130 if ( class_exists( 'WP_CLI' ) ) { 131 $total = count( $ids ); 132 \WP_CLI::success( "Successfully updated {$success} of {$total} handbook pages." ); 133 } 134 } 135 136 /** 137 * Handle a request to import from the markdown source 138 */ 139 public static function action_load_post_php() { 140 if ( ! isset( $_GET[ self::$submit_name ] ) 141 || ! isset( $_GET[ self::$nonce_name ] ) 142 || ! isset( $_GET['post'] ) ) { 143 return; 144 } 145 $post_id = (int) $_GET['post']; 146 if ( ! current_user_can( 'edit_post', $post_id ) 147 || ! wp_verify_nonce( $_GET[ self::$nonce_name ], self::$input_name ) 148 || ! in_array( get_post_type( $post_id ), self::$supported_post_types, true ) ) { 149 return; 150 } 151 152 $response = self::update_post_from_markdown_source( $post_id ); 153 if ( is_wp_error( $response ) ) { 154 wp_die( $response->get_error_message() ); 155 } 156 157 wp_safe_redirect( get_edit_post_link( $post_id, 'raw' ) ); 158 exit; 159 } 160 161 /** 162 * Add an input field for specifying Markdown source 163 */ 164 public static function action_edit_form_after_title( $post ) { 165 if ( ! in_array( $post->post_type, self::$supported_post_types, true ) ) { 166 return; 167 } 168 $markdown_source = get_post_meta( $post->ID, self::$meta_key, true ); 169 ?> 170 <label>Markdown source: <input 171 type="text" 172 name="<?php echo esc_attr( self::$input_name ); ?>" 173 value="<?php echo esc_attr( $markdown_source ); ?>" 174 placeholder="Enter a URL representing a markdown file to import" 175 size="50" /> 176 </label> <?php 177 if ( $markdown_source ) : 178 $update_link = add_query_arg( array( 179 self::$submit_name => 'import', 180 self::$nonce_name => wp_create_nonce( self::$input_name ), 181 ), get_edit_post_link( $post->ID, 'raw' ) ); 182 ?> 183 <a class="button button-small button-primary" href="<?php echo esc_url( $update_link ); ?>">Import</a> 184 <?php endif; ?> 185 <?php wp_nonce_field( self::$input_name, self::$nonce_name ); ?> 186 <?php 187 } 188 189 /** 190 * Save the Markdown source input field 191 */ 192 public static function action_save_post( $post_id ) { 193 194 if ( ! isset( $_POST[ self::$input_name ] ) 195 || ! isset( $_POST[ self::$nonce_name ] ) 196 || ! in_array( get_post_type( $post_id ), self::$supported_post_types, true ) ) { 197 return; 198 } 199 200 if ( ! wp_verify_nonce( $_POST[ self::$nonce_name ], self::$input_name ) ) { 201 return; 202 } 203 204 $markdown_source = ''; 205 if ( ! empty( $_POST[ self::$input_name ] ) ) { 206 $markdown_source = esc_url_raw( $_POST[ self::$input_name ] ); 207 } 208 update_post_meta( $post_id, self::$meta_key, $markdown_source ); 209 } 210 211 /** 212 * Filter cron schedules to add a 15 minute schedule 213 */ 214 public static function filter_cron_schedules( $schedules ) { 215 $schedules['15_minutes'] = array( 216 'interval' => 15 * MINUTE_IN_SECONDS, 217 'display' => '15 minutes' 218 ); 219 return $schedules; 220 } 221 222 /** 223 * Update a post from its Markdown source 224 */ 225 private static function update_post_from_markdown_source( $post_id ) { 226 $markdown_source = self::get_markdown_source( $post_id ); 227 if ( is_wp_error( $markdown_source ) ) { 228 return $markdown_source; 229 } 230 if ( ! function_exists( 'jetpack_require_lib' ) ) { 231 return new WP_Error( 'missing-jetpack-require-lib', 'jetpack_require_lib() is missing on system.' ); 232 } 233 234 // Transform GitHub repo HTML pages into their raw equivalents 235 $markdown_source = preg_replace( '#https?://github\.com/([^/]+/[^/]+)/blob/(.+)#', 'https://raw.githubusercontent.com/$1/$2', $markdown_source ); 236 $markdown_source = add_query_arg( 'v', time(), $markdown_source ); 237 $response = wp_remote_get( $markdown_source ); 238 if ( is_wp_error( $response ) ) { 239 return $response; 240 } elseif ( 200 !== wp_remote_retrieve_response_code( $response ) ) { 241 return new WP_Error( 'invalid-http-code', 'Markdown source returned non-200 http code.' ); 242 } 243 244 $markdown = wp_remote_retrieve_body( $response ); 245 // Strip YAML doc from the header 246 $markdown = preg_replace( '#^---(.+)---#Us', '', $markdown ); 247 248 $title = null; 249 if ( preg_match( '/^#\s(.+)/', $markdown, $matches ) ) { 250 $title = $matches[1]; 251 $markdown = preg_replace( '/^#\s(.+)/', '', $markdown ); 252 } 253 254 // Transform to HTML and save the post 255 jetpack_require_lib( 'markdown' ); 256 $parser = new \WPCom_GHF_Markdown_Parser; 257 $html = $parser->transform( $markdown ); 258 $post_data = array( 259 'ID' => $post_id, 260 'post_content' => wp_filter_post_kses( wp_slash( $html ) ), 261 ); 262 if ( ! is_null( $title ) ) { 263 $post_data['post_title'] = sanitize_text_field( wp_slash( $title ) ); 264 } 265 wp_update_post( $post_data ); 266 return true; 267 } 268 269 /** 270 * Retrieve the markdown source URL for a given post. 271 */ 272 public static function get_markdown_source( $post_id ) { 273 $markdown_source = get_post_meta( $post_id, self::$meta_key, true ); 274 if ( ! $markdown_source ) { 275 return new WP_Error( 'missing-markdown-source', 'Markdown source is missing for post.' ); 276 } 277 278 return $markdown_source; 279 } 280 } -
new file wordpress.org/public_html/wp-content/plugins/wporg-hosting-handbook/wporg-hosting-handbook.php
diff --git wordpress.org/public_html/wp-content/plugins/wporg-hosting-handbook/wporg-hosting-handbook.php wordpress.org/public_html/wp-content/plugins/wporg-hosting-handbook/wporg-hosting-handbook.php new file mode 100644 index 000000000..b7c0762ad
- + 1 <?php 2 /** 3 * Plugin name: Hosting Team Handbook: WordPress.org Customizations 4 * Description: Provides general customizations for the Hosting Team's presence on WordPress.org 5 * Version: 0.1.0 6 * Author: WordPress.org 7 * Author URI: http://wordpress.org/ 8 * License: GPLv2 or later 9 */ 10 11 require_once dirname( __FILE__ ) . '/inc/class-markdown-import.php'; 12 require_once dirname( __FILE__ ) . '/inc/class-handbook.php'; 13 14 /** 15 * Registry of actions and filters 16 */ 17 add_action( 'init', array( 'WPOrg_Hosting_Handbook\Markdown_Import', 'action_init' ) ); 18 add_action( 'wporg_hosting_handbook_manifest_import', array( 'WPOrg_Hosting_Handbook\Markdown_Import', 'action_wporg_hosting_handbook_manifest_import' ) ); 19 add_action( 'wporg_hosting_handbook_markdown_import', array( 'WPOrg_Hosting_Handbook\Markdown_Import', 'action_wporg_hosting_handbook_markdown_import' ) ); 20 add_action( 'load-post.php', array( 'WPOrg_Hosting_Handbook\Markdown_Import', 'action_load_post_php' ) ); 21 add_action( 'edit_form_after_title', array( 'WPOrg_Hosting_Handbook\Markdown_Import', 'action_edit_form_after_title' ) ); 22 add_action( 'save_post', array( 'WPOrg_Hosting_Handbook\Markdown_Import', 'action_save_post' ) ); 23 add_filter( 'cron_schedules', array( 'WPOrg_Hosting_Handbook\Markdown_Import', 'filter_cron_schedules' ) ); 24 add_filter( 'the_title', array( 'WPOrg_Hosting_Handbook\Handbook', 'filter_the_title_edit_link' ), 10, 2 ); 25 add_filter( 'get_edit_post_link', array( 'WPOrg_Hosting_Handbook\Handbook', 'redirect_edit_link_to_github' ), 10, 3 ); 26 add_filter( 'o2_filter_post_actions', array( 'WPOrg_Hosting_Handbook\Handbook', 'redirect_o2_edit_link_to_github' ), 11, 2 ); 27 28 add_action( 'wp_head', function(){ 29 ?> 30 <style> 31 pre code { 32 line-height: 16px; 33 } 34 a.github-edit { 35 margin-left: .5em; 36 font-size: .5em; 37 vertical-align: top; 38 display: inline-block; 39 border: 1px solid #eeeeee; 40 border-radius: 2px; 41 background: #eeeeee; 42 padding: .5em .6em .4em; 43 color: black; 44 margin-top: 0.1em; 45 } 46 a.github-edit > * { 47 opacity: 0.6; 48 } 49 a.github-edit:hover > * { 50 opacity: 1; 51 color: black; 52 } 53 a.github-edit img { 54 height: .8em; 55 } 56 .single-handbook div.table-of-contents { 57 margin: 0; 58 float: none; 59 padding: 0; 60 border: none; 61 box-shadow: none; 62 width: auto; 63 } 64 .single-handbook div.table-of-contents:after { 65 content: " "; 66 display: block; 67 clear: both; 68 } 69 .single-handbook .table-of-contents h2 { 70 display: none; 71 } 72 .single-handbook div.table-of-contents ul { 73 padding: 0; 74 margin-top: 0.4em; 75 margin-bottom: 1.1em; 76 } 77 .single-handbook div.table-of-contents > ul li { 78 display: inline-block; 79 padding: 0; 80 font-size: 12px; 81 } 82 .single-handbook div.table-of-contents > ul li a:after { 83 content: "|"; 84 display: inline-block; 85 width: 20px; 86 text-align: center; 87 color: #eeeeee 88 } 89 .single-handbook div.table-of-contents > ul li:last-child a:after { 90 content: ""; 91 } 92 .single-handbook div.table-of-contents ul ul { 93 display: none; 94 } 95 .single-handbook #secondary { 96 max-width: 240px; 97 } 98 </style> 99 <?php 100 });