Ticket #2968: 2968.5.diff
File 2968.5.diff, 21.5 KB (added by , 7 years ago) |
---|
-
wordpress.org/public_html/wp-content/plugins/wporg-markdown/README.md
1 # WPORG Markdown Importer 2 3 Imports Markdown from a remote site (like GitHub) into WordPress as pages. 4 5 ## Configuration 6 7 Each importer needs to override the abstract methods: 8 9 * `get_base()` - Base URL for imported pages. This will be stripped from the key before comparing. 10 * `get_manifest_url()` - URL pointing to the manifest. 11 * `get_post_type()` - Post type to import as. 12 13 ## Manifest Format 14 15 The manifest should be a JSON object, with the keys set to the desired permalink (excluding the base path). Each item should also be a JSON object, containing the following keys: 16 17 * `slug` - Post name to insert. (Must match the final path-part of the key.) 18 * `markdown_source` - URL for the Markdown file to parse into content. 19 * `parent` - Key for the parent to store under. (Must correspond to the non-final path-parts of the key.) 20 * `title` - Title to use when creating post. Used temporarily, will be updated from the Markdown file. If not specified, defaults to `slug` (but will be updated from Markdown source). 21 22 **Note:** The Handbook index should have the slug `index`. 23 24 Example: 25 26 ```json 27 { 28 "foo": { 29 "title": "Temporary Foo Title", 30 "slug": "foo", 31 "markdown_source": "https://raw.githubusercontent.com/WordPress/doc-repo/master/foo.md", 32 "parent": null 33 }, 34 "foo/bar": { 35 "title": "Temporary Bar Title", 36 "slug": "bar", 37 "markdown_source": "https://raw.githubusercontent.com/WordPress/doc-repo/master/foo/bar.md", 38 "parent": "foo" 39 }, 40 "foo/bar/quux": { 41 "title": "Temporary Quux Title", 42 "slug": "quux", 43 "markdown_source": "https://raw.githubusercontent.com/WordPress/doc-repo/master/foo/bar/quux.md", 44 "parent": "foo/bar" 45 } 46 } 47 ``` -
wordpress.org/public_html/wp-content/plugins/wporg-markdown/inc/class-editor.php
1 <?php 2 3 namespace WordPressdotorg\Markdown; 4 5 use WP_Post; 6 7 class Editor { 8 public function __construct( Importer $importer ) { 9 $this->importer = $importer; 10 } 11 12 public function init() { 13 add_filter( 'the_title', array( $this, 'filter_the_title_edit_link' ), 10, 2 ); 14 add_filter( 'get_edit_post_link', array( $this, 'redirect_edit_link_to_github' ), 10, 3 ); 15 add_filter( 'o2_filter_post_actions', array( $this, 'redirect_o2_edit_link_to_github' ), 11, 2 ); 16 add_action( 'wp_head', array( $this, 'render_edit_button_style' ) ); 17 add_action( 'edit_form_top', array( $this, 'render_editor_warning' ) ); 18 } 19 20 public function render_edit_button_style() { 21 ?> 22 <style> 23 a.github-edit { 24 margin-left: .5em; 25 font-size: .5em; 26 vertical-align: top; 27 display: inline-block; 28 border: 1px solid #eeeeee; 29 border-radius: 2px; 30 background: #eeeeee; 31 padding: .5em .6em .4em; 32 color: black; 33 margin-top: 0.1em; 34 } 35 a.github-edit > * { 36 opacity: 0.6; 37 } 38 a.github-edit:hover > * { 39 opacity: 1; 40 color: black; 41 } 42 a.github-edit img { 43 height: .8em; 44 } 45 </style> 46 <?php 47 } 48 49 /** 50 * Render a warning for editors accessing the edit page via the admin. 51 * 52 * @param WP_Post $post Post being edited. 53 */ 54 public function render_editor_warning( WP_Post $post ) { 55 if ( $post->post_type !== $this->importer->get_post_type() ) { 56 return; 57 } 58 59 printf( 60 '<div class="notice notice-warning"><p>%s</p><p><a href="%s">%s</a></p></div>', 61 'This page is maintained on GitHub. Content, title, and slug edits here will be discarded on next sync.', 62 $this->get_markdown_edit_link( $post->ID ), 63 'Edit on GitHub' 64 ); 65 } 66 67 /** 68 * Append a "Edit on GitHub" link to Handbook document titles 69 */ 70 public function filter_the_title_edit_link( $title, $id = null ) { 71 // Only apply to the main title for the document 72 if ( ! is_singular( $this->importer->get_post_type() ) 73 || ! is_main_query() 74 || ! in_the_loop() 75 || $id !== get_queried_object_id() ) { 76 return $title; 77 } 78 79 $markdown_source = $this->get_markdown_edit_link( get_the_ID() ); 80 if ( ! $markdown_source ) { 81 return $title; 82 } 83 84 $src = plugins_url( 'assets/images/github-mark.svg', dirname( dirname( __DIR__ ) ) . '/wporg-cli/wporg-cli.php' ); 85 86 return $title . ' <a class="github-edit" href="' . esc_url( $markdown_source ) . '"><img src="' . esc_url( $src ) . '"> <span>Edit</span></a>'; 87 } 88 89 /** 90 * WP-CLI Handbook pages are maintained in the GitHub repo, so the edit 91 * link should ridirect to there. 92 */ 93 public function redirect_edit_link_to_github( $link, $post_id, $context ) { 94 if ( is_admin() ) { 95 return $link; 96 } 97 $post = get_post( $post_id ); 98 if ( ! $post ) { 99 return $link; 100 } 101 102 if ( $this->importer->get_post_type() !== $post->post_type ) { 103 return $link; 104 } 105 106 $markdown_source = $this->get_markdown_edit_link( $post_id ); 107 if ( ! $markdown_source ) { 108 return $link; 109 } 110 111 if ( 'display' === $context ) { 112 $markdown_source = esc_url( $markdown_source ); 113 } 114 115 return $markdown_source; 116 } 117 118 /** 119 * o2 does inline editing, so we also need to remove the class name that it looks for. 120 * 121 * o2 obeys the edit_post capability for displaying the edit link, so we also need to manually 122 * add the edit link if it isn't there - it always redirects to GitHub, so it doesn't need to 123 * obey the edit_post capability in this instance. 124 */ 125 public static function redirect_o2_edit_link_to_github( $actions, $post_id ) { 126 $post = get_post( $post_id ); 127 if ( ! $post ) { 128 return $actions; 129 } 130 131 if ( $this->importer->get_post_type() !== $post->post_type ) { 132 return $actions; 133 } 134 135 $markdown_source = $this->get_markdown_edit_link( $post_id ); 136 if ( ! $markdown_source ) { 137 return $actions; 138 } 139 140 /* 141 * Define our own edit post action for o2. 142 * 143 * Notable differences from the original are: 144 * - the 'href' parameter always goes to the GitHub source. 145 * - the 'o2-edit' class is missing, so inline editing is disabled. 146 */ 147 $edit_action = array( 148 'action' => 'edit', 149 'href' => $markdown_source, 150 'classes' => array( 'edit-post-link' ), 151 'rel' => $post_id, 152 'initialState' => 'default' 153 ); 154 155 // Find and replace the existing edit action. 156 $replaced = false; 157 foreach( $actions as &$action ) { 158 if ( 'edit' === $action['action'] ) { 159 $action = $edit_action; 160 $replaced = true; 161 break; 162 } 163 } 164 unset( $action ); 165 166 // If there was no edit action replaced, add it in manually. 167 if ( ! $replaced ) { 168 $actions[30] = $edit_action; 169 } 170 171 return $actions; 172 } 173 174 protected function get_markdown_edit_link( $post_id ) { 175 $markdown_source = $this->importer->get_markdown_source( $post_id ); 176 if ( is_wp_error( $markdown_source ) ) { 177 return ''; 178 } 179 if ( 'github.com' !== parse_url( $markdown_source, PHP_URL_HOST ) 180 || false !== stripos( $markdown_source, '/edit/master/' ) ) { 181 return $markdown_source; 182 } 183 $markdown_source = str_replace( '/blob/master/', '/edit/master/', $markdown_source ); 184 return $markdown_source; 185 } 186 } -
wordpress.org/public_html/wp-content/plugins/wporg-markdown/inc/class-importer.php
1 <?php 2 3 namespace WordPressdotorg\Markdown; 4 5 use WP_CLI; 6 use WP_Error; 7 use WP_Post; 8 use WP_Query; 9 use WPCom_GHF_Markdown_Parser; 10 11 abstract class Importer { 12 /** 13 * Meta key to store source in. 14 * 15 * @var string 16 */ 17 protected $meta_key = 'wporg_markdown_source'; 18 19 /** 20 * Meta key to store request ETag in. 21 * 22 * @var string 23 */ 24 protected $etag_meta_key = 'wporg_markdown_etag'; 25 26 /** 27 * Posts per page to query for. 28 * 29 * This needs to be set at least as high as the number of pages being 30 * imported, but should not be unbounded (-1). 31 * 32 * @var int 33 */ 34 protected $posts_per_page = 350; 35 36 /** 37 * Get base URL for all pages. 38 * 39 * This is used for generating the keys for the existing pages. 40 * 41 * @see static::get_existing_for_post() 42 * 43 * @return string Base URL to strip from page permalink. 44 */ 45 abstract protected function get_base(); 46 47 /** 48 * Get manifest URL. 49 * 50 * This URL should point to a JSON file containing the manifest for the 51 * site's content. (Typically raw.githubusercontent.com) 52 * 53 * @return string URL for the manifest file. 54 */ 55 abstract protected function get_manifest_url(); 56 57 /** 58 * Get post type for the type being imported. 59 * 60 * @return string Post type slug to import as. 61 */ 62 abstract public function get_post_type(); 63 64 /** 65 * Get existing data for a given post. 66 * 67 * @param WP_Post $post Post to get existing data for. 68 * @return array 2-tuple of array key and data. 69 */ 70 protected function get_existing_for_post( WP_Post $post ) { 71 $key = rtrim( str_replace( $this->get_base(), '', get_permalink( $post->ID ) ), '/' ); 72 if ( empty( $key ) ) { 73 $key = 'index'; 74 } 75 76 $data = array( 77 'post_id' => $post->ID, 78 ); 79 return array( $key, $data ); 80 } 81 82 /** 83 * Import the manifest. 84 * 85 * Fetches the manifest, parses, and creates pages as needed. 86 */ 87 public function import_manifest() { 88 $response = wp_remote_get( $this->get_manifest_url() ); 89 if ( is_wp_error( $response ) ) { 90 if ( class_exists( 'WP_CLI' ) ) { 91 WP_CLI::error( $response->get_error_message() ); 92 } 93 return $response; 94 } elseif ( 200 !== wp_remote_retrieve_response_code( $response ) ) { 95 if ( class_exists( 'WP_CLI' ) ) { 96 WP_CLI::error( 'Non-200 from Markdown source' ); 97 } 98 return new WP_Error( 'invalid-http-code', 'Markdown source returned non-200 http code.' ); 99 } 100 $manifest = json_decode( wp_remote_retrieve_body( $response ), true ); 101 if ( ! $manifest ) { 102 if ( class_exists( 'WP_CLI' ) ) { 103 WP_CLI::error( 'Invalid manifest' ); 104 } 105 return new WP_Error( 'invalid-manifest', 'Manifest did not unfurl properly.' );; 106 } 107 // Fetch all handbook posts for comparison 108 $q = new WP_Query( array( 109 'post_type' => $this->get_post_type(), 110 'post_status' => 'publish', 111 'posts_per_page' => $this->posts_per_page, 112 ) ); 113 $existing = array(); 114 foreach ( $q->posts as $post ) { 115 list( $key, $data ) = $this->get_existing_for_post( $post ); 116 $existing[ $key ] = $data; 117 } 118 $created = $updated = 0; 119 foreach ( $manifest as $key => $doc ) { 120 // Already exists, update. 121 if ( ! empty( $existing[ $key ] ) ) { 122 $existing_id = $existing[ $key ]['post_id']; 123 if ( $this->update_post_from_manifest_doc( $existing_id, $doc ) ) { 124 $updated++; 125 } 126 127 continue; 128 } 129 if ( $this->process_manifest_doc( $doc, $existing, $manifest ) ) { 130 $created++; 131 } 132 } 133 if ( class_exists( 'WP_CLI' ) ) { 134 WP_CLI::success( "Successfully created {$created} and updated {$updated} handbook pages." ); 135 } 136 } 137 138 /** 139 * Process a document from the manifest. 140 * 141 * @param array $doc Document to process. 142 * @param array $existing List of existing posts, will be added to. 143 * @param array $manifest Manifest data. 144 * @return boolean True if processing succeeded, false otherwise. 145 */ 146 protected function process_manifest_doc( $doc, &$existing, $manifest ) { 147 $post_parent = null; 148 if ( ! empty( $doc['parent'] ) ) { 149 // Find the parent in the existing set 150 if ( empty( $existing[ $doc['parent'] ] ) ) { 151 if ( ! $this->process_manifest_doc( $manifest[ $doc['parent'] ], $existing, $manifest ) ) { 152 return false; 153 } 154 } 155 if ( ! empty( $existing[ $doc['parent'] ] ) ) { 156 $parent = $existing[ $doc['parent'] ]; 157 $post_parent = $parent['post_id']; 158 } 159 } 160 $post = $this->create_post_from_manifest_doc( $doc, $post_parent ); 161 if ( $post ) { 162 list( $key, $data ) = $this->get_existing_for_post( $post ); 163 $existing[ $key ] = $data; 164 return true; 165 } 166 return false; 167 } 168 169 /** 170 * Create a new handbook page from the manifest document 171 */ 172 protected function create_post_from_manifest_doc( $doc, $post_parent = null ) { 173 if ( $doc['slug'] === 'index' ) { 174 $doc['slug'] = $this->get_post_type(); 175 } 176 $post_data = array( 177 'post_type' => $this->get_post_type(), 178 'post_status' => 'publish', 179 'post_parent' => $post_parent, 180 'post_title' => wp_slash( $doc['slug'] ), 181 'post_name' => sanitize_title_with_dashes( $doc['slug'] ), 182 ); 183 if ( isset( $doc['title'] ) ) { 184 $doc['post_title'] = sanitize_text_field( wp_slash( $doc['title'] ) ); 185 } 186 $post_id = wp_insert_post( $post_data ); 187 if ( ! $post_id ) { 188 return false; 189 } 190 if ( class_exists( 'WP_CLI' ) ) { 191 WP_CLI::log( "Created post {$post_id} for {$doc['slug']}." ); 192 } 193 update_post_meta( $post_id, $this->meta_key, esc_url_raw( $doc['markdown_source'] ) ); 194 return get_post( $post_id ); 195 } 196 197 /** 198 * Update an existing post from the manifest. 199 * 200 * @param int $post_id Existing post ID. 201 * @param array $doc Document details from the manifest. 202 * @return boolean True if updated, false otherwise. 203 */ 204 protected function update_post_from_manifest_doc( $post_id, $doc ) { 205 $did_update = update_post_meta( $post_id, $this->meta_key, esc_url_raw( $doc['markdown_source'] ) ); 206 if ( ! $did_update ) { 207 return false; 208 } 209 210 if ( isset( $doc['meta'] ) ) { 211 foreach ( $doc['meta'] as $key => $value ) { 212 $did_update = update_post_meta( $post_id, wp_slash( $key ), wp_slash( $value ) ); 213 } 214 } 215 216 return true; 217 } 218 219 /** 220 * Update existing posts from Markdown source. 221 * 222 * Reparses the Markdown for every page. 223 */ 224 public function import_all_markdown() { 225 $q = new WP_Query( array( 226 'post_type' => $this->get_post_type(), 227 'post_status' => 'publish', 228 'fields' => 'ids', 229 'posts_per_page' => $this->posts_per_page, 230 ) ); 231 $ids = $q->posts; 232 $success = 0; 233 foreach( $ids as $id ) { 234 $ret = $this->update_post_from_markdown_source( $id ); 235 if ( class_exists( 'WP_CLI' ) ) { 236 if ( is_wp_error( $ret ) ) { 237 WP_CLI::warning( $ret->get_error_message() ); 238 } elseif ( false === $ret ) { 239 WP_CLI::log( "No updates for {$id}" ); 240 $success++; 241 } else { 242 WP_CLI::log( "Updated {$id} from markdown source" ); 243 $success++; 244 } 245 } 246 } 247 if ( class_exists( 'WP_CLI' ) ) { 248 $total = count( $ids ); 249 WP_CLI::success( "Successfully updated {$success} of {$total} pages." ); 250 } 251 } 252 253 /** 254 * Update a post from its Markdown source. 255 * 256 * @param int $post_id Post ID to update. 257 * @return boolean|WP_Error True if updated, false if no update needed, error otherwise. 258 */ 259 protected function update_post_from_markdown_source( $post_id ) { 260 $markdown_source = $this->get_markdown_source( $post_id ); 261 if ( is_wp_error( $markdown_source ) ) { 262 return $markdown_source; 263 } 264 if ( ! function_exists( 'jetpack_require_lib' ) ) { 265 return new WP_Error( 'missing-jetpack-require-lib', 'jetpack_require_lib() is missing on system.' ); 266 } 267 268 // Transform GitHub repo HTML pages into their raw equivalents 269 $markdown_source = preg_replace( '#https?://github\.com/([^/]+/[^/]+)/blob/(.+)#', 'https://raw.githubusercontent.com/$1/$2', $markdown_source ); 270 $markdown_source = add_query_arg( 'v', time(), $markdown_source ); 271 272 // Grab the stored ETag, and use it to deduplicate. 273 $args = array( 274 'headers' => array(), 275 ); 276 $last_etag = get_post_meta( $post_id, $this->etag_meta_key, true ); 277 if ( ! empty( $last_etag ) ) { 278 $args['headers']['If-None-Match'] = $last_etag; 279 } 280 281 $response = wp_remote_get( $markdown_source, $args ); 282 if ( is_wp_error( $response ) ) { 283 return $response; 284 } elseif ( 304 === wp_remote_retrieve_response_code( $response ) ) { 285 // No update required! 286 return false; 287 } elseif ( 200 !== wp_remote_retrieve_response_code( $response ) ) { 288 return new WP_Error( 'invalid-http-code', 'Markdown source returned non-200 http code.' ); 289 } 290 291 $etag = wp_remote_retrieve_header( $response, 'etag' ); 292 293 $markdown = wp_remote_retrieve_body( $response ); 294 // Strip YAML doc from the header 295 $markdown = preg_replace( '#^---(.+)---#Us', '', $markdown ); 296 297 $title = null; 298 if ( preg_match( '/^#\s(.+)/', $markdown, $matches ) ) { 299 $title = $matches[1]; 300 $markdown = preg_replace( '/^#\swp\s(.+)/', '', $markdown ); 301 } 302 $markdown = trim( $markdown ); 303 304 // Steal the first sentence as the excerpt 305 $excerpt = ''; 306 if ( preg_match( '/^(.+)/', $markdown, $matches ) ) { 307 $excerpt = $matches[1]; 308 $markdown = preg_replace( '/^(.+)/', '', $markdown ); 309 } 310 311 // Transform to HTML and save the post 312 jetpack_require_lib( 'markdown' ); 313 $parser = new WPCom_GHF_Markdown_Parser(); 314 $parser->preserve_shortcodes = false; 315 $html = $parser->transform( $markdown ); 316 $post_data = array( 317 'ID' => $post_id, 318 'post_content' => wp_filter_post_kses( wp_slash( $html ) ), 319 'post_excerpt' => sanitize_text_field( wp_slash( $excerpt ) ), 320 ); 321 if ( ! is_null( $title ) ) { 322 $post_data['post_title'] = sanitize_text_field( wp_slash( $title ) ); 323 } 324 wp_update_post( $post_data ); 325 326 // Set ETag for future updates. 327 update_post_meta( $post_id, $this->etag_meta_key, wp_slash( $etag ) ); 328 329 return true; 330 } 331 332 /** 333 * Retrieve the markdown source URL for a given post. 334 */ 335 public function get_markdown_source( $post_id ) { 336 $markdown_source = get_post_meta( $post_id, $this->meta_key, true ); 337 if ( ! $markdown_source ) { 338 return new WP_Error( 'missing-markdown-source', 'Markdown source is missing for post.' ); 339 } 340 341 return $markdown_source; 342 } 343 } -
wordpress.org/public_html/wp-content/plugins/wporg-markdown/plugin.php
1 <?php 2 /** 3 * Plugin Name: WPORG Markdown Importer 4 * Description: Automatic Markdown imports for handbooks and DevHub (CLI/API handbooks) 5 * Author: Daniel Bachhuber and Ryan McCue 6 */ 7 8 require __DIR__ . '/inc/class-editor.php'; 9 require __DIR__ . '/inc/class-importer.php'; -
wordpress.org/public_html/wp-content/themes/pub/wporg-developer/functions.php
61 61 require __DIR__ . '/inc/cli.php'; 62 62 63 63 /** 64 * REST API handbook. 65 */ 66 if ( class_exists( '\\WordPressdotorg\\Markdown\\Importer' ) ) { 67 require __DIR__ . '/inc/rest-api.php'; 68 } 69 70 /** 64 71 * Explanations for functions. hooks, classes, and methods. 65 72 */ 66 73 require( __DIR__ . '/inc/explanations.php' ); -
wordpress.org/public_html/wp-content/themes/pub/wporg-developer/inc/rest-api.php
1 <?php 2 3 use WordPressdotorg\Markdown\Editor; 4 use WordPressdotorg\Markdown\Importer; 5 6 class DevHub_REST_API extends Importer { 7 /** 8 * Singleton instance. 9 * 10 * @var static 11 */ 12 protected static $instance; 13 14 /** 15 * Get the singleton instance, or create if needed. 16 * 17 * @return static 18 */ 19 public static function instance() { 20 if ( empty( static::$instance ) ) { 21 static::$instance = new static(); 22 } 23 24 return static::$instance; 25 } 26 27 protected function get_base() { 28 return home_url( 'rest-api/' ); 29 } 30 31 protected function get_manifest_url() { 32 return 'https://raw.githubusercontent.com/WP-API/docs/master/bin/manifest.json'; 33 } 34 35 public function get_post_type() { 36 return 'rest-api-handbook'; 37 } 38 39 public function init() { 40 add_filter( 'cron_schedules', array( $this, 'filter_cron_schedules' ) ); 41 add_action( 'init', array( $this, 'register_cron_jobs' ) ); 42 add_action( 'devhub_restapi_import_manifest', array( $this, 'import_manifest' ) ); 43 add_action( 'devhub_restapi_import_all_markdown', array( $this, 'import_all_markdown' ) ); 44 45 $editor = new Editor( $this ); 46 $editor->init(); 47 } 48 49 /** 50 * Filter cron schedules to add a 15 minute schedule, if there isn't one. 51 */ 52 public function filter_cron_schedules( $schedules ) { 53 if ( empty( $schedules['15_minutes'] ) ) { 54 $schedules['15_minutes'] = array( 55 'interval' => 15 * MINUTE_IN_SECONDS, 56 'display' => '15 minutes' 57 ); 58 } 59 return $schedules; 60 } 61 62 public function register_cron_jobs() { 63 if ( ! wp_next_scheduled( 'devhub_restapi_import_manifest' ) ) { 64 wp_schedule_event( time(), '15_minutes', 'devhub_restapi_import_manifest' ); 65 } 66 if ( ! wp_next_scheduled( 'devhub_restapi_import_all_markdown' ) ) { 67 wp_schedule_event( time(), '15_minutes', 'devhub_restapi_import_all_markdown' ); 68 } 69 } 70 } 71 72 DevHub_REST_API::instance()->init(); 73