[ Index ] |
PHP Cross Reference of WordPress |
[Summary view] [Print] [Text view]
1 <?php 2 /** 3 * Press This class and display functionality 4 * 5 * @package WordPress 6 * @subpackage Press_This 7 * @since 4.2.0 8 */ 9 10 /** 11 * Press This class. 12 * 13 * @since 4.2.0 14 */ 15 class WP_Press_This { 16 // Used to trigger the bookmarklet update notice. 17 const VERSION = 8; 18 public $version = 8; 19 20 private $images = array(); 21 22 private $embeds = array(); 23 24 private $domain = ''; 25 26 /** 27 * Constructor. 28 * 29 * @since 4.2.0 30 */ 31 public function __construct() {} 32 33 /** 34 * App and site settings data, including i18n strings for the client-side. 35 * 36 * @since 4.2.0 37 * 38 * @return array Site settings. 39 */ 40 public function site_settings() { 41 return array( 42 /** 43 * Filters whether or not Press This should redirect the user in the parent window upon save. 44 * 45 * @since 4.2.0 46 * 47 * @param bool $redirect Whether to redirect in parent window or not. Default false. 48 */ 49 'redirInParent' => apply_filters( 'press_this_redirect_in_parent', false ), 50 ); 51 } 52 53 /** 54 * Get the source's images and save them locally, for posterity, unless we can't. 55 * 56 * @since 4.2.0 57 * 58 * @param int $post_id Post ID. 59 * @param string $content Optional. Current expected markup for Press This. Expects slashed. Default empty. 60 * @return string New markup with old image URLs replaced with the local attachment ones if swapped. 61 */ 62 public function side_load_images( $post_id, $content = '' ) { 63 $content = wp_unslash( $content ); 64 65 if ( preg_match_all( '/<img [^>]+>/', $content, $matches ) && current_user_can( 'upload_files' ) ) { 66 foreach ( (array) $matches[0] as $image ) { 67 // This is inserted from our JS so HTML attributes should always be in double quotes. 68 if ( ! preg_match( '/src="([^"]+)"/', $image, $url_matches ) ) { 69 continue; 70 } 71 72 $image_src = $url_matches[1]; 73 74 // Don't try to sideload a file without a file extension, leads to WP upload error. 75 if ( ! preg_match( '/[^\?]+\.(?:jpe?g|jpe|gif|png)(?:\?|$)/i', $image_src ) ) { 76 continue; 77 } 78 79 // Sideload image, which gives us a new image src. 80 $new_src = media_sideload_image( $image_src, $post_id, null, 'src' ); 81 82 if ( ! is_wp_error( $new_src ) ) { 83 // Replace the POSTED content <img> with correct uploaded ones. 84 // Need to do it in two steps so we don't replace links to the original image if any. 85 $new_image = str_replace( $image_src, $new_src, $image ); 86 $content = str_replace( $image, $new_image, $content ); 87 } 88 } 89 } 90 91 // Expected slashed 92 return wp_slash( $content ); 93 } 94 95 /** 96 * Ajax handler for saving the post as draft or published. 97 * 98 * @since 4.2.0 99 */ 100 public function save_post() { 101 if ( empty( $_POST['post_ID'] ) || ! $post_id = (int) $_POST['post_ID'] ) { 102 wp_send_json_error( array( 'errorMessage' => __( 'Missing post ID.' ) ) ); 103 } 104 105 if ( empty( $_POST['_wpnonce'] ) || ! wp_verify_nonce( $_POST['_wpnonce'], 'update-post_' . $post_id ) || 106 ! current_user_can( 'edit_post', $post_id ) ) { 107 108 wp_send_json_error( array( 'errorMessage' => __( 'Invalid post.' ) ) ); 109 } 110 111 $post_data = array( 112 'ID' => $post_id, 113 'post_title' => ( ! empty( $_POST['post_title'] ) ) ? sanitize_text_field( trim( $_POST['post_title'] ) ) : '', 114 'post_content' => ( ! empty( $_POST['post_content'] ) ) ? trim( $_POST['post_content'] ) : '', 115 'post_type' => 'post', 116 'post_status' => 'draft', 117 'post_format' => ( ! empty( $_POST['post_format'] ) ) ? sanitize_text_field( $_POST['post_format'] ) : '', 118 ); 119 120 // Only accept categories if the user actually can assign 121 $category_tax = get_taxonomy( 'category' ); 122 if ( current_user_can( $category_tax->cap->assign_terms ) ) { 123 $post_data['post_category'] = ( ! empty( $_POST['post_category'] ) ) ? $_POST['post_category'] : array(); 124 } 125 126 // Only accept taxonomies if the user can actually assign 127 if ( ! empty( $_POST['tax_input'] ) ) { 128 $tax_input = $_POST['tax_input']; 129 foreach ( $tax_input as $tax => $_ti ) { 130 $tax_object = get_taxonomy( $tax ); 131 if ( ! $tax_object || ! current_user_can( $tax_object->cap->assign_terms ) ) { 132 unset( $tax_input[ $tax ] ); 133 } 134 } 135 136 $post_data['tax_input'] = $tax_input; 137 } 138 139 // Toggle status to pending if user cannot actually publish 140 if ( ! empty( $_POST['post_status'] ) && 'publish' === $_POST['post_status'] ) { 141 if ( current_user_can( 'publish_posts' ) ) { 142 $post_data['post_status'] = 'publish'; 143 } else { 144 $post_data['post_status'] = 'pending'; 145 } 146 } 147 148 $post_data['post_content'] = $this->side_load_images( $post_id, $post_data['post_content'] ); 149 150 /** 151 * Filters the post data of a Press This post before saving/updating. 152 * 153 * The {@see 'side_load_images'} action has already run at this point. 154 * 155 * @since 4.5.0 156 * 157 * @param array $post_data The post data. 158 */ 159 $post_data = apply_filters( 'press_this_save_post', $post_data ); 160 161 $updated = wp_update_post( $post_data, true ); 162 163 if ( is_wp_error( $updated ) ) { 164 wp_send_json_error( array( 'errorMessage' => $updated->get_error_message() ) ); 165 } else { 166 if ( isset( $post_data['post_format'] ) ) { 167 if ( current_theme_supports( 'post-formats', $post_data['post_format'] ) ) { 168 set_post_format( $post_id, $post_data['post_format'] ); 169 } elseif ( $post_data['post_format'] ) { 170 set_post_format( $post_id, false ); 171 } 172 } 173 174 $forceRedirect = false; 175 176 if ( 'publish' === get_post_status( $post_id ) ) { 177 $redirect = get_post_permalink( $post_id ); 178 } elseif ( isset( $_POST['pt-force-redirect'] ) && $_POST['pt-force-redirect'] === 'true' ) { 179 $forceRedirect = true; 180 $redirect = get_edit_post_link( $post_id, 'js' ); 181 } else { 182 $redirect = false; 183 } 184 185 /** 186 * Filters the URL to redirect to when Press This saves. 187 * 188 * @since 4.2.0 189 * 190 * @param string $url Redirect URL. If `$status` is 'publish', this will be the post permalink. 191 * Otherwise, the default is false resulting in no redirect. 192 * @param int $post_id Post ID. 193 * @param string $status Post status. 194 */ 195 $redirect = apply_filters( 'press_this_save_redirect', $redirect, $post_id, $post_data['post_status'] ); 196 197 if ( $redirect ) { 198 wp_send_json_success( array( 'redirect' => $redirect, 'force' => $forceRedirect ) ); 199 } else { 200 wp_send_json_success( array( 'postSaved' => true ) ); 201 } 202 } 203 } 204 205 /** 206 * Ajax handler for adding a new category. 207 * 208 * @since 4.2.0 209 */ 210 public function add_category() { 211 if ( false === wp_verify_nonce( $_POST['new_cat_nonce'], 'add-category' ) ) { 212 wp_send_json_error(); 213 } 214 215 $taxonomy = get_taxonomy( 'category' ); 216 217 if ( ! current_user_can( $taxonomy->cap->edit_terms ) || empty( $_POST['name'] ) ) { 218 wp_send_json_error(); 219 } 220 221 $parent = isset( $_POST['parent'] ) && (int) $_POST['parent'] > 0 ? (int) $_POST['parent'] : 0; 222 $names = explode( ',', $_POST['name'] ); 223 $added = $data = array(); 224 225 foreach ( $names as $cat_name ) { 226 $cat_name = trim( $cat_name ); 227 $cat_nicename = sanitize_title( $cat_name ); 228 229 if ( empty( $cat_nicename ) ) { 230 continue; 231 } 232 233 // @todo Find a more performant way to check existence, maybe get_term() with a separate parent check. 234 if ( term_exists( $cat_name, $taxonomy->name, $parent ) ) { 235 if ( count( $names ) === 1 ) { 236 wp_send_json_error( array( 'errorMessage' => __( 'This category already exists.' ) ) ); 237 } else { 238 continue; 239 } 240 } 241 242 $cat_id = wp_insert_term( $cat_name, $taxonomy->name, array( 'parent' => $parent ) ); 243 244 if ( is_wp_error( $cat_id ) ) { 245 continue; 246 } elseif ( is_array( $cat_id ) ) { 247 $cat_id = $cat_id['term_id']; 248 } 249 250 $added[] = $cat_id; 251 } 252 253 if ( empty( $added ) ) { 254 wp_send_json_error( array( 'errorMessage' => __( 'This category cannot be added. Please change the name and try again.' ) ) ); 255 } 256 257 foreach ( $added as $new_cat_id ) { 258 $new_cat = get_category( $new_cat_id ); 259 260 if ( is_wp_error( $new_cat ) ) { 261 wp_send_json_error( array( 'errorMessage' => __( 'Error while adding the category. Please try again later.' ) ) ); 262 } 263 264 $data[] = array( 265 'term_id' => $new_cat->term_id, 266 'name' => $new_cat->name, 267 'parent' => $new_cat->parent, 268 ); 269 } 270 wp_send_json_success( $data ); 271 } 272 273 /** 274 * Downloads the source's HTML via server-side call for the given URL. 275 * 276 * @since 4.2.0 277 * 278 * @param string $url URL to scan. 279 * @return string Source's HTML sanitized markup 280 */ 281 public function fetch_source_html( $url ) { 282 if ( empty( $url ) ) { 283 return new WP_Error( 'invalid-url', __( 'A valid URL was not provided.' ) ); 284 } 285 286 $remote_url = wp_safe_remote_get( $url, array( 287 'timeout' => 30, 288 // Use an explicit user-agent for Press This 289 'user-agent' => 'Press This (WordPress/' . get_bloginfo( 'version' ) . '); ' . get_bloginfo( 'url' ) 290 ) ); 291 292 if ( is_wp_error( $remote_url ) ) { 293 return $remote_url; 294 } 295 296 $allowed_elements = array( 297 'img' => array( 298 'src' => true, 299 'width' => true, 300 'height' => true, 301 ), 302 'iframe' => array( 303 'src' => true, 304 ), 305 'link' => array( 306 'rel' => true, 307 'itemprop' => true, 308 'href' => true, 309 ), 310 'meta' => array( 311 'property' => true, 312 'name' => true, 313 'content' => true, 314 ) 315 ); 316 317 $source_content = wp_remote_retrieve_body( $remote_url ); 318 $source_content = wp_kses( $source_content, $allowed_elements ); 319 320 return $source_content; 321 } 322 323 /** 324 * Utility method to limit an array to 50 values. 325 * 326 * @ignore 327 * @since 4.2.0 328 * 329 * @param array $value Array to limit. 330 * @return array Original array if fewer than 50 values, limited array, empty array otherwise. 331 */ 332 private function _limit_array( $value ) { 333 if ( is_array( $value ) ) { 334 if ( count( $value ) > 50 ) { 335 return array_slice( $value, 0, 50 ); 336 } 337 338 return $value; 339 } 340 341 return array(); 342 } 343 344 /** 345 * Utility method to limit the length of a given string to 5,000 characters. 346 * 347 * @ignore 348 * @since 4.2.0 349 * 350 * @param string $value String to limit. 351 * @return bool|int|string If boolean or integer, that value. If a string, the original value 352 * if fewer than 5,000 characters, a truncated version, otherwise an 353 * empty string. 354 */ 355 private function _limit_string( $value ) { 356 $return = ''; 357 358 if ( is_numeric( $value ) || is_bool( $value ) ) { 359 $return = $value; 360 } else if ( is_string( $value ) ) { 361 if ( mb_strlen( $value ) > 5000 ) { 362 $return = mb_substr( $value, 0, 5000 ); 363 } else { 364 $return = $value; 365 } 366 367 $return = html_entity_decode( $return, ENT_QUOTES, 'UTF-8' ); 368 $return = sanitize_text_field( trim( $return ) ); 369 } 370 371 return $return; 372 } 373 374 /** 375 * Utility method to limit a given URL to 2,048 characters. 376 * 377 * @ignore 378 * @since 4.2.0 379 * 380 * @param string $url URL to check for length and validity. 381 * @return string Escaped URL if of valid length (< 2048) and makeup. Empty string otherwise. 382 */ 383 private function _limit_url( $url ) { 384 if ( ! is_string( $url ) ) { 385 return ''; 386 } 387 388 // HTTP 1.1 allows 8000 chars but the "de-facto" standard supported in all current browsers is 2048. 389 if ( strlen( $url ) > 2048 ) { 390 return ''; // Return empty rather than a truncated/invalid URL 391 } 392 393 // Does not look like a URL. 394 if ( ! preg_match( '/^([!#$&-;=?-\[\]_a-z~]|%[0-9a-fA-F]{2})+$/', $url ) ) { 395 return ''; 396 } 397 398 // If the URL is root-relative, prepend the protocol and domain name 399 if ( $url && $this->domain && preg_match( '%^/[^/]+%', $url ) ) { 400 $url = $this->domain . $url; 401 } 402 403 // Not absolute or protocol-relative URL. 404 if ( ! preg_match( '%^(?:https?:)?//[^/]+%', $url ) ) { 405 return ''; 406 } 407 408 return esc_url_raw( $url, array( 'http', 'https' ) ); 409 } 410 411 /** 412 * Utility method to limit image source URLs. 413 * 414 * Excluded URLs include share-this type buttons, loaders, spinners, spacers, WordPress interface images, 415 * tiny buttons or thumbs, mathtag.com or quantserve.com images, or the WordPress.com stats gif. 416 * 417 * @ignore 418 * @since 4.2.0 419 * 420 * @param string $src Image source URL. 421 * @return string If not matched an excluded URL type, the original URL, empty string otherwise. 422 */ 423 private function _limit_img( $src ) { 424 $src = $this->_limit_url( $src ); 425 426 if ( preg_match( '!/ad[sx]?/!i', $src ) ) { 427 // Ads 428 return ''; 429 } else if ( preg_match( '!(/share-?this[^.]+?\.[a-z0-9]{3,4})(\?.*)?$!i', $src ) ) { 430 // Share-this type button 431 return ''; 432 } else if ( preg_match( '!/(spinner|loading|spacer|blank|rss)\.(gif|jpg|png)!i', $src ) ) { 433 // Loaders, spinners, spacers 434 return ''; 435 } else if ( preg_match( '!/([^./]+[-_])?(spinner|loading|spacer|blank)s?([-_][^./]+)?\.[a-z0-9]{3,4}!i', $src ) ) { 436 // Fancy loaders, spinners, spacers 437 return ''; 438 } else if ( preg_match( '!([^./]+[-_])?thumb[^.]*\.(gif|jpg|png)$!i', $src ) ) { 439 // Thumbnails, too small, usually irrelevant to context 440 return ''; 441 } else if ( false !== stripos( $src, '/wp-includes/' ) ) { 442 // Classic WordPress interface images 443 return ''; 444 } else if ( preg_match( '![^\d]\d{1,2}x\d+\.(gif|jpg|png)$!i', $src ) ) { 445 // Most often tiny buttons/thumbs (< 100px wide) 446 return ''; 447 } else if ( preg_match( '!/pixel\.(mathtag|quantserve)\.com!i', $src ) ) { 448 // See mathtag.com and https://www.quantcast.com/how-we-do-it/iab-standard-measurement/how-we-collect-data/ 449 return ''; 450 } else if ( preg_match( '!/[gb]\.gif(\?.+)?$!i', $src ) ) { 451 // WordPress.com stats gif 452 return ''; 453 } 454 455 return $src; 456 } 457 458 /** 459 * Limit embed source URLs to specific providers. 460 * 461 * Not all core oEmbed providers are supported. Supported providers include YouTube, Vimeo, 462 * Daily Motion, SoundCloud, and Twitter. 463 * 464 * @ignore 465 * @since 4.2.0 466 * 467 * @param string $src Embed source URL. 468 * @return string If not from a supported provider, an empty string. Otherwise, a reformatted embed URL. 469 */ 470 private function _limit_embed( $src ) { 471 $src = $this->_limit_url( $src ); 472 473 if ( empty( $src ) ) 474 return ''; 475 476 if ( preg_match( '!//(m|www)\.youtube\.com/(embed|v)/([^?]+)\?.+$!i', $src, $src_matches ) ) { 477 // Embedded Youtube videos (www or mobile) 478 $src = 'https://www.youtube.com/watch?v=' . $src_matches[3]; 479 } else if ( preg_match( '!//player\.vimeo\.com/video/([\d]+)([?/].*)?$!i', $src, $src_matches ) ) { 480 // Embedded Vimeo iframe videos 481 $src = 'https://vimeo.com/' . (int) $src_matches[1]; 482 } else if ( preg_match( '!//vimeo\.com/moogaloop\.swf\?clip_id=([\d]+)$!i', $src, $src_matches ) ) { 483 // Embedded Vimeo Flash videos 484 $src = 'https://vimeo.com/' . (int) $src_matches[1]; 485 } else if ( preg_match( '!//(www\.)?dailymotion\.com/embed/video/([^/?]+)([/?].+)?!i', $src, $src_matches ) ) { 486 // Embedded Daily Motion videos 487 $src = 'https://www.dailymotion.com/video/' . $src_matches[2]; 488 } else { 489 $oembed = _wp_oembed_get_object(); 490 491 if ( ! $oembed->get_provider( $src, array( 'discover' => false ) ) ) { 492 $src = ''; 493 } 494 } 495 496 return $src; 497 } 498 499 /** 500 * Process a meta data entry from the source. 501 * 502 * @ignore 503 * @since 4.2.0 504 * 505 * @param string $meta_name Meta key name. 506 * @param mixed $meta_value Meta value. 507 * @param array $data Associative array of source data. 508 * @return array Processed data array. 509 */ 510 private function _process_meta_entry( $meta_name, $meta_value, $data ) { 511 if ( preg_match( '/:?(title|description|keywords|site_name)$/', $meta_name ) ) { 512 $data['_meta'][ $meta_name ] = $meta_value; 513 } else { 514 switch ( $meta_name ) { 515 case 'og:url': 516 case 'og:video': 517 case 'og:video:secure_url': 518 $meta_value = $this->_limit_embed( $meta_value ); 519 520 if ( ! isset( $data['_embeds'] ) ) { 521 $data['_embeds'] = array(); 522 } 523 524 if ( ! empty( $meta_value ) && ! in_array( $meta_value, $data['_embeds'] ) ) { 525 $data['_embeds'][] = $meta_value; 526 } 527 528 break; 529 case 'og:image': 530 case 'og:image:secure_url': 531 case 'twitter:image0:src': 532 case 'twitter:image0': 533 case 'twitter:image:src': 534 case 'twitter:image': 535 $meta_value = $this->_limit_img( $meta_value ); 536 537 if ( ! isset( $data['_images'] ) ) { 538 $data['_images'] = array(); 539 } 540 541 if ( ! empty( $meta_value ) && ! in_array( $meta_value, $data['_images'] ) ) { 542 $data['_images'][] = $meta_value; 543 } 544 545 break; 546 } 547 } 548 549 return $data; 550 } 551 552 /** 553 * Fetches and parses _meta, _images, and _links data from the source. 554 * 555 * @since 4.2.0 556 * 557 * @param string $url URL to scan. 558 * @param array $data Optional. Existing data array if you have one. Default empty array. 559 * @return array New data array. 560 */ 561 public function source_data_fetch_fallback( $url, $data = array() ) { 562 if ( empty( $url ) ) { 563 return array(); 564 } 565 566 // Download source page to tmp file. 567 $source_content = $this->fetch_source_html( $url ); 568 if ( is_wp_error( $source_content ) ) { 569 return array( 'errors' => $source_content->get_error_messages() ); 570 } 571 572 // Fetch and gather <meta> data first, so discovered media is offered 1st to user. 573 if ( empty( $data['_meta'] ) ) { 574 $data['_meta'] = array(); 575 } 576 577 if ( preg_match_all( '/<meta [^>]+>/', $source_content, $matches ) ) { 578 $items = $this->_limit_array( $matches[0] ); 579 580 foreach ( $items as $value ) { 581 if ( preg_match( '/(property|name)="([^"]+)"[^>]+content="([^"]+)"/', $value, $new_matches ) ) { 582 $meta_name = $this->_limit_string( $new_matches[2] ); 583 $meta_value = $this->_limit_string( $new_matches[3] ); 584 585 // Sanity check. $key is usually things like 'title', 'description', 'keywords', etc. 586 if ( strlen( $meta_name ) > 100 ) { 587 continue; 588 } 589 590 $data = $this->_process_meta_entry( $meta_name, $meta_value, $data ); 591 } 592 } 593 } 594 595 // Fetch and gather <img> data. 596 if ( empty( $data['_images'] ) ) { 597 $data['_images'] = array(); 598 } 599 600 if ( preg_match_all( '/<img [^>]+>/', $source_content, $matches ) ) { 601 $items = $this->_limit_array( $matches[0] ); 602 603 foreach ( $items as $value ) { 604 if ( ( preg_match( '/width=(\'|")(\d+)\\1/i', $value, $new_matches ) && $new_matches[2] < 256 ) || 605 ( preg_match( '/height=(\'|")(\d+)\\1/i', $value, $new_matches ) && $new_matches[2] < 128 ) ) { 606 607 continue; 608 } 609 610 if ( preg_match( '/src=(\'|")([^\'"]+)\\1/i', $value, $new_matches ) ) { 611 $src = $this->_limit_img( $new_matches[2] ); 612 if ( ! empty( $src ) && ! in_array( $src, $data['_images'] ) ) { 613 $data['_images'][] = $src; 614 } 615 } 616 } 617 } 618 619 // Fetch and gather <iframe> data. 620 if ( empty( $data['_embeds'] ) ) { 621 $data['_embeds'] = array(); 622 } 623 624 if ( preg_match_all( '/<iframe [^>]+>/', $source_content, $matches ) ) { 625 $items = $this->_limit_array( $matches[0] ); 626 627 foreach ( $items as $value ) { 628 if ( preg_match( '/src=(\'|")([^\'"]+)\\1/', $value, $new_matches ) ) { 629 $src = $this->_limit_embed( $new_matches[2] ); 630 631 if ( ! empty( $src ) && ! in_array( $src, $data['_embeds'] ) ) { 632 $data['_embeds'][] = $src; 633 } 634 } 635 } 636 } 637 638 // Fetch and gather <link> data. 639 if ( empty( $data['_links'] ) ) { 640 $data['_links'] = array(); 641 } 642 643 if ( preg_match_all( '/<link [^>]+>/', $source_content, $matches ) ) { 644 $items = $this->_limit_array( $matches[0] ); 645 646 foreach ( $items as $value ) { 647 if ( preg_match( '/rel=["\'](canonical|shortlink|icon)["\']/i', $value, $matches_rel ) && preg_match( '/href=[\'"]([^\'" ]+)[\'"]/i', $value, $matches_url ) ) { 648 $rel = $matches_rel[1]; 649 $url = $this->_limit_url( $matches_url[1] ); 650 651 if ( ! empty( $url ) && empty( $data['_links'][ $rel ] ) ) { 652 $data['_links'][ $rel ] = $url; 653 } 654 } 655 } 656 } 657 658 return $data; 659 } 660 661 /** 662 * Handles backward-compat with the legacy version of Press This by supporting its query string params. 663 * 664 * @since 4.2.0 665 * 666 * @return array 667 */ 668 public function merge_or_fetch_data() { 669 // Get data from $_POST and $_GET, as appropriate ($_POST > $_GET), to remain backward compatible. 670 $data = array(); 671 672 // Only instantiate the keys we want. Sanity check and sanitize each one. 673 foreach ( array( 'u', 's', 't', 'v' ) as $key ) { 674 if ( ! empty( $_POST[ $key ] ) ) { 675 $value = wp_unslash( $_POST[ $key ] ); 676 } else if ( ! empty( $_GET[ $key ] ) ) { 677 $value = wp_unslash( $_GET[ $key ] ); 678 } else { 679 continue; 680 } 681 682 if ( 'u' === $key ) { 683 $value = $this->_limit_url( $value ); 684 685 if ( preg_match( '%^(?:https?:)?//[^/]+%i', $value, $domain_match ) ) { 686 $this->domain = $domain_match[0]; 687 } 688 } else { 689 $value = $this->_limit_string( $value ); 690 } 691 692 if ( ! empty( $value ) ) { 693 $data[ $key ] = $value; 694 } 695 } 696 697 /** 698 * Filters whether to enable in-source media discovery in Press This. 699 * 700 * @since 4.2.0 701 * 702 * @param bool $enable Whether to enable media discovery. 703 */ 704 if ( apply_filters( 'enable_press_this_media_discovery', true ) ) { 705 /* 706 * If no title, _images, _embed, and _meta was passed via $_POST, fetch data from source as fallback, 707 * making PT fully backward compatible with the older bookmarklet. 708 */ 709 if ( empty( $_POST ) && ! empty( $data['u'] ) ) { 710 if ( isset( $_GET['_wpnonce'] ) && wp_verify_nonce( $_GET['_wpnonce'], 'scan-site' ) ) { 711 $data = $this->source_data_fetch_fallback( $data['u'], $data ); 712 } else { 713 $data['errors'] = 'missing nonce'; 714 } 715 } else { 716 foreach ( array( '_images', '_embeds' ) as $type ) { 717 if ( empty( $_POST[ $type ] ) ) { 718 continue; 719 } 720 721 $data[ $type ] = array(); 722 $items = $this->_limit_array( $_POST[ $type ] ); 723 724 foreach ( $items as $key => $value ) { 725 if ( $type === '_images' ) { 726 $value = $this->_limit_img( wp_unslash( $value ) ); 727 } else { 728 $value = $this->_limit_embed( wp_unslash( $value ) ); 729 } 730 731 if ( ! empty( $value ) ) { 732 $data[ $type ][] = $value; 733 } 734 } 735 } 736 737 foreach ( array( '_meta', '_links' ) as $type ) { 738 if ( empty( $_POST[ $type ] ) ) { 739 continue; 740 } 741 742 $data[ $type ] = array(); 743 $items = $this->_limit_array( $_POST[ $type ] ); 744 745 foreach ( $items as $key => $value ) { 746 // Sanity check. These are associative arrays, $key is usually things like 'title', 'description', 'keywords', etc. 747 if ( empty( $key ) || strlen( $key ) > 100 ) { 748 continue; 749 } 750 751 if ( $type === '_meta' ) { 752 $value = $this->_limit_string( wp_unslash( $value ) ); 753 754 if ( ! empty( $value ) ) { 755 $data = $this->_process_meta_entry( $key, $value, $data ); 756 } 757 } else { 758 if ( in_array( $key, array( 'canonical', 'shortlink', 'icon' ), true ) ) { 759 $data[ $type ][ $key ] = $this->_limit_url( wp_unslash( $value ) ); 760 } 761 } 762 } 763 } 764 } 765 766 // Support passing a single image src as `i` 767 if ( ! empty( $_REQUEST['i'] ) && ( $img_src = $this->_limit_img( wp_unslash( $_REQUEST['i'] ) ) ) ) { 768 if ( empty( $data['_images'] ) ) { 769 $data['_images'] = array( $img_src ); 770 } elseif ( ! in_array( $img_src, $data['_images'], true ) ) { 771 array_unshift( $data['_images'], $img_src ); 772 } 773 } 774 } 775 776 /** 777 * Filters the Press This data array. 778 * 779 * @since 4.2.0 780 * 781 * @param array $data Press This Data array. 782 */ 783 return apply_filters( 'press_this_data', $data ); 784 } 785 786 /** 787 * Adds another stylesheet inside TinyMCE. 788 * 789 * @since 4.2.0 790 * 791 * @param string $styles URL to editor stylesheet. 792 * @return string Possibly modified stylesheets list. 793 */ 794 public function add_editor_style( $styles ) { 795 if ( ! empty( $styles ) ) { 796 $styles .= ','; 797 } 798 799 $press_this = admin_url( 'css/press-this-editor.css' ); 800 if ( is_rtl() ) { 801 $press_this = str_replace( '.css', '-rtl.css', $press_this ); 802 } 803 804 return $styles . $press_this; 805 } 806 807 /** 808 * Outputs the post format selection HTML. 809 * 810 * @since 4.2.0 811 * 812 * @param WP_Post $post Post object. 813 */ 814 public function post_formats_html( $post ) { 815 if ( current_theme_supports( 'post-formats' ) && post_type_supports( $post->post_type, 'post-formats' ) ) { 816 $post_formats = get_theme_support( 'post-formats' ); 817 818 if ( is_array( $post_formats[0] ) ) { 819 $post_format = get_post_format( $post->ID ); 820 821 if ( ! $post_format ) { 822 $post_format = '0'; 823 } 824 825 // Add in the current one if it isn't there yet, in case the current theme doesn't support it. 826 if ( $post_format && ! in_array( $post_format, $post_formats[0] ) ) { 827 $post_formats[0][] = $post_format; 828 } 829 830 ?> 831 <div id="post-formats-select"> 832 <fieldset><legend class="screen-reader-text"><?php _e( 'Post Formats' ); ?></legend> 833 <input type="radio" name="post_format" class="post-format" id="post-format-0" value="0" <?php checked( $post_format, '0' ); ?> /> 834 <label for="post-format-0" class="post-format-icon post-format-standard"><?php echo get_post_format_string( 'standard' ); ?></label> 835 <?php 836 837 foreach ( $post_formats[0] as $format ) { 838 $attr_format = esc_attr( $format ); 839 ?> 840 <br /> 841 <input type="radio" name="post_format" class="post-format" id="post-format-<?php echo $attr_format; ?>" value="<?php echo $attr_format; ?>" <?php checked( $post_format, $format ); ?> /> 842 <label for="post-format-<?php echo $attr_format ?>" class="post-format-icon post-format-<?php echo $attr_format; ?>"><?php echo esc_html( get_post_format_string( $format ) ); ?></label> 843 <?php 844 } 845 846 ?> 847 </fieldset> 848 </div> 849 <?php 850 } 851 } 852 } 853 854 /** 855 * Outputs the categories HTML. 856 * 857 * @since 4.2.0 858 * 859 * @param WP_Post $post Post object. 860 */ 861 public function categories_html( $post ) { 862 $taxonomy = get_taxonomy( 'category' ); 863 864 // Bail if user cannot assign terms 865 if ( ! current_user_can( $taxonomy->cap->assign_terms ) ) { 866 return; 867 } 868 869 // Only show "add" if user can edit terms 870 if ( current_user_can( $taxonomy->cap->edit_terms ) ) { 871 ?> 872 <button type="button" class="add-cat-toggle button-link" aria-expanded="false"> 873 <span class="dashicons dashicons-plus"></span><span class="screen-reader-text"><?php _e( 'Toggle add category' ); ?></span> 874 </button> 875 <div class="add-category is-hidden"> 876 <label class="screen-reader-text" for="new-category"><?php echo $taxonomy->labels->add_new_item; ?></label> 877 <input type="text" id="new-category" class="add-category-name" placeholder="<?php echo esc_attr( $taxonomy->labels->new_item_name ); ?>" value="" aria-required="true"> 878 <label class="screen-reader-text" for="new-category-parent"><?php echo $taxonomy->labels->parent_item_colon; ?></label> 879 <div class="postform-wrapper"> 880 <?php 881 wp_dropdown_categories( array( 882 'taxonomy' => 'category', 883 'hide_empty' => 0, 884 'name' => 'new-category-parent', 885 'orderby' => 'name', 886 'hierarchical' => 1, 887 'show_option_none' => '— ' . $taxonomy->labels->parent_item . ' —' 888 ) ); 889 ?> 890 </div> 891 <button type="button" class="add-cat-submit"><?php _e( 'Add' ); ?></button> 892 </div> 893 <?php 894 895 } 896 ?> 897 <div class="categories-search-wrapper"> 898 <input id="categories-search" type="search" class="categories-search" placeholder="<?php esc_attr_e( 'Search categories by name' ) ?>"> 899 <label for="categories-search"> 900 <span class="dashicons dashicons-search"></span><span class="screen-reader-text"><?php _e( 'Search categories' ); ?></span> 901 </label> 902 </div> 903 <div aria-label="<?php esc_attr_e( 'Categories' ); ?>"> 904 <ul class="categories-select"> 905 <?php wp_terms_checklist( $post->ID, array( 'taxonomy' => 'category', 'list_only' => true ) ); ?> 906 </ul> 907 </div> 908 <?php 909 } 910 911 /** 912 * Outputs the tags HTML. 913 * 914 * @since 4.2.0 915 * 916 * @param WP_Post $post Post object. 917 */ 918 public function tags_html( $post ) { 919 $taxonomy = get_taxonomy( 'post_tag' ); 920 $user_can_assign_terms = current_user_can( $taxonomy->cap->assign_terms ); 921 $esc_tags = get_terms_to_edit( $post->ID, 'post_tag' ); 922 923 if ( ! $esc_tags || is_wp_error( $esc_tags ) ) { 924 $esc_tags = ''; 925 } 926 927 ?> 928 <div class="tagsdiv" id="post_tag"> 929 <div class="jaxtag"> 930 <input type="hidden" name="tax_input[post_tag]" class="the-tags" value="<?php echo $esc_tags; // escaped in get_terms_to_edit() ?>"> 931 <?php 932 933 if ( $user_can_assign_terms ) { 934 ?> 935 <div class="ajaxtag hide-if-no-js"> 936 <label class="screen-reader-text" for="new-tag-post_tag"><?php _e( 'Tags' ); ?></label> 937 <p> 938 <input type="text" id="new-tag-post_tag" name="newtag[post_tag]" class="newtag form-input-tip" size="16" autocomplete="off" value="" aria-describedby="new-tag-desc" /> 939 <button type="button" class="tagadd"><?php _e( 'Add' ); ?></button> 940 </p> 941 </div> 942 <p class="howto" id="new-tag-desc"> 943 <?php echo $taxonomy->labels->separate_items_with_commas; ?> 944 </p> 945 <?php 946 } 947 948 ?> 949 </div> 950 <ul class="tagchecklist" role="list"></ul> 951 </div> 952 <?php 953 954 if ( $user_can_assign_terms ) { 955 ?> 956 <button type="button" class="button-link tagcloud-link" id="link-post_tag" aria-expanded="false"><?php echo $taxonomy->labels->choose_from_most_used; ?></button> 957 <?php 958 } 959 } 960 961 /** 962 * Get a list of embeds with no duplicates. 963 * 964 * @since 4.2.0 965 * 966 * @param array $data The site's data. 967 * @return array Embeds selected to be available. 968 */ 969 public function get_embeds( $data ) { 970 $selected_embeds = array(); 971 972 // Make sure to add the Pressed page if it's a valid oembed itself 973 if ( ! empty ( $data['u'] ) && $this->_limit_embed( $data['u'] ) ) { 974 $data['_embeds'][] = $data['u']; 975 } 976 977 if ( ! empty( $data['_embeds'] ) ) { 978 foreach ( $data['_embeds'] as $src ) { 979 $prot_relative_src = preg_replace( '/^https?:/', '', $src ); 980 981 if ( in_array( $prot_relative_src, $this->embeds ) ) { 982 continue; 983 } 984 985 $selected_embeds[] = $src; 986 $this->embeds[] = $prot_relative_src; 987 } 988 } 989 990 return $selected_embeds; 991 } 992 993 /** 994 * Get a list of images with no duplicates. 995 * 996 * @since 4.2.0 997 * 998 * @param array $data The site's data. 999 * @return array 1000 */ 1001 public function get_images( $data ) { 1002 $selected_images = array(); 1003 1004 if ( ! empty( $data['_images'] ) ) { 1005 foreach ( $data['_images'] as $src ) { 1006 if ( false !== strpos( $src, 'gravatar.com' ) ) { 1007 $src = preg_replace( '%http://[\d]+\.gravatar\.com/%', 'https://secure.gravatar.com/', $src ); 1008 } 1009 1010 $prot_relative_src = preg_replace( '/^https?:/', '', $src ); 1011 1012 if ( in_array( $prot_relative_src, $this->images ) || 1013 ( false !== strpos( $src, 'avatar' ) && count( $this->images ) > 15 ) ) { 1014 // Skip: already selected or some type of avatar and we've already gathered more than 15 images. 1015 continue; 1016 } 1017 1018 $selected_images[] = $src; 1019 $this->images[] = $prot_relative_src; 1020 } 1021 } 1022 1023 return $selected_images; 1024 } 1025 1026 /** 1027 * Gets the source page's canonical link, based on passed location and meta data. 1028 * 1029 * @since 4.2.0 1030 * 1031 * @param array $data The site's data. 1032 * @return string Discovered canonical URL, or empty 1033 */ 1034 public function get_canonical_link( $data ) { 1035 $link = ''; 1036 1037 if ( ! empty( $data['_links']['canonical'] ) ) { 1038 $link = $data['_links']['canonical']; 1039 } elseif ( ! empty( $data['u'] ) ) { 1040 $link = $data['u']; 1041 } elseif ( ! empty( $data['_meta'] ) ) { 1042 if ( ! empty( $data['_meta']['twitter:url'] ) ) { 1043 $link = $data['_meta']['twitter:url']; 1044 } else if ( ! empty( $data['_meta']['og:url'] ) ) { 1045 $link = $data['_meta']['og:url']; 1046 } 1047 } 1048 1049 if ( empty( $link ) && ! empty( $data['_links']['shortlink'] ) ) { 1050 $link = $data['_links']['shortlink']; 1051 } 1052 1053 return $link; 1054 } 1055 1056 /** 1057 * Gets the source page's site name, based on passed meta data. 1058 * 1059 * @since 4.2.0 1060 * 1061 * @param array $data The site's data. 1062 * @return string Discovered site name, or empty 1063 */ 1064 public function get_source_site_name( $data ) { 1065 $name = ''; 1066 1067 if ( ! empty( $data['_meta'] ) ) { 1068 if ( ! empty( $data['_meta']['og:site_name'] ) ) { 1069 $name = $data['_meta']['og:site_name']; 1070 } else if ( ! empty( $data['_meta']['application-name'] ) ) { 1071 $name = $data['_meta']['application-name']; 1072 } 1073 } 1074 1075 return $name; 1076 } 1077 1078 /** 1079 * Gets the source page's title, based on passed title and meta data. 1080 * 1081 * @since 4.2.0 1082 * 1083 * @param array $data The site's data. 1084 * @return string Discovered page title, or empty 1085 */ 1086 public function get_suggested_title( $data ) { 1087 $title = ''; 1088 1089 if ( ! empty( $data['t'] ) ) { 1090 $title = $data['t']; 1091 } elseif ( ! empty( $data['_meta'] ) ) { 1092 if ( ! empty( $data['_meta']['twitter:title'] ) ) { 1093 $title = $data['_meta']['twitter:title']; 1094 } else if ( ! empty( $data['_meta']['og:title'] ) ) { 1095 $title = $data['_meta']['og:title']; 1096 } else if ( ! empty( $data['_meta']['title'] ) ) { 1097 $title = $data['_meta']['title']; 1098 } 1099 } 1100 1101 return $title; 1102 } 1103 1104 /** 1105 * Gets the source page's suggested content, based on passed data (description, selection, etc). 1106 * 1107 * Features a blockquoted excerpt, as well as content attribution, if any. 1108 * 1109 * @since 4.2.0 1110 * 1111 * @param array $data The site's data. 1112 * @return string Discovered content, or empty 1113 */ 1114 public function get_suggested_content( $data ) { 1115 $content = $text = ''; 1116 1117 if ( ! empty( $data['s'] ) ) { 1118 $text = $data['s']; 1119 } else if ( ! empty( $data['_meta'] ) ) { 1120 if ( ! empty( $data['_meta']['twitter:description'] ) ) { 1121 $text = $data['_meta']['twitter:description']; 1122 } else if ( ! empty( $data['_meta']['og:description'] ) ) { 1123 $text = $data['_meta']['og:description']; 1124 } else if ( ! empty( $data['_meta']['description'] ) ) { 1125 $text = $data['_meta']['description']; 1126 } 1127 1128 // If there is an ellipsis at the end, the description is very likely auto-generated. Better to ignore it. 1129 if ( $text && substr( $text, -3 ) === '...' ) { 1130 $text = ''; 1131 } 1132 } 1133 1134 $default_html = array( 'quote' => '', 'link' => '', 'embed' => '' ); 1135 1136 if ( ! empty( $data['u'] ) && $this->_limit_embed( $data['u'] ) ) { 1137 $default_html['embed'] = '<p>[embed]' . $data['u'] . '[/embed]</p>'; 1138 1139 if ( ! empty( $data['s'] ) ) { 1140 // If the user has selected some text, do quote it. 1141 $default_html['quote'] = '<blockquote>%1$s</blockquote>'; 1142 } 1143 } else { 1144 $default_html['quote'] = '<blockquote>%1$s</blockquote>'; 1145 $default_html['link'] = '<p>' . _x( 'Source:', 'Used in Press This to indicate where the content comes from.' ) . 1146 ' <em><a href="%1$s">%2$s</a></em></p>'; 1147 } 1148 1149 /** 1150 * Filters the default HTML tags used in the suggested content for the editor. 1151 * 1152 * The HTML strings use printf format. After filtering the content is added at the specified places with `sprintf()`. 1153 * 1154 * @since 4.2.0 1155 * 1156 * @param array $default_html Associative array with three possible keys: 1157 * - 'quote' where %1$s is replaced with the site description or the selected content. 1158 * - 'link' where %1$s is link href, %2$s is link text, usually the source page title. 1159 * - 'embed' which contains an [embed] shortcode when the source page offers embeddable content. 1160 * @param array $data Associative array containing the data from the source page. 1161 */ 1162 $default_html = apply_filters( 'press_this_suggested_html', $default_html, $data ); 1163 1164 if ( ! empty( $default_html['embed'] ) ) { 1165 $content .= $default_html['embed']; 1166 } 1167 1168 // Wrap suggested content in the specified HTML. 1169 if ( ! empty( $default_html['quote'] ) && $text ) { 1170 $content .= sprintf( $default_html['quote'], $text ); 1171 } 1172 1173 // Add source attribution if there is one available. 1174 if ( ! empty( $default_html['link'] ) ) { 1175 $title = $this->get_suggested_title( $data ); 1176 $url = $this->get_canonical_link( $data ); 1177 1178 if ( ! $title ) { 1179 $title = $this->get_source_site_name( $data ); 1180 } 1181 1182 if ( $url && $title ) { 1183 $content .= sprintf( $default_html['link'], $url, $title ); 1184 } 1185 } 1186 1187 return $content; 1188 } 1189 1190 /** 1191 * Serves the app's base HTML, which in turns calls the load script. 1192 * 1193 * @since 4.2.0 1194 * 1195 * @global WP_Locale $wp_locale 1196 * @global bool $is_IE 1197 */ 1198 public function html() { 1199 global $wp_locale; 1200 1201 $wp_version = get_bloginfo( 'version' ); 1202 1203 // Get data, new (POST) and old (GET). 1204 $data = $this->merge_or_fetch_data(); 1205 1206 $post_title = $this->get_suggested_title( $data ); 1207 1208 $post_content = $this->get_suggested_content( $data ); 1209 1210 // Get site settings array/data. 1211 $site_settings = $this->site_settings(); 1212 1213 // Pass the images and embeds 1214 $images = $this->get_images( $data ); 1215 $embeds = $this->get_embeds( $data ); 1216 1217 $site_data = array( 1218 'v' => ! empty( $data['v'] ) ? $data['v'] : '', 1219 'u' => ! empty( $data['u'] ) ? $data['u'] : '', 1220 'hasData' => ! empty( $data ) && ! isset( $data['errors'] ), 1221 ); 1222 1223 if ( ! empty( $images ) ) { 1224 $site_data['_images'] = $images; 1225 } 1226 1227 if ( ! empty( $embeds ) ) { 1228 $site_data['_embeds'] = $embeds; 1229 } 1230 1231 // Add press-this-editor.css and remove theme's editor-style.css, if any. 1232 remove_editor_styles(); 1233 1234 add_filter( 'mce_css', array( $this, 'add_editor_style' ) ); 1235 1236 if ( ! empty( $GLOBALS['is_IE'] ) ) { 1237 @header( 'X-UA-Compatible: IE=edge' ); 1238 } 1239 1240 @header( 'Content-Type: ' . get_option( 'html_type' ) . '; charset=' . get_option( 'blog_charset' ) ); 1241 1242 ?> 1243 <!DOCTYPE html> 1244 <!--[if IE 7]> <html class="lt-ie9 lt-ie8" <?php language_attributes(); ?>> <![endif]--> 1245 <!--[if IE 8]> <html class="lt-ie9" <?php language_attributes(); ?>> <![endif]--> 1246 <!--[if gt IE 8]><!--> <html <?php language_attributes(); ?>> <!--<![endif]--> 1247 <head> 1248 <meta http-equiv="Content-Type" content="<?php echo esc_attr( get_bloginfo( 'html_type' ) ); ?>; charset=<?php echo esc_attr( get_option( 'blog_charset' ) ); ?>" /> 1249 <meta name="viewport" content="width=device-width"> 1250 <title><?php esc_html_e( 'Press This!' ) ?></title> 1251 1252 <script> 1253 window.wpPressThisData = <?php echo wp_json_encode( $site_data ); ?>; 1254 window.wpPressThisConfig = <?php echo wp_json_encode( $site_settings ); ?>; 1255 </script> 1256 1257 <script type="text/javascript"> 1258 var ajaxurl = '<?php echo esc_js( admin_url( 'admin-ajax.php', 'relative' ) ); ?>', 1259 pagenow = 'press-this', 1260 typenow = 'post', 1261 adminpage = 'press-this-php', 1262 thousandsSeparator = '<?php echo addslashes( $wp_locale->number_format['thousands_sep'] ); ?>', 1263 decimalPoint = '<?php echo addslashes( $wp_locale->number_format['decimal_point'] ); ?>', 1264 isRtl = <?php echo (int) is_rtl(); ?>; 1265 </script> 1266 1267 <?php 1268 /* 1269 * $post->ID is needed for the embed shortcode so we can show oEmbed previews in the editor. 1270 * Maybe find a way without it. 1271 */ 1272 $post = get_default_post_to_edit( 'post', true ); 1273 $post_ID = (int) $post->ID; 1274 1275 wp_enqueue_media( array( 'post' => $post_ID ) ); 1276 wp_enqueue_style( 'press-this' ); 1277 wp_enqueue_script( 'press-this' ); 1278 wp_enqueue_script( 'json2' ); 1279 wp_enqueue_script( 'editor' ); 1280 1281 $categories_tax = get_taxonomy( 'category' ); 1282 $show_categories = current_user_can( $categories_tax->cap->assign_terms ) || current_user_can( $categories_tax->cap->edit_terms ); 1283 1284 $tag_tax = get_taxonomy( 'post_tag' ); 1285 $show_tags = current_user_can( $tag_tax->cap->assign_terms ); 1286 1287 $supports_formats = false; 1288 $post_format = 0; 1289 1290 if ( current_theme_supports( 'post-formats' ) && post_type_supports( $post->post_type, 'post-formats' ) ) { 1291 $supports_formats = true; 1292 1293 if ( ! ( $post_format = get_post_format( $post_ID ) ) ) { 1294 $post_format = 0; 1295 } 1296 } 1297 1298 /** This action is documented in wp-admin/admin-header.php */ 1299 do_action( 'admin_enqueue_scripts', 'press-this.php' ); 1300 1301 /** This action is documented in wp-admin/admin-header.php */ 1302 do_action( 'admin_print_styles-press-this.php' ); 1303 1304 /** This action is documented in wp-admin/admin-header.php */ 1305 do_action( 'admin_print_styles' ); 1306 1307 /** This action is documented in wp-admin/admin-header.php */ 1308 do_action( 'admin_print_scripts-press-this.php' ); 1309 1310 /** This action is documented in wp-admin/admin-header.php */ 1311 do_action( 'admin_print_scripts' ); 1312 1313 /** This action is documented in wp-admin/admin-header.php */ 1314 do_action( 'admin_head-press-this.php' ); 1315 1316 /** This action is documented in wp-admin/admin-header.php */ 1317 do_action( 'admin_head' ); 1318 ?> 1319 </head> 1320 <?php 1321 1322 $admin_body_class = 'press-this'; 1323 $admin_body_class .= ( is_rtl() ) ? ' rtl' : ''; 1324 $admin_body_class .= ' branch-' . str_replace( array( '.', ',' ), '-', floatval( $wp_version ) ); 1325 $admin_body_class .= ' version-' . str_replace( '.', '-', preg_replace( '/^([.0-9]+).*/', '$1', $wp_version ) ); 1326 $admin_body_class .= ' admin-color-' . sanitize_html_class( get_user_option( 'admin_color' ), 'fresh' ); 1327 $admin_body_class .= ' locale-' . sanitize_html_class( strtolower( str_replace( '_', '-', get_user_locale() ) ) ); 1328 1329 /** This filter is documented in wp-admin/admin-header.php */ 1330 $admin_body_classes = apply_filters( 'admin_body_class', '' ); 1331 1332 ?> 1333 <body class="wp-admin wp-core-ui <?php echo $admin_body_classes . ' ' . $admin_body_class; ?>"> 1334 <div id="adminbar" class="adminbar"> 1335 <h1 id="current-site" class="current-site"> 1336 <a class="current-site-link" href="<?php echo esc_url( home_url( '/' ) ); ?>" target="_blank" rel="home"> 1337 <span class="dashicons dashicons-wordpress"></span> 1338 <span class="current-site-name"><?php bloginfo( 'name' ); ?></span> 1339 </a> 1340 </h1> 1341 <button type="button" class="options button-link closed"> 1342 <span class="dashicons dashicons-tag on-closed"></span> 1343 <span class="screen-reader-text on-closed"><?php _e( 'Show post options' ); ?></span> 1344 <span aria-hidden="true" class="on-open"><?php _e( 'Done' ); ?></span> 1345 <span class="screen-reader-text on-open"><?php _e( 'Hide post options' ); ?></span> 1346 </button> 1347 </div> 1348 1349 <div id="scanbar" class="scan"> 1350 <form method="GET"> 1351 <label for="url-scan" class="screen-reader-text"><?php _e( 'Scan site for content' ); ?></label> 1352 <input type="url" name="u" id="url-scan" class="scan-url" value="<?php echo esc_attr( $site_data['u'] ) ?>" placeholder="<?php esc_attr_e( 'Enter a URL to scan' ) ?>" /> 1353 <input type="submit" name="url-scan-submit" id="url-scan-submit" class="scan-submit" value="<?php esc_attr_e( 'Scan' ) ?>" /> 1354 <?php wp_nonce_field( 'scan-site' ); ?> 1355 </form> 1356 </div> 1357 1358 <form id="pressthis-form" method="post" action="post.php" autocomplete="off"> 1359 <input type="hidden" name="post_ID" id="post_ID" value="<?php echo $post_ID; ?>" /> 1360 <input type="hidden" name="action" value="press-this-save-post" /> 1361 <input type="hidden" name="post_status" id="post_status" value="draft" /> 1362 <input type="hidden" name="wp-preview" id="wp-preview" value="" /> 1363 <input type="hidden" name="post_title" id="post_title" value="" /> 1364 <input type="hidden" name="pt-force-redirect" id="pt-force-redirect" value="" /> 1365 <?php 1366 1367 wp_nonce_field( 'update-post_' . $post_ID, '_wpnonce', false ); 1368 wp_nonce_field( 'add-category', '_ajax_nonce-add-category', false ); 1369 1370 ?> 1371 1372 <div class="wrapper"> 1373 <div class="editor-wrapper"> 1374 <div class="alerts" role="alert" aria-live="assertive" aria-relevant="all" aria-atomic="true"> 1375 <?php 1376 1377 if ( isset( $data['v'] ) && $this->version > $data['v'] ) { 1378 ?> 1379 <p class="alert is-notice"> 1380 <?php printf( __( 'You should upgrade <a href="%s" target="_blank">your bookmarklet</a> to the latest version!' ), admin_url( 'tools.php' ) ); ?> 1381 </p> 1382 <?php 1383 } 1384 1385 ?> 1386 </div> 1387 1388 <div id="app-container" class="editor"> 1389 <span id="title-container-label" class="post-title-placeholder" aria-hidden="true"><?php _e( 'Post title' ); ?></span> 1390 <h2 id="title-container" class="post-title" contenteditable="true" spellcheck="true" aria-label="<?php esc_attr_e( 'Post title' ); ?>" tabindex="0"><?php echo esc_html( $post_title ); ?></h2> 1391 1392 <div class="media-list-container"> 1393 <div class="media-list-inner-container"> 1394 <h2 class="screen-reader-text"><?php _e( 'Suggested media' ); ?></h2> 1395 <ul class="media-list"></ul> 1396 </div> 1397 </div> 1398 1399 <?php 1400 wp_editor( $post_content, 'pressthis', array( 1401 'drag_drop_upload' => true, 1402 'editor_height' => 600, 1403 'media_buttons' => false, 1404 'textarea_name' => 'post_content', 1405 'teeny' => true, 1406 'tinymce' => array( 1407 'resize' => false, 1408 'wordpress_adv_hidden' => false, 1409 'add_unload_trigger' => false, 1410 'statusbar' => false, 1411 'autoresize_min_height' => 600, 1412 'wp_autoresize_on' => true, 1413 'plugins' => 'lists,media,paste,tabfocus,fullscreen,wordpress,wpautoresize,wpeditimage,wpgallery,wplink,wptextpattern,wpview', 1414 'toolbar1' => 'bold,italic,bullist,numlist,blockquote,link,unlink', 1415 'toolbar2' => 'undo,redo', 1416 ), 1417 'quicktags' => array( 1418 'buttons' => 'strong,em,link,block,del,ins,img,ul,ol,li,code,more', 1419 ), 1420 ) ); 1421 1422 ?> 1423 </div> 1424 </div> 1425 1426 <div class="options-panel-back is-hidden" tabindex="-1"></div> 1427 <div class="options-panel is-off-screen is-hidden" tabindex="-1"> 1428 <div class="post-options"> 1429 1430 <?php if ( $supports_formats ) : ?> 1431 <button type="button" class="post-option"> 1432 <span class="dashicons dashicons-admin-post"></span> 1433 <span class="post-option-title"><?php _ex( 'Format', 'post format' ); ?></span> 1434 <span class="post-option-contents" id="post-option-post-format"><?php echo esc_html( get_post_format_string( $post_format ) ); ?></span> 1435 <span class="dashicons post-option-forward"></span> 1436 </button> 1437 <?php endif; ?> 1438 1439 <?php if ( $show_categories ) : ?> 1440 <button type="button" class="post-option"> 1441 <span class="dashicons dashicons-category"></span> 1442 <span class="post-option-title"><?php _e( 'Categories' ); ?></span> 1443 <span class="dashicons post-option-forward"></span> 1444 </button> 1445 <?php endif; ?> 1446 1447 <?php if ( $show_tags ) : ?> 1448 <button type="button" class="post-option"> 1449 <span class="dashicons dashicons-tag"></span> 1450 <span class="post-option-title"><?php _e( 'Tags' ); ?></span> 1451 <span class="dashicons post-option-forward"></span> 1452 </button> 1453 <?php endif; ?> 1454 </div> 1455 1456 <?php if ( $supports_formats ) : ?> 1457 <div class="setting-modal is-off-screen is-hidden"> 1458 <button type="button" class="modal-close"> 1459 <span class="dashicons post-option-back"></span> 1460 <span class="setting-title" aria-hidden="true"><?php _ex( 'Format', 'post format' ); ?></span> 1461 <span class="screen-reader-text"><?php _e( 'Back to post options' ) ?></span> 1462 </button> 1463 <?php $this->post_formats_html( $post ); ?> 1464 </div> 1465 <?php endif; ?> 1466 1467 <?php if ( $show_categories ) : ?> 1468 <div class="setting-modal is-off-screen is-hidden"> 1469 <button type="button" class="modal-close"> 1470 <span class="dashicons post-option-back"></span> 1471 <span class="setting-title" aria-hidden="true"><?php _e( 'Categories' ); ?></span> 1472 <span class="screen-reader-text"><?php _e( 'Back to post options' ) ?></span> 1473 </button> 1474 <?php $this->categories_html( $post ); ?> 1475 </div> 1476 <?php endif; ?> 1477 1478 <?php if ( $show_tags ) : ?> 1479 <div class="setting-modal tags is-off-screen is-hidden"> 1480 <button type="button" class="modal-close"> 1481 <span class="dashicons post-option-back"></span> 1482 <span class="setting-title" aria-hidden="true"><?php _e( 'Tags' ); ?></span> 1483 <span class="screen-reader-text"><?php _e( 'Back to post options' ) ?></span> 1484 </button> 1485 <?php $this->tags_html( $post ); ?> 1486 </div> 1487 <?php endif; ?> 1488 </div><!-- .options-panel --> 1489 </div><!-- .wrapper --> 1490 1491 <div class="press-this-actions"> 1492 <div class="pressthis-media-buttons"> 1493 <button type="button" class="insert-media" data-editor="pressthis"> 1494 <span class="dashicons dashicons-admin-media"></span> 1495 <span class="screen-reader-text"><?php _e( 'Add Media' ); ?></span> 1496 </button> 1497 </div> 1498 <div class="post-actions"> 1499 <span class="spinner"> </span> 1500 <div class="split-button"> 1501 <div class="split-button-head"> 1502 <button type="button" class="publish-button split-button-primary" aria-live="polite"> 1503 <span class="publish"><?php echo ( current_user_can( 'publish_posts' ) ) ? __( 'Publish' ) : __( 'Submit for Review' ); ?></span> 1504 <span class="saving-draft"><?php _e( 'Saving…' ); ?></span> 1505 </button><button type="button" class="split-button-toggle" aria-haspopup="true" aria-expanded="false"> 1506 <i class="dashicons dashicons-arrow-down-alt2"></i> 1507 <span class="screen-reader-text"><?php _e('More actions'); ?></span> 1508 </button> 1509 </div> 1510 <ul class="split-button-body"> 1511 <li><button type="button" class="button-link draft-button split-button-option"><?php _e( 'Save Draft' ); ?></button></li> 1512 <li><button type="button" class="button-link standard-editor-button split-button-option"><?php _e( 'Standard Editor' ); ?></button></li> 1513 <li><button type="button" class="button-link preview-button split-button-option"><?php _e( 'Preview' ); ?></button></li> 1514 </ul> 1515 </div> 1516 </div> 1517 </div> 1518 </form> 1519 1520 <?php 1521 /** This action is documented in wp-admin/admin-footer.php */ 1522 do_action( 'admin_footer', '' ); 1523 1524 /** This action is documented in wp-admin/admin-footer.php */ 1525 do_action( 'admin_print_footer_scripts-press-this.php' ); 1526 1527 /** This action is documented in wp-admin/admin-footer.php */ 1528 do_action( 'admin_print_footer_scripts' ); 1529 1530 /** This action is documented in wp-admin/admin-footer.php */ 1531 do_action( 'admin_footer-press-this.php' ); 1532 ?> 1533 </body> 1534 </html> 1535 <?php 1536 die(); 1537 } 1538 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body
Generated: Sun Sep 24 01:00:03 2017 | Cross-referenced by PHPXref 0.7.1 |