Changeset 3384901
- Timestamp:
- 10/27/2025 12:56:10 AM (7 weeks ago)
- Location:
- restful-syndication
- Files:
-
- 3 edited
- 2 copied
-
tags/1.6.0 (copied) (copied from restful-syndication/trunk)
-
tags/1.6.0/index.php (modified) (26 diffs)
-
tags/1.6.0/readme.txt (copied) (copied from restful-syndication/trunk/readme.txt) (2 diffs)
-
trunk/index.php (modified) (26 diffs)
-
trunk/readme.txt (modified) (2 diffs)
Legend:
- Unmodified
- Added
- Removed
-
restful-syndication/tags/1.6.0/index.php
r3180974 r3384901 4 4 Plugin URI: https://mediarealm.com.au/ 5 5 Description: Import content from the Wordpress REST API on another Wordpress site 6 Version: 1. 5.06 Version: 1.6.0 7 7 Author: Media Realm 8 8 Author URI: https://www.mediarealm.com.au/ … … 106 106 public $errors_logged = array(); 107 107 108 private $push_remote_auth_key = null; 109 private $push_remote_domain = null; 110 108 111 public function __construct() { 109 112 add_action('admin_menu', array($this, 'admin_menu')); … … 122 125 )); 123 126 }); 127 128 if(is_plugin_active('advanced-custom-fields/acf.php') || is_plugin_active('advanced-custom-fields-pro/acf.php')) { 129 if(apply_filters('restful_syndication_allow_image_credit_fields', true) === true) { 130 $this->settings['fields_image_credits'] = array( 131 "title" => "Enable Image Credit Fields", 132 "type" => "checkbox", 133 ); 134 135 $options = get_option($this->settings_prefix . 'settings'); 136 if($options['fields_image_credits'] == "true") { 137 add_action('acf/include_fields', array($this, 'fields_image_credits_acf')); 138 add_filter('restful_syndication_api_headers', array($this, 'fields_image_credits_api_header')); 139 } 140 } 141 142 } 124 143 } 125 144 … … 314 333 } 315 334 335 $headers = array( 336 'X-RESTful-Syndication-Version' => get_file_data(__FILE__, array('Version' => 'Version'), false)['Version'], 337 ); 338 339 $headers = apply_filters('restful_syndication_api_headers', $headers); 340 316 341 if(!empty($options['auth_username']) && !empty($options['auth_password'])) { 317 $headers = array( 318 'Authorization' => 'Basic ' . base64_encode($options['auth_username'] . ':' . $options['auth_password']), 319 ); 320 } else { 321 $headers = array(); 342 $headers['Authorization'] = 'Basic ' . base64_encode($options['auth_username'] . ':' . $options['auth_password']); 343 } 344 345 if($full === true && $this->push_remote_domain !== null && $this->push_remote_auth_key !== null && strpos($url, "restful_push_auth_key=") === false) { 346 // Add the Push Auth Key back to the URL as a parameter for subsequent calls 347 // This is needed for taxonomies, authors, images, etc. 348 349 $query = parse_url($url, PHP_URL_QUERY); 350 if ($query) { 351 $url .= '&restful_push_auth_key=' . urlencode($this->push_remote_auth_key); 352 } else { 353 $url .= '?restful_push_auth_key=' . urlencode($this->push_remote_auth_key); 354 } 322 355 } 323 356 … … 335 368 } 336 369 337 $data = json_decode(wp_remote_retrieve_body($response), true); 370 $response_body = wp_remote_retrieve_body($response); 371 $data = json_decode($response_body, true); 372 373 if($data == false) { 374 $this->log("rest_fetch() - JSON not decoded when fetching ".$url."."); 375 } 338 376 339 377 return $data; … … 405 443 $existing_post_id = 0; 406 444 445 if(!is_array($post)) { 446 $this->log("syndicate_one() - post is not an array. Failing."); 447 return; 448 } 449 407 450 // Have we already ingested this post? 408 451 if($allow_overwrite === true && $this->post_guid_exists($post['guid']['rendered']) !== null) { … … 411 454 } else if($this->post_guid_exists($post['guid']['rendered']) !== null) { 412 455 // Already exists on this site - skip over this post 456 $this->log("syndicate_one() - Post GUID already exists. Skipping."); 413 457 return; 414 458 } … … 418 462 if(isset($options['earliest_post_date']) && strtotime($options['earliest_post_date']) !== false && strtotime($post['date']) <= strtotime($options['earliest_post_date'])) { 419 463 // This post is earlier than the specified start date 464 $this->log("syndicate_one() - Post is earlier than earliest post cutoff date. Skipping."); 420 465 return; 421 466 } … … 455 500 456 501 // Parse the HTML 457 $dom = new domDocument; 458 $dom->loadHTML('<?xml encoding="utf-8" ?>' . $html, LIBXML_HTML_NOIMPLIED|LIBXML_HTML_NODEFDTD); 502 if(class_exists('\Dom\HTMLDocument')) { 503 // PHP 8.4+ 504 $dom = \Dom\HTMLDocument::createFromString($html, LIBXML_HTML_NOIMPLIED|LIBXML_NOERROR); 505 $dom->strictErrorChecking = false; 506 } else { 507 $dom = new domDocument; 508 $html = str_replace("<hr>", "<hr />", $html); // Workaround for certain libxml versions breaking on this unclosed tag 509 $dom->loadHTML('<?xml encoding="utf-8" ?>' . $html, LIBXML_HTML_NOIMPLIED|LIBXML_HTML_NODEFDTD); 510 } 459 511 460 512 // Find and download any embedded images found in the HTML … … 465 517 // Download the image and attach it 466 518 $url = $img->getAttribute('src'); 467 $attachment_id = $this->ingest_image($url); 468 519 $classes = $img->getAttribute('class'); 520 $image_id = $this->get_image_id_from_classes($classes); 521 $attachment_id = $this->ingest_image($url, null, null, $image_id, $post); 522 469 523 // Update the SRC in the HTML 470 524 $url_new = wp_get_attachment_image_src($attachment_id, $options['image_embed_size']); … … 512 566 $audio_shortcode = $dom->createElement('div'); 513 567 $audio_shortcode->setAttribute('class', 'audio-filter'); 514 $audio_shortcode-> nodeValue= '[audio src="'.esc_url($url).'"]';568 $audio_shortcode->textContent = '[audio src="'.esc_url($url).'"]'; 515 569 516 570 // Replace the original <audio> tag with this new <div class="audio-filter">[audio]</div> arrangement … … 539 593 $embed_shortcode = $dom->createElement('div'); 540 594 $embed_shortcode->setAttribute('class', 'video-filter'); 541 $embed_shortcode-> nodeValue= '[embed]'.esc_url($url_new).'[/embed]';595 $embed_shortcode->textContent = '[embed]'.esc_url($url_new).'[/embed]'; 542 596 543 597 // Replace the original <div class="embed_youtube"> tag with this new <div>[embed]url[/embed]</div> arrangement … … 576 630 // Create a new paragraph, and insert the iframe shortcode 577 631 $embed_shortcode = $dom->createElement('p'); 578 $embed_shortcode-> nodeValue= '[restful_syndication_iframe src="'.esc_url($url).'" width="'.esc_attr($width).'" height="'.esc_attr($height).'"]';632 $embed_shortcode->textContent = '[restful_syndication_iframe src="'.esc_url($url).'" width="'.esc_attr($width).'" height="'.esc_attr($height).'"]'; 579 633 580 634 // Replace the original <iframe> tag with this new <p>[restful_syndication_iframe src="url"]</p> arrangement … … 609 663 // Create a new paragraph, and insert the iframe shortcode 610 664 $embed_shortcode = $dom->createElement('p'); 611 $embed_shortcode-> nodeValue= '[restful_syndication_iframe src="'.esc_url($url).'" width="'.esc_attr($width).'" height="'.esc_attr($height).'"]';665 $embed_shortcode->textContent = '[restful_syndication_iframe src="'.esc_url($url).'" width="'.esc_attr($width).'" height="'.esc_attr($height).'"]'; 612 666 613 667 // Replace the original <iframe> tag with this new <p>[restful_syndication_iframe src="url"]</p> arrangement … … 677 731 if(is_array($tag)) { 678 732 $tags[] = $tag['term_id']; 733 } 734 } 735 } 736 } 737 } 738 } 739 } 740 741 // Find local matching author taxonomy 742 $author_terms = array(); 743 744 if(isset($post['_links']['wp:term']) && taxonomy_exists('author')) { 745 foreach($post['_links']['wp:term'] as $term_link) { 746 if($term_link['taxonomy'] == 'author') { 747 $term_data_all = $this->rest_fetch($term_link['href'], true); 748 749 foreach($term_data_all as $term_data) { 750 if(isset($term_data['name']) && !empty($term_data['name'])) { 751 $term = get_term_by('name', $term_data['name'], 'author'); 752 753 if($term !== false) { 754 // Term already exists 755 $author_terms[] = $term->term_id; 756 } else { 757 // Create the author taxonomy term 758 $term = wp_insert_term($term_data['name'], 'author'); 759 760 if(is_array($term)) { 761 $author_terms[] = $term['term_id']; 679 762 } 680 763 } … … 750 833 ), false); 751 834 835 if($post_id > 0 && count($author_terms) > 0) { 836 // Set author taxonomy terms 837 wp_set_object_terms($post_id, $author_terms, 'author'); 838 } 839 752 840 if($post_id > 0 && isset($post['_links']['wp:featuredmedia'][0]['href'])) { 753 841 // Download and attach featured image … … 757 845 758 846 if(isset($attachment['media_details']['sizes']['full']['source_url'])) { 759 $featured_attachment_id = $this->ingest_image($attachment['media_details']['sizes']['full']['source_url'], $post_id );847 $featured_attachment_id = $this->ingest_image($attachment['media_details']['sizes']['full']['source_url'], $post_id, $attachment); 760 848 set_post_thumbnail($post_id, $featured_attachment_id); 761 849 } elseif(isset($attachment['source_url'])) { 762 $featured_attachment_id = $this->ingest_image($attachment['source_url'], $post_id );850 $featured_attachment_id = $this->ingest_image($attachment['source_url'], $post_id, $attachment); 763 851 set_post_thumbnail($post_id, $featured_attachment_id); 764 852 } else { 765 $this->log("Attachment full image not found: " . $api_url); 766 } 853 $this->log("Attachment full image not found: " . $api_url . " - " . print_r($attachment, true)); 854 } 855 } else { 856 $this->log("Featured Media Link not found. " . $post_id); 767 857 } 768 858 … … 809 899 $supplied_url_info = parse_url($_POST['restful_push_url']); 810 900 $supplied_domain = $supplied_url_info['host']; 901 $query_string = $supplied_url_info['query']; 902 parse_str($query_string, $query_string_parsed); 811 903 812 904 foreach($domains as $key => $domain) { … … 818 910 } 819 911 912 if(isset($query_string_parsed['restful_push_auth_key'])) { 913 $this->push_remote_auth_key = $query_string_parsed['restful_push_auth_key']; 914 $this->push_remote_domain = $supplied_domain; 915 } 916 820 917 // Request data 821 918 $payload = $this->rest_fetch($_POST['restful_push_url'], true); 822 919 823 920 if(empty($payload) || $payload == null) { 824 return array("error" => 'Failed to fetch post data from API', "errors" => $ errors_logged);921 return array("error" => 'Failed to fetch post data from API', "errors" => $this->errors_logged); 825 922 } 826 923 … … 833 930 if($_POST['post_status'] == 'future' && isset($_POST['schedule_date'])) { 834 931 $payload['date'] = wp_date('Y-m-d H:i:s', $this->wp_strtotime($_POST['schedule_date'])); 932 $payload['date_gmt'] = date('Y-m-d H:i:s', $this->wp_strtotime($_POST['schedule_date'])); 835 933 } 836 934 } 837 935 838 936 // Process data 839 $post_id = $this->syndicate_one($payload, true, $force_publish, true, $post_status, true); 840 841 return array("post_id" => $post_id); 937 $post_id = $this->syndicate_one($payload, true, false, true, $post_status, true); 938 939 return array( 940 "post_id" => $post_id, 941 "errors" => $this->errors_logged, 942 'post_status' => $post_status, 943 'post_date' => $payload['date'], 944 ); 842 945 } 843 946 … … 1034 1137 } 1035 1138 1036 private function ingest_image($url, $parent_post_id = null ) {1139 private function ingest_image($url, $parent_post_id = null, $attachment_rest_data = null, $original_attachment_id = null, $post_rest_data = null) { 1037 1140 // Download a file from a URL, and add it to the media library 1038 1141 1039 1142 require_once(ABSPATH . 'wp-admin/includes/image.php'); 1040 1041 $filename = basename($url);1042 $response = wp_remote_get($url,1043 array(1044 "timeout" => 30,1045 )1046 );1047 1048 if(is_wp_error($response)) {1049 $this->log("HTTP request failed when downloading image. " . $response->get_error_message());1050 return;1051 }1052 1053 $image_data = wp_remote_retrieve_body($response);1054 1055 if(empty($image_data)) {1056 $this->log("Failed to download image " . $url);1057 return null;1058 }1059 1060 $upload_dir = wp_upload_dir();1061 1062 if(wp_mkdir_p($upload_dir['path'])) {1063 $file = $upload_dir['path'] . '/' . $filename;1064 } else {1065 $file = $upload_dir['basedir'] . '/' . $filename;1066 }1067 1143 1068 1144 // Lookup existing file by meta field … … 1078 1154 } 1079 1155 1156 $filename = basename($url); 1157 1158 // Check extension - uses get_allowed_mime_types() for mime type list 1159 $check_filetype = wp_check_filetype($filename); 1160 1161 if($check_filetype['type'] == false) { 1162 $this->log("Stopped download of image due to invalid file type: " . $url); 1163 return null; 1164 } 1165 1166 $response = wp_remote_get($url, 1167 array( 1168 "timeout" => 30, 1169 ) 1170 ); 1171 1172 if(is_wp_error($response)) { 1173 $this->log("HTTP request failed when downloading image. " . $response->get_error_message()); 1174 return; 1175 } 1176 1177 $image_data = wp_remote_retrieve_body($response); 1178 1179 if(empty($image_data)) { 1180 $this->log("Failed to download image " . $url); 1181 return null; 1182 } 1183 1184 $upload_dir = wp_upload_dir(); 1185 1186 if(wp_mkdir_p($upload_dir['path'])) { 1187 $file = $upload_dir['path'] . '/' . $filename; 1188 } else { 1189 $file = $upload_dir['basedir'] . '/' . $filename; 1190 } 1191 1080 1192 if(file_exists($file)) { 1081 1193 // File already exists - get the attachment ID … … 1093 1205 } 1094 1206 1095 $wp_filetype = wp_check_filetype($filename, null); 1207 // Check if file is an image 1208 $check_filetype_and_ext = wp_check_filetype_and_ext($file, $filename); 1209 1210 if($check_filetype_and_ext['type'] == false) { 1211 $this->log("Downloaded file is not a valid image: " . $file . ". Deleting file from disk."); 1212 unlink($file); 1213 return null; 1214 } 1096 1215 1097 1216 $attachment = array( 1098 'post_mime_type' => $ wp_filetype['type'],1217 'post_mime_type' => $check_filetype_and_ext['type'], 1099 1218 'post_title' => sanitize_file_name($filename), 1100 1219 'post_content' => '', … … 1109 1228 update_post_meta($attach_id, 'restfulsyndication_original_url', $url); 1110 1229 1230 // Fetch rest data for image 1231 // We will want to save some metadata 1232 if(empty($attachment_rest_data)) { 1233 if(!empty($original_attachment_id) && !empty($post_rest_data)) { 1234 if(isset($post_rest_data['_links']['self'][0]['href'])) { 1235 $post_rest_url = $post_rest_data['_links']['self'][0]['href']; 1236 $attachment_rest_url = str_replace("wp/v2/posts/", "wp/v2/media/", $post_rest_url); 1237 $attachment_rest_url = substr($attachment_rest_url, 0, strpos($attachment_rest_url, "/media/") + 7) . $original_attachment_id; 1238 $attachment_rest_data = $this->rest_fetch($attachment_rest_url, true); 1239 } 1240 } 1241 } 1242 1243 if(isset($attachment_rest_data['alt_text'])) { 1244 update_post_meta($attach_id, '_wp_attachment_image_alt', $attachment_rest_data['alt_text']); 1245 } 1246 1247 if(isset($attachment_rest_data['title']['rendered'])) { 1248 $update = array( 1249 'ID' => $attach_id, 1250 'post_title' => strip_tags($attachment_rest_data['title']['rendered']), 1251 ); 1252 wp_update_post($update); 1253 } 1254 1255 if(isset($attachment_rest_data['caption']['rendered'])) { 1256 $update = array( 1257 'ID' => $attach_id, 1258 'post_excerpt' => strip_tags($attachment_rest_data['caption']['rendered']), 1259 ); 1260 wp_update_post($update); 1261 } 1262 1263 if(isset($attachment_rest_data['description']['rendered'])) { 1264 $update = array( 1265 'ID' => $attach_id, 1266 'post_content' => strip_tags($attachment_rest_data['description']['rendered']), 1267 ); 1268 wp_update_post($update); 1269 } 1270 1271 // Image Credit fields 1272 $fields = array( 1273 'media_credit_text', 1274 'media_credit_url', 1275 'media_credit_supplied', 1276 'supplied_notes', 1277 'media_credit_original', 1278 'media_credit_ai_generated', 1279 ); 1280 1281 foreach($fields as $field) { 1282 if(isset($attachment_rest_data['image_credit'][$field])) { 1283 if($attachment_rest_data['image_credit'][$field] === true) { 1284 $attachment_rest_data['image_credit'][$field] = '1'; 1285 } elseif($attachment_rest_data['image_credit'][$field] === false) { 1286 $attachment_rest_data['image_credit'][$field] = '0'; 1287 } 1288 update_post_meta($attach_id, $field, sanitize_text_field($attachment_rest_data['image_credit'][$field])); 1289 } 1290 } 1291 1111 1292 if(!file_exists($file)) { 1112 1293 $this->log("Image doesn't exist after processing " . $file); … … 1115 1296 1116 1297 } 1117 1118 1119 1298 1120 1299 return $attach_id; … … 1165 1344 } 1166 1345 1346 private function get_image_id_from_classes($class_string) { 1347 // If given a string of classes, return just the Image ID (if it exists) 1348 if(preg_match('/wp-image-(\d+)/', $class_string, $matches)) { 1349 return intval($matches[1]); 1350 } 1351 return null; 1352 } 1353 1354 public function fields_image_credits_acf() { 1355 if (!function_exists('acf_add_local_field_group')) { 1356 return; 1357 } 1358 1359 acf_add_local_field_group(array( 1360 'key' => 'group_683a92f3ef691', 1361 'title' => 'Media Source', 1362 'fields' => array( 1363 array( 1364 'key' => 'field_683a92f4ab28d', 1365 'label' => 'Media Credit', 1366 'name' => 'media_credit_text', 1367 'aria-label' => '', 1368 'type' => 'text', 1369 'instructions' => 'Store the name of the photographer or stock photo website here.', 1370 'required' => 0, 1371 'conditional_logic' => 0, 1372 'wrapper' => array( 1373 'width' => '', 1374 'class' => '', 1375 'id' => '', 1376 ), 1377 'relevanssi_exclude' => 1, 1378 'default_value' => '', 1379 'maxlength' => '', 1380 'allow_in_bindings' => 0, 1381 'placeholder' => '', 1382 'prepend' => '', 1383 'append' => '', 1384 ), 1385 array( 1386 'key' => 'field_683a9338ab28e', 1387 'label' => 'Media Credit URL', 1388 'name' => 'media_credit_url', 1389 'aria-label' => '', 1390 'type' => 'url', 1391 'instructions' => 'Paste the link to the original media source.', 1392 'required' => 0, 1393 'conditional_logic' => 0, 1394 'wrapper' => array( 1395 'width' => '', 1396 'class' => '', 1397 'id' => '', 1398 ), 1399 'relevanssi_exclude' => 1, 1400 'default_value' => '', 1401 'allow_in_bindings' => 0, 1402 'placeholder' => '', 1403 ), 1404 array( 1405 'key' => 'field_683d2a1832a6d', 1406 'label' => 'Supplied?', 1407 'name' => 'media_credit_supplied', 1408 'aria-label' => '', 1409 'type' => 'true_false', 1410 'instructions' => 'If this image has been supplied with permission to use it on your website, tick this box. This is sometimes the case with press releases. However, you should still ensure you have permission to use it from the original creator or photographer. If you downloaded this photo from a stock website, do not tick this box.', 1411 'required' => 0, 1412 'conditional_logic' => 0, 1413 'wrapper' => array( 1414 'width' => '', 1415 'class' => '', 1416 'id' => '', 1417 ), 1418 'relevanssi_exclude' => 1, 1419 'message' => '', 1420 'default_value' => 0, 1421 'allow_in_bindings' => 0, 1422 'ui' => 0, 1423 'ui_on_text' => '', 1424 'ui_off_text' => '', 1425 ), 1426 array( 1427 'key' => 'field_6853aa7b51fdd', 1428 'label' => 'Supplied Notes', 1429 'name' => 'supplied_notes', 1430 'aria-label' => '', 1431 'type' => 'textarea', 1432 'instructions' => 'Please note who who gave you permission to use this image. This is an internal field and won\'t be displayed to end-users of your website.', 1433 'required' => 0, 1434 'conditional_logic' => array( 1435 array( 1436 array( 1437 'field' => 'field_683d2a1832a6d', 1438 'operator' => '==', 1439 'value' => '1', 1440 ), 1441 ), 1442 ), 1443 'wrapper' => array( 1444 'width' => '', 1445 'class' => '', 1446 'id' => '', 1447 ), 1448 'relevanssi_exclude' => 0, 1449 'default_value' => '', 1450 'maxlength' => '', 1451 'allow_in_bindings' => 0, 1452 'rows' => '', 1453 'placeholder' => '', 1454 'new_lines' => '', 1455 ), 1456 array( 1457 'key' => 'field_683d2af832a6e', 1458 'label' => 'Original?', 1459 'name' => 'media_credit_original', 1460 'aria-label' => '', 1461 'type' => 'true_false', 1462 'instructions' => 'If you have created this image yourself, tick this box.', 1463 'required' => 0, 1464 'conditional_logic' => 0, 1465 'wrapper' => array( 1466 'width' => '', 1467 'class' => '', 1468 'id' => '', 1469 ), 1470 'relevanssi_exclude' => 1, 1471 'message' => '', 1472 'default_value' => 0, 1473 'allow_in_bindings' => 0, 1474 'ui' => 0, 1475 'ui_on_text' => '', 1476 'ui_off_text' => '', 1477 ), 1478 array( 1479 'key' => 'field_683d2b2732a6f', 1480 'label' => 'AI Generated?', 1481 'name' => 'media_credit_ai_generated', 1482 'aria-label' => '', 1483 'type' => 'true_false', 1484 'instructions' => 'If this image was created with Artificial Intelligence (AI) tools, tick this box. Usage of AI media creation tools is still a grey area, and caution should be exercised.', 1485 'required' => 0, 1486 'conditional_logic' => 0, 1487 'wrapper' => array( 1488 'width' => '', 1489 'class' => '', 1490 'id' => '', 1491 ), 1492 'relevanssi_exclude' => 1, 1493 'message' => '', 1494 'default_value' => 0, 1495 'allow_in_bindings' => 0, 1496 'ui' => 0, 1497 'ui_on_text' => '', 1498 'ui_off_text' => '', 1499 ), 1500 ), 1501 'location' => array( 1502 array( 1503 array( 1504 'param' => 'attachment', 1505 'operator' => '==', 1506 'value' => 'all', 1507 ), 1508 ), 1509 ), 1510 'menu_order' => 0, 1511 'position' => 'normal', 1512 'style' => 'seamless', 1513 'label_placement' => 'top', 1514 'instruction_placement' => 'label', 1515 'hide_on_screen' => '', 1516 'active' => true, 1517 'description' => '', 1518 'show_in_rest' => 0, 1519 )); 1520 } 1521 1522 public function fields_image_credits_api_header($headers) { 1523 // Header to disable embedding image credits in 1524 $headers['X-RESTful-Syndication-Hide-Image-Credits'] = 'True'; 1525 1526 return $headers; 1527 } 1528 1167 1529 private function random_str($length, $keyspace = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ!@#$%^&*()-=+`~') { 1168 1530 // From https://stackoverflow.com/a/31284266/2888733 -
restful-syndication/tags/1.6.0/readme.txt
r3214629 r3384901 1 1 === RESTful Content Syndication === 2 2 Contributors: anthonyeden 3 Tags: syndication, rest, wp-rest 4 Stable Tag: 1. 5.03 Tags: syndication, rest, wp-rest, wp-json, json 4 Stable Tag: 1.6.0 5 5 Requires at least: 6.0.0 6 Tested up to: 6. 7.16 Tested up to: 6.8.3 7 7 Requires PHP: 7.4.0 8 8 License: GPLv2 or later … … 59 59 60 60 == Changelog == 61 62 = 1.6.0 = 63 64 * Image Ingest: Import meta fields where available (title, caption, alt text) 65 * Image Credits: Add option to enable these new media credit fields via the plugin. This option is only available if you have the Advanced Custom Fields plugin active on your site. 66 * Add support for Author taxonomy 67 * Content Push: Return additional fields upon successful push 68 * Content Push: GMT date should align with local date 69 * Content Push: Provide auth key back to source site for subsequent resource pulls 70 * Use \Dom\HTMLDocument where available (in PHP 8.4) 71 * Add new filter: restful_syndication_api_headers 72 * Security: Ensure only image files are downloaded and stored locally (thanks Wordfence/kr0d for responsibly disclosing) 61 73 62 74 = 1.5.0 = -
restful-syndication/trunk/index.php
r3180974 r3384901 4 4 Plugin URI: https://mediarealm.com.au/ 5 5 Description: Import content from the Wordpress REST API on another Wordpress site 6 Version: 1. 5.06 Version: 1.6.0 7 7 Author: Media Realm 8 8 Author URI: https://www.mediarealm.com.au/ … … 106 106 public $errors_logged = array(); 107 107 108 private $push_remote_auth_key = null; 109 private $push_remote_domain = null; 110 108 111 public function __construct() { 109 112 add_action('admin_menu', array($this, 'admin_menu')); … … 122 125 )); 123 126 }); 127 128 if(is_plugin_active('advanced-custom-fields/acf.php') || is_plugin_active('advanced-custom-fields-pro/acf.php')) { 129 if(apply_filters('restful_syndication_allow_image_credit_fields', true) === true) { 130 $this->settings['fields_image_credits'] = array( 131 "title" => "Enable Image Credit Fields", 132 "type" => "checkbox", 133 ); 134 135 $options = get_option($this->settings_prefix . 'settings'); 136 if($options['fields_image_credits'] == "true") { 137 add_action('acf/include_fields', array($this, 'fields_image_credits_acf')); 138 add_filter('restful_syndication_api_headers', array($this, 'fields_image_credits_api_header')); 139 } 140 } 141 142 } 124 143 } 125 144 … … 314 333 } 315 334 335 $headers = array( 336 'X-RESTful-Syndication-Version' => get_file_data(__FILE__, array('Version' => 'Version'), false)['Version'], 337 ); 338 339 $headers = apply_filters('restful_syndication_api_headers', $headers); 340 316 341 if(!empty($options['auth_username']) && !empty($options['auth_password'])) { 317 $headers = array( 318 'Authorization' => 'Basic ' . base64_encode($options['auth_username'] . ':' . $options['auth_password']), 319 ); 320 } else { 321 $headers = array(); 342 $headers['Authorization'] = 'Basic ' . base64_encode($options['auth_username'] . ':' . $options['auth_password']); 343 } 344 345 if($full === true && $this->push_remote_domain !== null && $this->push_remote_auth_key !== null && strpos($url, "restful_push_auth_key=") === false) { 346 // Add the Push Auth Key back to the URL as a parameter for subsequent calls 347 // This is needed for taxonomies, authors, images, etc. 348 349 $query = parse_url($url, PHP_URL_QUERY); 350 if ($query) { 351 $url .= '&restful_push_auth_key=' . urlencode($this->push_remote_auth_key); 352 } else { 353 $url .= '?restful_push_auth_key=' . urlencode($this->push_remote_auth_key); 354 } 322 355 } 323 356 … … 335 368 } 336 369 337 $data = json_decode(wp_remote_retrieve_body($response), true); 370 $response_body = wp_remote_retrieve_body($response); 371 $data = json_decode($response_body, true); 372 373 if($data == false) { 374 $this->log("rest_fetch() - JSON not decoded when fetching ".$url."."); 375 } 338 376 339 377 return $data; … … 405 443 $existing_post_id = 0; 406 444 445 if(!is_array($post)) { 446 $this->log("syndicate_one() - post is not an array. Failing."); 447 return; 448 } 449 407 450 // Have we already ingested this post? 408 451 if($allow_overwrite === true && $this->post_guid_exists($post['guid']['rendered']) !== null) { … … 411 454 } else if($this->post_guid_exists($post['guid']['rendered']) !== null) { 412 455 // Already exists on this site - skip over this post 456 $this->log("syndicate_one() - Post GUID already exists. Skipping."); 413 457 return; 414 458 } … … 418 462 if(isset($options['earliest_post_date']) && strtotime($options['earliest_post_date']) !== false && strtotime($post['date']) <= strtotime($options['earliest_post_date'])) { 419 463 // This post is earlier than the specified start date 464 $this->log("syndicate_one() - Post is earlier than earliest post cutoff date. Skipping."); 420 465 return; 421 466 } … … 455 500 456 501 // Parse the HTML 457 $dom = new domDocument; 458 $dom->loadHTML('<?xml encoding="utf-8" ?>' . $html, LIBXML_HTML_NOIMPLIED|LIBXML_HTML_NODEFDTD); 502 if(class_exists('\Dom\HTMLDocument')) { 503 // PHP 8.4+ 504 $dom = \Dom\HTMLDocument::createFromString($html, LIBXML_HTML_NOIMPLIED|LIBXML_NOERROR); 505 $dom->strictErrorChecking = false; 506 } else { 507 $dom = new domDocument; 508 $html = str_replace("<hr>", "<hr />", $html); // Workaround for certain libxml versions breaking on this unclosed tag 509 $dom->loadHTML('<?xml encoding="utf-8" ?>' . $html, LIBXML_HTML_NOIMPLIED|LIBXML_HTML_NODEFDTD); 510 } 459 511 460 512 // Find and download any embedded images found in the HTML … … 465 517 // Download the image and attach it 466 518 $url = $img->getAttribute('src'); 467 $attachment_id = $this->ingest_image($url); 468 519 $classes = $img->getAttribute('class'); 520 $image_id = $this->get_image_id_from_classes($classes); 521 $attachment_id = $this->ingest_image($url, null, null, $image_id, $post); 522 469 523 // Update the SRC in the HTML 470 524 $url_new = wp_get_attachment_image_src($attachment_id, $options['image_embed_size']); … … 512 566 $audio_shortcode = $dom->createElement('div'); 513 567 $audio_shortcode->setAttribute('class', 'audio-filter'); 514 $audio_shortcode-> nodeValue= '[audio src="'.esc_url($url).'"]';568 $audio_shortcode->textContent = '[audio src="'.esc_url($url).'"]'; 515 569 516 570 // Replace the original <audio> tag with this new <div class="audio-filter">[audio]</div> arrangement … … 539 593 $embed_shortcode = $dom->createElement('div'); 540 594 $embed_shortcode->setAttribute('class', 'video-filter'); 541 $embed_shortcode-> nodeValue= '[embed]'.esc_url($url_new).'[/embed]';595 $embed_shortcode->textContent = '[embed]'.esc_url($url_new).'[/embed]'; 542 596 543 597 // Replace the original <div class="embed_youtube"> tag with this new <div>[embed]url[/embed]</div> arrangement … … 576 630 // Create a new paragraph, and insert the iframe shortcode 577 631 $embed_shortcode = $dom->createElement('p'); 578 $embed_shortcode-> nodeValue= '[restful_syndication_iframe src="'.esc_url($url).'" width="'.esc_attr($width).'" height="'.esc_attr($height).'"]';632 $embed_shortcode->textContent = '[restful_syndication_iframe src="'.esc_url($url).'" width="'.esc_attr($width).'" height="'.esc_attr($height).'"]'; 579 633 580 634 // Replace the original <iframe> tag with this new <p>[restful_syndication_iframe src="url"]</p> arrangement … … 609 663 // Create a new paragraph, and insert the iframe shortcode 610 664 $embed_shortcode = $dom->createElement('p'); 611 $embed_shortcode-> nodeValue= '[restful_syndication_iframe src="'.esc_url($url).'" width="'.esc_attr($width).'" height="'.esc_attr($height).'"]';665 $embed_shortcode->textContent = '[restful_syndication_iframe src="'.esc_url($url).'" width="'.esc_attr($width).'" height="'.esc_attr($height).'"]'; 612 666 613 667 // Replace the original <iframe> tag with this new <p>[restful_syndication_iframe src="url"]</p> arrangement … … 677 731 if(is_array($tag)) { 678 732 $tags[] = $tag['term_id']; 733 } 734 } 735 } 736 } 737 } 738 } 739 } 740 741 // Find local matching author taxonomy 742 $author_terms = array(); 743 744 if(isset($post['_links']['wp:term']) && taxonomy_exists('author')) { 745 foreach($post['_links']['wp:term'] as $term_link) { 746 if($term_link['taxonomy'] == 'author') { 747 $term_data_all = $this->rest_fetch($term_link['href'], true); 748 749 foreach($term_data_all as $term_data) { 750 if(isset($term_data['name']) && !empty($term_data['name'])) { 751 $term = get_term_by('name', $term_data['name'], 'author'); 752 753 if($term !== false) { 754 // Term already exists 755 $author_terms[] = $term->term_id; 756 } else { 757 // Create the author taxonomy term 758 $term = wp_insert_term($term_data['name'], 'author'); 759 760 if(is_array($term)) { 761 $author_terms[] = $term['term_id']; 679 762 } 680 763 } … … 750 833 ), false); 751 834 835 if($post_id > 0 && count($author_terms) > 0) { 836 // Set author taxonomy terms 837 wp_set_object_terms($post_id, $author_terms, 'author'); 838 } 839 752 840 if($post_id > 0 && isset($post['_links']['wp:featuredmedia'][0]['href'])) { 753 841 // Download and attach featured image … … 757 845 758 846 if(isset($attachment['media_details']['sizes']['full']['source_url'])) { 759 $featured_attachment_id = $this->ingest_image($attachment['media_details']['sizes']['full']['source_url'], $post_id );847 $featured_attachment_id = $this->ingest_image($attachment['media_details']['sizes']['full']['source_url'], $post_id, $attachment); 760 848 set_post_thumbnail($post_id, $featured_attachment_id); 761 849 } elseif(isset($attachment['source_url'])) { 762 $featured_attachment_id = $this->ingest_image($attachment['source_url'], $post_id );850 $featured_attachment_id = $this->ingest_image($attachment['source_url'], $post_id, $attachment); 763 851 set_post_thumbnail($post_id, $featured_attachment_id); 764 852 } else { 765 $this->log("Attachment full image not found: " . $api_url); 766 } 853 $this->log("Attachment full image not found: " . $api_url . " - " . print_r($attachment, true)); 854 } 855 } else { 856 $this->log("Featured Media Link not found. " . $post_id); 767 857 } 768 858 … … 809 899 $supplied_url_info = parse_url($_POST['restful_push_url']); 810 900 $supplied_domain = $supplied_url_info['host']; 901 $query_string = $supplied_url_info['query']; 902 parse_str($query_string, $query_string_parsed); 811 903 812 904 foreach($domains as $key => $domain) { … … 818 910 } 819 911 912 if(isset($query_string_parsed['restful_push_auth_key'])) { 913 $this->push_remote_auth_key = $query_string_parsed['restful_push_auth_key']; 914 $this->push_remote_domain = $supplied_domain; 915 } 916 820 917 // Request data 821 918 $payload = $this->rest_fetch($_POST['restful_push_url'], true); 822 919 823 920 if(empty($payload) || $payload == null) { 824 return array("error" => 'Failed to fetch post data from API', "errors" => $ errors_logged);921 return array("error" => 'Failed to fetch post data from API', "errors" => $this->errors_logged); 825 922 } 826 923 … … 833 930 if($_POST['post_status'] == 'future' && isset($_POST['schedule_date'])) { 834 931 $payload['date'] = wp_date('Y-m-d H:i:s', $this->wp_strtotime($_POST['schedule_date'])); 932 $payload['date_gmt'] = date('Y-m-d H:i:s', $this->wp_strtotime($_POST['schedule_date'])); 835 933 } 836 934 } 837 935 838 936 // Process data 839 $post_id = $this->syndicate_one($payload, true, $force_publish, true, $post_status, true); 840 841 return array("post_id" => $post_id); 937 $post_id = $this->syndicate_one($payload, true, false, true, $post_status, true); 938 939 return array( 940 "post_id" => $post_id, 941 "errors" => $this->errors_logged, 942 'post_status' => $post_status, 943 'post_date' => $payload['date'], 944 ); 842 945 } 843 946 … … 1034 1137 } 1035 1138 1036 private function ingest_image($url, $parent_post_id = null ) {1139 private function ingest_image($url, $parent_post_id = null, $attachment_rest_data = null, $original_attachment_id = null, $post_rest_data = null) { 1037 1140 // Download a file from a URL, and add it to the media library 1038 1141 1039 1142 require_once(ABSPATH . 'wp-admin/includes/image.php'); 1040 1041 $filename = basename($url);1042 $response = wp_remote_get($url,1043 array(1044 "timeout" => 30,1045 )1046 );1047 1048 if(is_wp_error($response)) {1049 $this->log("HTTP request failed when downloading image. " . $response->get_error_message());1050 return;1051 }1052 1053 $image_data = wp_remote_retrieve_body($response);1054 1055 if(empty($image_data)) {1056 $this->log("Failed to download image " . $url);1057 return null;1058 }1059 1060 $upload_dir = wp_upload_dir();1061 1062 if(wp_mkdir_p($upload_dir['path'])) {1063 $file = $upload_dir['path'] . '/' . $filename;1064 } else {1065 $file = $upload_dir['basedir'] . '/' . $filename;1066 }1067 1143 1068 1144 // Lookup existing file by meta field … … 1078 1154 } 1079 1155 1156 $filename = basename($url); 1157 1158 // Check extension - uses get_allowed_mime_types() for mime type list 1159 $check_filetype = wp_check_filetype($filename); 1160 1161 if($check_filetype['type'] == false) { 1162 $this->log("Stopped download of image due to invalid file type: " . $url); 1163 return null; 1164 } 1165 1166 $response = wp_remote_get($url, 1167 array( 1168 "timeout" => 30, 1169 ) 1170 ); 1171 1172 if(is_wp_error($response)) { 1173 $this->log("HTTP request failed when downloading image. " . $response->get_error_message()); 1174 return; 1175 } 1176 1177 $image_data = wp_remote_retrieve_body($response); 1178 1179 if(empty($image_data)) { 1180 $this->log("Failed to download image " . $url); 1181 return null; 1182 } 1183 1184 $upload_dir = wp_upload_dir(); 1185 1186 if(wp_mkdir_p($upload_dir['path'])) { 1187 $file = $upload_dir['path'] . '/' . $filename; 1188 } else { 1189 $file = $upload_dir['basedir'] . '/' . $filename; 1190 } 1191 1080 1192 if(file_exists($file)) { 1081 1193 // File already exists - get the attachment ID … … 1093 1205 } 1094 1206 1095 $wp_filetype = wp_check_filetype($filename, null); 1207 // Check if file is an image 1208 $check_filetype_and_ext = wp_check_filetype_and_ext($file, $filename); 1209 1210 if($check_filetype_and_ext['type'] == false) { 1211 $this->log("Downloaded file is not a valid image: " . $file . ". Deleting file from disk."); 1212 unlink($file); 1213 return null; 1214 } 1096 1215 1097 1216 $attachment = array( 1098 'post_mime_type' => $ wp_filetype['type'],1217 'post_mime_type' => $check_filetype_and_ext['type'], 1099 1218 'post_title' => sanitize_file_name($filename), 1100 1219 'post_content' => '', … … 1109 1228 update_post_meta($attach_id, 'restfulsyndication_original_url', $url); 1110 1229 1230 // Fetch rest data for image 1231 // We will want to save some metadata 1232 if(empty($attachment_rest_data)) { 1233 if(!empty($original_attachment_id) && !empty($post_rest_data)) { 1234 if(isset($post_rest_data['_links']['self'][0]['href'])) { 1235 $post_rest_url = $post_rest_data['_links']['self'][0]['href']; 1236 $attachment_rest_url = str_replace("wp/v2/posts/", "wp/v2/media/", $post_rest_url); 1237 $attachment_rest_url = substr($attachment_rest_url, 0, strpos($attachment_rest_url, "/media/") + 7) . $original_attachment_id; 1238 $attachment_rest_data = $this->rest_fetch($attachment_rest_url, true); 1239 } 1240 } 1241 } 1242 1243 if(isset($attachment_rest_data['alt_text'])) { 1244 update_post_meta($attach_id, '_wp_attachment_image_alt', $attachment_rest_data['alt_text']); 1245 } 1246 1247 if(isset($attachment_rest_data['title']['rendered'])) { 1248 $update = array( 1249 'ID' => $attach_id, 1250 'post_title' => strip_tags($attachment_rest_data['title']['rendered']), 1251 ); 1252 wp_update_post($update); 1253 } 1254 1255 if(isset($attachment_rest_data['caption']['rendered'])) { 1256 $update = array( 1257 'ID' => $attach_id, 1258 'post_excerpt' => strip_tags($attachment_rest_data['caption']['rendered']), 1259 ); 1260 wp_update_post($update); 1261 } 1262 1263 if(isset($attachment_rest_data['description']['rendered'])) { 1264 $update = array( 1265 'ID' => $attach_id, 1266 'post_content' => strip_tags($attachment_rest_data['description']['rendered']), 1267 ); 1268 wp_update_post($update); 1269 } 1270 1271 // Image Credit fields 1272 $fields = array( 1273 'media_credit_text', 1274 'media_credit_url', 1275 'media_credit_supplied', 1276 'supplied_notes', 1277 'media_credit_original', 1278 'media_credit_ai_generated', 1279 ); 1280 1281 foreach($fields as $field) { 1282 if(isset($attachment_rest_data['image_credit'][$field])) { 1283 if($attachment_rest_data['image_credit'][$field] === true) { 1284 $attachment_rest_data['image_credit'][$field] = '1'; 1285 } elseif($attachment_rest_data['image_credit'][$field] === false) { 1286 $attachment_rest_data['image_credit'][$field] = '0'; 1287 } 1288 update_post_meta($attach_id, $field, sanitize_text_field($attachment_rest_data['image_credit'][$field])); 1289 } 1290 } 1291 1111 1292 if(!file_exists($file)) { 1112 1293 $this->log("Image doesn't exist after processing " . $file); … … 1115 1296 1116 1297 } 1117 1118 1119 1298 1120 1299 return $attach_id; … … 1165 1344 } 1166 1345 1346 private function get_image_id_from_classes($class_string) { 1347 // If given a string of classes, return just the Image ID (if it exists) 1348 if(preg_match('/wp-image-(\d+)/', $class_string, $matches)) { 1349 return intval($matches[1]); 1350 } 1351 return null; 1352 } 1353 1354 public function fields_image_credits_acf() { 1355 if (!function_exists('acf_add_local_field_group')) { 1356 return; 1357 } 1358 1359 acf_add_local_field_group(array( 1360 'key' => 'group_683a92f3ef691', 1361 'title' => 'Media Source', 1362 'fields' => array( 1363 array( 1364 'key' => 'field_683a92f4ab28d', 1365 'label' => 'Media Credit', 1366 'name' => 'media_credit_text', 1367 'aria-label' => '', 1368 'type' => 'text', 1369 'instructions' => 'Store the name of the photographer or stock photo website here.', 1370 'required' => 0, 1371 'conditional_logic' => 0, 1372 'wrapper' => array( 1373 'width' => '', 1374 'class' => '', 1375 'id' => '', 1376 ), 1377 'relevanssi_exclude' => 1, 1378 'default_value' => '', 1379 'maxlength' => '', 1380 'allow_in_bindings' => 0, 1381 'placeholder' => '', 1382 'prepend' => '', 1383 'append' => '', 1384 ), 1385 array( 1386 'key' => 'field_683a9338ab28e', 1387 'label' => 'Media Credit URL', 1388 'name' => 'media_credit_url', 1389 'aria-label' => '', 1390 'type' => 'url', 1391 'instructions' => 'Paste the link to the original media source.', 1392 'required' => 0, 1393 'conditional_logic' => 0, 1394 'wrapper' => array( 1395 'width' => '', 1396 'class' => '', 1397 'id' => '', 1398 ), 1399 'relevanssi_exclude' => 1, 1400 'default_value' => '', 1401 'allow_in_bindings' => 0, 1402 'placeholder' => '', 1403 ), 1404 array( 1405 'key' => 'field_683d2a1832a6d', 1406 'label' => 'Supplied?', 1407 'name' => 'media_credit_supplied', 1408 'aria-label' => '', 1409 'type' => 'true_false', 1410 'instructions' => 'If this image has been supplied with permission to use it on your website, tick this box. This is sometimes the case with press releases. However, you should still ensure you have permission to use it from the original creator or photographer. If you downloaded this photo from a stock website, do not tick this box.', 1411 'required' => 0, 1412 'conditional_logic' => 0, 1413 'wrapper' => array( 1414 'width' => '', 1415 'class' => '', 1416 'id' => '', 1417 ), 1418 'relevanssi_exclude' => 1, 1419 'message' => '', 1420 'default_value' => 0, 1421 'allow_in_bindings' => 0, 1422 'ui' => 0, 1423 'ui_on_text' => '', 1424 'ui_off_text' => '', 1425 ), 1426 array( 1427 'key' => 'field_6853aa7b51fdd', 1428 'label' => 'Supplied Notes', 1429 'name' => 'supplied_notes', 1430 'aria-label' => '', 1431 'type' => 'textarea', 1432 'instructions' => 'Please note who who gave you permission to use this image. This is an internal field and won\'t be displayed to end-users of your website.', 1433 'required' => 0, 1434 'conditional_logic' => array( 1435 array( 1436 array( 1437 'field' => 'field_683d2a1832a6d', 1438 'operator' => '==', 1439 'value' => '1', 1440 ), 1441 ), 1442 ), 1443 'wrapper' => array( 1444 'width' => '', 1445 'class' => '', 1446 'id' => '', 1447 ), 1448 'relevanssi_exclude' => 0, 1449 'default_value' => '', 1450 'maxlength' => '', 1451 'allow_in_bindings' => 0, 1452 'rows' => '', 1453 'placeholder' => '', 1454 'new_lines' => '', 1455 ), 1456 array( 1457 'key' => 'field_683d2af832a6e', 1458 'label' => 'Original?', 1459 'name' => 'media_credit_original', 1460 'aria-label' => '', 1461 'type' => 'true_false', 1462 'instructions' => 'If you have created this image yourself, tick this box.', 1463 'required' => 0, 1464 'conditional_logic' => 0, 1465 'wrapper' => array( 1466 'width' => '', 1467 'class' => '', 1468 'id' => '', 1469 ), 1470 'relevanssi_exclude' => 1, 1471 'message' => '', 1472 'default_value' => 0, 1473 'allow_in_bindings' => 0, 1474 'ui' => 0, 1475 'ui_on_text' => '', 1476 'ui_off_text' => '', 1477 ), 1478 array( 1479 'key' => 'field_683d2b2732a6f', 1480 'label' => 'AI Generated?', 1481 'name' => 'media_credit_ai_generated', 1482 'aria-label' => '', 1483 'type' => 'true_false', 1484 'instructions' => 'If this image was created with Artificial Intelligence (AI) tools, tick this box. Usage of AI media creation tools is still a grey area, and caution should be exercised.', 1485 'required' => 0, 1486 'conditional_logic' => 0, 1487 'wrapper' => array( 1488 'width' => '', 1489 'class' => '', 1490 'id' => '', 1491 ), 1492 'relevanssi_exclude' => 1, 1493 'message' => '', 1494 'default_value' => 0, 1495 'allow_in_bindings' => 0, 1496 'ui' => 0, 1497 'ui_on_text' => '', 1498 'ui_off_text' => '', 1499 ), 1500 ), 1501 'location' => array( 1502 array( 1503 array( 1504 'param' => 'attachment', 1505 'operator' => '==', 1506 'value' => 'all', 1507 ), 1508 ), 1509 ), 1510 'menu_order' => 0, 1511 'position' => 'normal', 1512 'style' => 'seamless', 1513 'label_placement' => 'top', 1514 'instruction_placement' => 'label', 1515 'hide_on_screen' => '', 1516 'active' => true, 1517 'description' => '', 1518 'show_in_rest' => 0, 1519 )); 1520 } 1521 1522 public function fields_image_credits_api_header($headers) { 1523 // Header to disable embedding image credits in 1524 $headers['X-RESTful-Syndication-Hide-Image-Credits'] = 'True'; 1525 1526 return $headers; 1527 } 1528 1167 1529 private function random_str($length, $keyspace = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ!@#$%^&*()-=+`~') { 1168 1530 // From https://stackoverflow.com/a/31284266/2888733 -
restful-syndication/trunk/readme.txt
r3214629 r3384901 1 1 === RESTful Content Syndication === 2 2 Contributors: anthonyeden 3 Tags: syndication, rest, wp-rest 4 Stable Tag: 1. 5.03 Tags: syndication, rest, wp-rest, wp-json, json 4 Stable Tag: 1.6.0 5 5 Requires at least: 6.0.0 6 Tested up to: 6. 7.16 Tested up to: 6.8.3 7 7 Requires PHP: 7.4.0 8 8 License: GPLv2 or later … … 59 59 60 60 == Changelog == 61 62 = 1.6.0 = 63 64 * Image Ingest: Import meta fields where available (title, caption, alt text) 65 * Image Credits: Add option to enable these new media credit fields via the plugin. This option is only available if you have the Advanced Custom Fields plugin active on your site. 66 * Add support for Author taxonomy 67 * Content Push: Return additional fields upon successful push 68 * Content Push: GMT date should align with local date 69 * Content Push: Provide auth key back to source site for subsequent resource pulls 70 * Use \Dom\HTMLDocument where available (in PHP 8.4) 71 * Add new filter: restful_syndication_api_headers 72 * Security: Ensure only image files are downloaded and stored locally (thanks Wordfence/kr0d for responsibly disclosing) 61 73 62 74 = 1.5.0 =
Note: See TracChangeset
for help on using the changeset viewer.