| 1 | <?php |
| 2 | |
| 3 | class DevHub_CLI { |
| 4 | |
| 5 | private static $commands_manifest = 'https://raw.githubusercontent.com/wp-cli/handbook/master/bin/commands-manifest.json'; |
| 6 | private static $meta_key = 'wporg_cli_markdown_source'; |
| 7 | private static $supported_post_types = array( 'command' ); |
| 8 | private static $posts_per_page = 350; |
| 9 | |
| 10 | public function __construct() { |
| 11 | $this->init(); |
| 12 | } |
| 13 | |
| 14 | public function init() { |
| 15 | add_action( 'init', array( $this, 'action_init_register_cron_jobs' ) ); |
| 16 | add_action( 'init', array( $this, 'action_init_register_post_types' ) ); |
| 17 | add_action( 'devhub_cli_manifest_import', array( $this, 'action_devhub_cli_manifest_import' ) ); |
| 18 | add_action( 'devhub_cli_markdown_import', array( $this, 'action_devhub_cli_markdown_import' ) ); |
| 19 | } |
| 20 | |
| 21 | public static function action_init_register_cron_jobs() { |
| 22 | if ( ! wp_next_scheduled( 'devhub_cli_manifest_import' ) ) { |
| 23 | wp_schedule_event( time(), 'twicedaily', 'devhub_cli_manifest_import' ); |
| 24 | } |
| 25 | if ( ! wp_next_scheduled( 'devhub_cli_markdown_import' ) ) { |
| 26 | wp_schedule_event( time(), 'twicedaily', 'devhub_cli_markdown_import' ); |
| 27 | } |
| 28 | } |
| 29 | |
| 30 | public static function action_init_register_post_types() { |
| 31 | $supports = array( |
| 32 | 'comments', |
| 33 | 'custom-fields', |
| 34 | 'editor', |
| 35 | 'excerpt', |
| 36 | 'revisions', |
| 37 | 'title', |
| 38 | ); |
| 39 | register_post_type( 'command', array( |
| 40 | 'has_archive' => 'cli/commands', |
| 41 | 'label' => __( 'Commands', 'wporg' ), |
| 42 | 'labels' => array( |
| 43 | 'name' => __( 'Commands', 'wporg' ), |
| 44 | 'singular_name' => __( 'Command', 'wporg' ), |
| 45 | 'all_items' => __( 'Commands', 'wporg' ), |
| 46 | 'new_item' => __( 'New Command', 'wporg' ), |
| 47 | 'add_new' => __( 'Add New', 'wporg' ), |
| 48 | 'add_new_item' => __( 'Add New Command', 'wporg' ), |
| 49 | 'edit_item' => __( 'Edit Command', 'wporg' ), |
| 50 | 'view_item' => __( 'View Command', 'wporg' ), |
| 51 | 'search_items' => __( 'Search Commands', 'wporg' ), |
| 52 | 'not_found' => __( 'No Commands found', 'wporg' ), |
| 53 | 'not_found_in_trash' => __( 'No Commands found in trash', 'wporg' ), |
| 54 | 'parent_item_colon' => __( 'Parent Command', 'wporg' ), |
| 55 | 'menu_name' => __( 'Commands', 'wporg' ), |
| 56 | ), |
| 57 | 'public' => true, |
| 58 | 'hierarchical'=> true, |
| 59 | 'rewrite' => array( |
| 60 | 'feeds' => false, |
| 61 | 'slug' => 'cli/commands', |
| 62 | 'with_front' => false, |
| 63 | ), |
| 64 | 'supports' => $supports, |
| 65 | ) ); |
| 66 | } |
| 67 | |
| 68 | public static function action_devhub_cli_manifest_import() { |
| 69 | $response = wp_remote_get( self::$commands_manifest ); |
| 70 | if ( is_wp_error( $response ) ) { |
| 71 | return $response; |
| 72 | } elseif ( 200 !== wp_remote_retrieve_response_code( $response ) ) { |
| 73 | return new WP_Error( 'invalid-http-code', 'Markdown source returned non-200 http code.' ); |
| 74 | } |
| 75 | $manifest = json_decode( wp_remote_retrieve_body( $response ), true ); |
| 76 | if ( ! $manifest ) { |
| 77 | return new WP_Error( 'invalid-manifest', 'Manifest did not unfurl properly.' );; |
| 78 | } |
| 79 | // Fetch all handbook posts for comparison |
| 80 | $q = new WP_Query( array( |
| 81 | 'post_type' => self::$supported_post_types, |
| 82 | 'post_status' => 'publish', |
| 83 | 'posts_per_page' => self::$posts_per_page, |
| 84 | ) ); |
| 85 | $existing = array(); |
| 86 | foreach( $q->posts as $post ) { |
| 87 | $cmd_path = rtrim( str_replace( home_url( 'cli/commands/' ), '', get_permalink( $post->ID ) ), '/' ); |
| 88 | $existing[ $cmd_path ] = array( |
| 89 | 'post_id' => $post->ID, |
| 90 | 'cmd_path' => $cmd_path, |
| 91 | ); |
| 92 | } |
| 93 | $created = 0; |
| 94 | foreach( $manifest as $doc ) { |
| 95 | // Already exists |
| 96 | if ( wp_filter_object_list( $existing, array( 'cmd_path' => $doc['cmd_path'] ) ) ) { |
| 97 | continue; |
| 98 | } |
| 99 | if ( self::process_manifest_doc( $doc, $existing, $manifest ) ) { |
| 100 | $created++; |
| 101 | } |
| 102 | } |
| 103 | if ( class_exists( 'WP_CLI' ) ) { |
| 104 | \WP_CLI::success( "Successfully created {$created} handbook pages." ); |
| 105 | } |
| 106 | } |
| 107 | |
| 108 | private static function process_manifest_doc( $doc, &$existing, $manifest ) { |
| 109 | $post_parent = null; |
| 110 | if ( ! empty( $doc['parent'] ) ) { |
| 111 | // Find the parent in the existing set |
| 112 | $parents = wp_filter_object_list( $existing, array( 'cmd_path' => $doc['parent'] ) ); |
| 113 | if ( empty( $parents ) ) { |
| 114 | if ( ! self::process_manifest_doc( $manifest[ $doc['parent'] ], $existing, $manifest ) ) { |
| 115 | return; |
| 116 | } |
| 117 | $parents = wp_filter_object_list( $existing, array( 'cmd_path' => $doc['parent'] ) ); |
| 118 | } |
| 119 | if ( ! empty( $parents ) ) { |
| 120 | $parent = array_shift( $parents ); |
| 121 | $post_parent = $parent['post_id']; |
| 122 | } |
| 123 | } |
| 124 | $post = self::create_post_from_manifest_doc( $doc, $post_parent ); |
| 125 | if ( $post ) { |
| 126 | $cmd_path = rtrim( str_replace( home_url( 'cli/commands/' ), '', get_permalink( $post->ID ) ), '/' ); |
| 127 | $existing[ $cmd_path ] = array( |
| 128 | 'post_id' => $post->ID, |
| 129 | 'cmd_path' => $cmd_path, |
| 130 | ); |
| 131 | return true; |
| 132 | } |
| 133 | return false; |
| 134 | } |
| 135 | |
| 136 | public static function action_devhub_cli_markdown_import() { |
| 137 | $q = new WP_Query( array( |
| 138 | 'post_type' => self::$supported_post_types, |
| 139 | 'post_status' => 'publish', |
| 140 | 'fields' => 'ids', |
| 141 | 'posts_per_page' => self::$posts_per_page, |
| 142 | ) ); |
| 143 | $ids = $q->posts; |
| 144 | $success = 0; |
| 145 | foreach( $ids as $id ) { |
| 146 | $ret = self::update_post_from_markdown_source( $id ); |
| 147 | if ( class_exists( 'WP_CLI' ) ) { |
| 148 | if ( is_wp_error( $ret ) ) { |
| 149 | \WP_CLI::warning( $ret->get_error_message() ); |
| 150 | } else { |
| 151 | \WP_CLI::log( "Updated {$id} from markdown source" ); |
| 152 | $success++; |
| 153 | } |
| 154 | } |
| 155 | } |
| 156 | if ( class_exists( 'WP_CLI' ) ) { |
| 157 | $total = count( $ids ); |
| 158 | \WP_CLI::success( "Successfully updated {$success} of {$total} CLI command pages." ); |
| 159 | } |
| 160 | } |
| 161 | |
| 162 | /** |
| 163 | * Create a new handbook page from the manifest document |
| 164 | */ |
| 165 | private static function create_post_from_manifest_doc( $doc, $post_parent = null ) { |
| 166 | $post_data = array( |
| 167 | 'post_type' => 'command', |
| 168 | 'post_status' => 'publish', |
| 169 | 'post_parent' => $post_parent, |
| 170 | 'post_title' => sanitize_text_field( wp_slash( $doc['title'] ) ), |
| 171 | 'post_name' => sanitize_title_with_dashes( $doc['slug'] ), |
| 172 | ); |
| 173 | $post_id = wp_insert_post( $post_data ); |
| 174 | if ( ! $post_id ) { |
| 175 | return false; |
| 176 | } |
| 177 | if ( class_exists( 'WP_CLI' ) ) { |
| 178 | \WP_CLI::log( "Created post {$post_id} for {$doc['title']}." ); |
| 179 | } |
| 180 | update_post_meta( $post_id, self::$meta_key, esc_url_raw( $doc['markdown_source'] ) ); |
| 181 | return get_post( $post_id ); |
| 182 | } |
| 183 | |
| 184 | /** |
| 185 | * Update a post from its Markdown source |
| 186 | */ |
| 187 | private static function update_post_from_markdown_source( $post_id ) { |
| 188 | $markdown_source = self::get_markdown_source( $post_id ); |
| 189 | if ( is_wp_error( $markdown_source ) ) { |
| 190 | return $markdown_source; |
| 191 | } |
| 192 | if ( ! function_exists( 'jetpack_require_lib' ) ) { |
| 193 | return new WP_Error( 'missing-jetpack-require-lib', 'jetpack_require_lib() is missing on system.' ); |
| 194 | } |
| 195 | |
| 196 | // Transform GitHub repo HTML pages into their raw equivalents |
| 197 | $markdown_source = preg_replace( '#https?://github\.com/([^/]+/[^/]+)/blob/(.+)#', 'https://raw.githubusercontent.com/$1/$2', $markdown_source ); |
| 198 | $markdown_source = add_query_arg( 'v', time(), $markdown_source ); |
| 199 | $response = wp_remote_get( $markdown_source ); |
| 200 | if ( is_wp_error( $response ) ) { |
| 201 | return $response; |
| 202 | } elseif ( 200 !== wp_remote_retrieve_response_code( $response ) ) { |
| 203 | return new WP_Error( 'invalid-http-code', 'Markdown source returned non-200 http code.' ); |
| 204 | } |
| 205 | |
| 206 | $markdown = wp_remote_retrieve_body( $response ); |
| 207 | // Strip YAML doc from the header |
| 208 | $markdown = preg_replace( '#^---(.+)---#Us', '', $markdown ); |
| 209 | |
| 210 | $title = null; |
| 211 | if ( preg_match( '/^#\s(.+)/', $markdown, $matches ) ) { |
| 212 | $title = $matches[1]; |
| 213 | $markdown = preg_replace( '/^#\swp\s(.+)/', '', $markdown ); |
| 214 | } |
| 215 | |
| 216 | // Transform to HTML and save the post |
| 217 | jetpack_require_lib( 'markdown' ); |
| 218 | $parser = new \WPCom_GHF_Markdown_Parser; |
| 219 | $html = $parser->transform( $markdown ); |
| 220 | $post_data = array( |
| 221 | 'ID' => $post_id, |
| 222 | 'post_content' => wp_filter_post_kses( wp_slash( $html ) ), |
| 223 | ); |
| 224 | if ( ! is_null( $title ) ) { |
| 225 | $post_data['post_title'] = sanitize_text_field( wp_slash( $title ) ); |
| 226 | } |
| 227 | wp_update_post( $post_data ); |
| 228 | return true; |
| 229 | } |
| 230 | |
| 231 | /** |
| 232 | * Retrieve the markdown source URL for a given post. |
| 233 | */ |
| 234 | public static function get_markdown_source( $post_id ) { |
| 235 | $markdown_source = get_post_meta( $post_id, self::$meta_key, true ); |
| 236 | if ( ! $markdown_source ) { |
| 237 | return new WP_Error( 'missing-markdown-source', 'Markdown source is missing for post.' ); |
| 238 | } |
| 239 | |
| 240 | return $markdown_source; |
| 241 | } |
| 242 | |
| 243 | } |
| 244 | |
| 245 | $devhub_cli = new DevHub_CLI(); |