'aero_auto_sync', 'meta_value' => 1, 'post_type' => 'aero-template', 'post_status' => 'private', 'numberposts' => -1 ]); //Loop through the post foreach ($aeroPosts as $post) { //Get the token $token = get_post_meta($post->ID, "aero_token",true); //Check if there are new/modified records $response = aeropageModCheckApiCall($token); //If there's an error, we skip if($response["status"] !== "success") continue; //if there are new/modified records, we sync it if($response["has_new_records"] == 1){ aeropageSyncPosts($post->ID); } } }catch(Exception $e){ die(json_encode( array( "status" => "error", "message" => $e->getMessage() ) )); } } add_action('admin_menu', 'aeropage_plugin_menu'); function aeropage_plugin_menu(){ add_menu_page( 'Aeropage Sync for Airtable', 'Aeropage', 'manage_options', 'aeropage' , 'aeroplugin_admin_page', plugin_dir_url( __FILE__ ) . 'assets/aeropage-icon-white-20px.svg', 61 ); } /** * Init Admin Page. * * @return void */ function aeroplugin_admin_page() { require_once plugin_dir_path( __FILE__ ) . 'templates/app.php'; //aeropageList(); } add_action( 'admin_enqueue_scripts', 'aeroplugin_admin_enqueue_scripts' ); /** * Enqueue scripts and styles. * * @return void */ function aeroplugin_admin_enqueue_scripts() { //Enqueue only in the plugin page. if(isset($_GET['page']) && $_GET['page'] === "aeropage"){ wp_enqueue_style( 'aeroplugin-style', plugin_dir_url( __FILE__ ) . 'build/index.css', array(), '1.2.3' ); wp_enqueue_script( 'aeroplugin-script', plugin_dir_url( __FILE__ ) . 'build/index.js', array( 'wp-element' ), date("h:i:s"), true ); wp_add_inline_script( 'aeroplugin-script', 'const MYSCRIPT = ' . json_encode( array( 'ajaxUrl' => admin_url( 'admin-ajax.php' ), 'plugin_admin_path' => parse_url(admin_url())["path"], 'plugin_assets' => plugin_dir_url( __FILE__ ).'assets', 'plugin_name' => "aeropage", //This is the name of the plugin. 'wp_nonce' => wp_create_nonce( 'aeropage_nonce' ), ) ), 'before' ); } } add_action("wp_ajax_aeropageList", "aeropageList"); function aeropageList() { aeropageRefererChecker(); aeropageCheckUserCapabilities(); $aeroPosts = get_posts(['post_type' => 'aero-template','post_status' => 'private','numberposts' => -1]); foreach ($aeroPosts as $post) { $post->sync_status = get_post_meta($post->ID, "aero_sync_status",true); $post->sync_time = get_post_meta($post->ID, "aero_sync_time",true); $post->sync_message = get_post_meta($post->ID, "aero_sync_message",true); $post->connection = get_post_meta($post->ID, "aero_connection", true); $post->aero_page_id = get_post_meta($post->ID, "aero_page_id", true); $post->aero_website_id = get_post_meta($post->ID, "aero_website_id", true); $post->token = get_post_meta($post->ID, "aero_token", true); } // this is for react... header('Content-Type: application/json'); die(json_encode($aeroPosts)); } /** This adds sync to the admin bar */ add_action( 'admin_bar_menu', 'aeroAddAdminBar', 100 ); function aeroAddAdminBar( $admin_bar ){ global $post; if(!$post) return; $aeroCPT = get_post_meta($post->ID, '_aero_cpt', true); //If there's a value for _aero_cpt, we add this nav bar item //Will only show if user is an admin, we don't want it to show for 'members' only if($aeroCPT && current_user_can( 'manage_options' )){ $admin_bar->add_menu( array( 'id'=>'aero-sync-bar', 'title'=>'
 Resync Aeropage
', 'href'=>'', ) ); } } //This adds the View Record link in the admin bar menu add_action( 'admin_bar_menu', 'aeroRecordLink', 101 ); function aeroRecordLink( $admin_bar ){ global $post; if(!$post) return; //Get the Custom Post Type ID used for the $aeroCPT = get_post_meta($post->ID, '_aero_cpt', true); //If there's a value for _aero_cpt, we add this nav bar item //Will only show if user is an admin, we don't want it to show for 'members' only if($aeroCPT && current_user_can( 'manage_options' )){ //Get the connection from the parent CPT $connection = get_post_meta($aeroCPT, "aero_connection", true); //Get the record ID $recordID = get_post_meta($post->ID, '_aero_id', true); $admin_bar->add_menu( array( 'id'=>'aero-record-link', 'title'=>'View Record', 'href'=>"https://airtable.com/".$connection."/".$recordID, 'meta' => array( 'target' => '_blank' ) ) ); } } add_action( 'wp_footer', 'aeroSyncScript' ); function aeroSyncScript() { global $post; if($post){$aeroCPT = get_post_meta($post->ID, '_aero_cpt', true);} //If there's a value for aero_cpt, we add this script to the footer in the //actual wordpress site. This will only be shown if the user is an admin if(isset($aeroCPT) && current_user_can( 'manage_options' )){ ?> $token, "status" => $status, "sync_time" => $sync_time, "auto_sync" => $auto_sync, "post_status" => $record_post_status, "mapped_fields" => $mapped_fields, "mapped_post_type" => $mapped_post_type ? $mapped_post_type : "" ) )); } add_action("wp_ajax_aeropageGetRegisteredPostTypes", "aeropageGetRegisteredPostTypes"); function aeropageGetRegisteredPostTypes(){ aeropageRefererChecker(); aeropageCheckUserCapabilities(); $registeredPostTypes = get_post_types(array(), "objects"); die(json_encode(array( "status" => "success", "post_types" => $registeredPostTypes ))); } // make sure all the custom post types are registered. add_action( 'init', 'aeroRegisterTypes' ); function aeroRegisterTypes() { try{ $flush = null; $aeroPosts = get_posts(['post_type' => 'aero-template','post_status' => 'private','numberposts' => -1]); foreach ($aeroPosts as $template) { $title = $template->post_title; $slug = $template->post_name; // eg Headphones $mapped_post_type = get_post_meta($template->ID, "aero_mapped_post_type", true); //Prevent mapped post types from being registered. if (!post_type_exists($slug) && $slug && !$mapped_post_type) { // echo "SLUG: "; // echo $slug; $flush = true; register_post_type( "$slug", //airconnex_templates array( "labels" => array( "name"=> _("$title"), "singular_name" => _("$title") ), 'hierarchical' => true, "has_archive" => false, "rewrite" => array( "slug" => "$slug" ), // my custom slug "supports" => array( "title","editor","thumbnail" ), // editor page settings "show_in_rest" => true, // gutenberg "description" => "$title", "public" => true, // All the relevant settings below inherit from this setting "publicly_queryable" => true, // When a parse_request() search is conducted, should it be included? "show_ui" => true, // Should the primary admin menu be displayed? "show_in_nav_menus" => true, // Should it show up in Appearance > Menus? "show_in_menu" => true, // This inherits from show_ui, and determines *where* it should be displayed in the admin "show_in_admin_bar" => true, // Should it show up in the toolbar when a user is logged in? 'taxonomies' => array('category', 'post_tag'), // add taxonomies //,'post_tag' ) ); } //Register the post meta for the posts $synced_fields = get_post_meta($template->ID, "aero_sync_fields", true); if($synced_fields){ //Add the cpt meta key and the record ID meta key register_post_meta($slug, "_aero_cpt", array("show_in_rest" => true, "description" => "Custom post type ID")); register_post_meta($slug, "_aero_id", array("show_in_rest" => true, "description" => "Airtable Record ID")); foreach($synced_fields as $key => $value){ //Register the fields register_post_meta($slug, "aero_$key", array("show_in_rest" => true, "description" => $key)); } } } if ($flush){flush_rewrite_rules();} }catch(Exception $e){ echo esc_attr($e->getMessage()); } } // add custom types to category / archive page queries // function aeroPostTypesInQuery ($query) // { // if(empty($query->query['post_type']) or $query->query['post_type'] === 'post') // { // $defaultTypes = array ('post', 'page'); // $aeroTypes = array(); // $aeroPosts = get_posts(['post_type' => 'aero-template','post_status' => 'private','numberposts' => -1]); // foreach ($aeroPosts as $template) // { // $slug = $template->post_name; // eg Headphone // $aeroTypes[] = $slug; // add to array // } // $postArray = array_merge($defaultTypes,$aeroTypes); // $query->set('post_type', $postArray); // } // } // add_action('pre_get_posts', 'aeroPostTypesInQuery'); add_action("wp_ajax_aeropageEdit", "aeropageEdit"); function aeropageEdit() // called by ajax, adds the cpt { aeropageRefererChecker(); aeropageCheckUserCapabilities(); $post_id = null; if($_POST['id']) { $post_id = intval($_POST['id']); } // can be passed an id (edit) or empty to create new // wordpress will automatically increment the slug if its already used. $template_post = array( 'ID' => $post_id, 'post_title' => sanitize_text_field($_POST['title']), 'post_name' => sanitize_text_field($_POST['slug']), 'post_excerpt'=> sanitize_text_field($_POST['dynamic']), 'post_type' => 'aero-template', 'post_status' => 'private' ); $id = wp_insert_post($template_post); if ($id) { $auto_sync = false; if($_POST['auto_sync'] === "true"){ $auto_sync = true; } update_post_meta ($id,'aero_token', sanitize_text_field($_POST['token'])); update_post_meta ($id,'aero_auto_sync', $auto_sync); // update_post_meta ($id,'aero_connection', sanitize_text_field($_POST["app"])."/".sanitize_text_field($_POST["table"])."/".sanitize_text_field($_POST["view"])); update_post_meta ($id, 'aero_post_status', sanitize_text_field($_POST["post_status"])); $post_type = sanitize_text_field($_POST["post_type"]); //If there is a post type, we will save it to the post meta update_post_meta($id, 'aero_mapped_post_type', $post_type); if($post_type){ update_post_meta($id, 'aero_mapped_post_meta_fields', json_decode(stripslashes($_POST["mapped_fields"]))); } // Removed this since we are already doing the sync process in the frontend when saving the settings and when manually syncing... // This is so that it will prevent 504 issues when syncing and updating posts in the database. // $response = aeropageSyncPosts($id); // if($response['status'] === "error"){ // header('Status: 503 Service Temporarily Unavailable'); // die(json_encode(array("status" => "error", "message" => $response["message"]))); // }else{ // die(json_encode(array("status" => "success", "post_id" => $id, "response" => $response))); // } die(json_encode(array("status" => "success", "post_id" => $id))); } die(json_encode(array("status" => "error", "message" => "No ID found in the database."))); } // function aeropageTokenCheck() // { // $token = $_POST["token"]; // aeropageTokenApiCall($token); // } function aeropageTokenApiCall($token) { $split = explode('-', $token); if(count($split) > 1){ $api_url = "https://api.aeropage.io/api/v5/tools/connector/$split[1]"; }else{ $api_url = "https://tools.aeropage.io/api/token/$token/"; } $response = wp_remote_get($api_url, array("timeout" => 30)); $result = json_decode( wp_remote_retrieve_body($response), true); return $result; } function aeropageModCheckApiCall($token) { $split = explode('-', $token); if(count($split) > 1){ $api_url = "https://api.aeropage.io/api/v5/modtime/check/$split[1]"; }else{ $api_url = "https://tools.aeropage.io/api/modcheck/$token/"; } $result = json_decode(wp_remote_retrieve_body(wp_remote_get($api_url, array("timeout" => 30))), true); return $result; } add_action("wp_ajax_aeropageDeletePost", "aeropageDeletePost"); function aeropageDeletePost() { try{ aeropageRefererChecker(); aeropageCheckUserCapabilities(); global $wpdb; $post_id = null; if($_POST['id']) { $post_id = intval($_POST['id']); } $parent = get_post($post_id); $mapped_post_type = get_post_meta($post_id, 'aero_mapped_post_type', true); $slug = $parent->post_name; //Delete all the posts for that post type $wpdb->query($wpdb->prepare( " DELETE a,b,c FROM wp_posts a LEFT JOIN wp_term_relationships b ON (a.ID = b.object_id) LEFT JOIN wp_postmeta c ON (a.ID = c.post_id) WHERE a.post_type = %s; " , $slug)); if($mapped_post_type){ $wpdb->query($wpdb->prepare( " DELETE a,b,c FROM wp_posts a LEFT JOIN wp_term_relationships b ON (a.ID = b.object_id) LEFT JOIN wp_postmeta c ON (a.ID = c.post_id AND c.meta_key = '_aero_cpt') WHERE c.meta_value = %d; " , $post_id)); } // Unregister the post type first unregister_post_type($slug); // Remove the post wp_delete_post($post_id, true); die(json_encode(array("status" => "success"))); }catch(Exception $e){ die(json_encode(array("status" => "error", "message" => $e->getMessage()))); } } add_action("wp_ajax_aeropageGetPostMetaForSelectedPostType", "aeropageGetPostMetaForSelectedPostType"); function aeropageGetPostMetaForSelectedPostType(){ $postType = $_POST["post_type"]; $postMetaKeys = get_registered_meta_keys("post", $postType); $acfFields = array(); //Get the ACF fields if (function_exists('acf_get_field_groups')) { $acf_field_group = acf_get_field_groups(array('post_type' => $postType)); foreach ($acf_field_group as $key => $value) { $acfGroupID = $value["key"]; $acf_fields = acf_get_fields($acfGroupID); if($acf_fields){ foreach ($acf_fields as $key => $field) { $acfFields[$field["name"]] = $field; } } } } die(json_encode(array( "status" => "success", "meta_keys" => array( "Registered Meta" => $postMetaKeys, "ACF" => $acfFields ) ))); } //echo aeropageSyncPosts(343); //global $wpdb; //$dump = get_posts(['meta_key' => "aero_media_atttebWsy0MAl73jq", 'numberposts' => 1]); //echo var_dump($dump); function aeropageGetChoices($value){ return $value["name"]; } add_action("wp_ajax_aeropageSyncPosts", "aeropageSyncPosts"); function aeropageSyncPosts($parentId) { $isAjax = false; try{ if(!$parentId) { $isAjax = true; $parentId = intval($_POST["id"]); //If ajax, check for nonce. aeropageRefererChecker(); aeropageCheckUserCapabilities(); } if(!$parentId) { die(json_encode(array("status" => "error", "message" => "No parent ID was passed."))); } global $wpdb; $parent = get_post($parentId); $token = get_post_meta($parentId,'aero_token',true); $record_post_status = get_post_meta($parentId, 'aero_post_status', true); $mapped_fields = get_post_meta($parentId, 'aero_mapped_post_meta_fields', true); $mapped_post_type = get_post_meta($parentId, 'aero_mapped_post_type', true); $callFromBackend = false; if($_POST["noCall"] != "1"){ $apiData = aeropageTokenApiCall($token); $callFromBackend = true; }else{ //Use the data from the frontend //it has the same data structure from the tools response //except that the records are in batches of 100 $apiData = json_decode(wp_unslash($_POST["apiData"]), true); } $response = array(); $acf_image_fields = array(); if ( isset($apiData['status']['type']) && $apiData['status']['type'] == 'success' && $apiData['records'] ) { $response['status'] = 'success'; $response['message'] = ""; // If it's not the first batch of sync and the api call did not come from the backend // We will just append the message so that we can still see the whole sync if($_POST["firstBatch"] == 0 && !$callFromBackend){ $response['message'] = get_post_meta ($parentId,'aero_sync_message', true); } update_post_meta ($parentId,'aero_sync_status','success'); $sync_time = time(); update_post_meta ($parentId,'aero_sync_time', $sync_time); // trash posts // fields are indexed numerically - iterate to create an array of types and sanitize $fieldData = array(); $fieldTypeByName = array(); $postContentFieldNames = array(); foreach ($apiData['fields'] as $key=>$datafield) { $fld_id = sanitize_text_field($datafield['id']); $fld_name = sanitize_text_field($datafield['name']); $fld_type = sanitize_text_field($datafield['type']); $fieldData[$fld_id] = $datafield; //add the whole object $fieldData[$fld_id]['id'] = $fld_id; $fieldData[$fld_id]['name'] = $fld_name; $fieldData[$fld_id]['type'] = $fld_type; $fieldTypeByName[$fld_name] = $fld_type; if(is_array($apiData["status"]["content"]) && in_array($fld_id, $apiData["status"]["content"])){ $postContentFieldNames[] = $fld_name; } // } //Add the choices to ACF and then retrive the fields that are image type if($mapped_fields){ $typesWithChoices = array("select", "radio", "checkbox"); foreach($mapped_fields as $key => $value){ //Check if image type if($value->metaValues->type == "image" || $value->metaValues->type == "file" ){ $acf_image_fields[$value->airtableField] = $value; } if(!in_array($value->metaValues->type, $typesWithChoices)) continue; // $airtableField = $value->airtableField; $fieldDta = null; $label = $value->metaValues->label; foreach($apiData["fields"] as $key => $field){ if($field["name"] == $airtableField){ $fieldDta = $field; } } if($fieldDta && is_array($fieldDta["options"]["choices"])){ $choices = array_map("aeropageGetChoices", $fieldDta["options"]["choices"]); if(function_exists("acf_get_field")){ $ACFField = acf_get_field($value->metaValues->key, true); foreach ($choices as $key => $choice) { $ACFField["choices"][$choice] = $choice; } acf_update_field($ACFField); $response['message'] .= "Choices for ACF Field $label updated using $airtableField Airtable field choices.
"; } } } $response["acf_media_fields"] = $acf_image_fields; } // echo "
";
      // print_r($postContentFieldNames);
      // echo "
"; update_post_meta ($parentId,'aero_sync_fields', $fieldTypeByName); // add to the parent cpt //If there is a mapped post type, we use that post type instead of the parent $post_type = $mapped_post_type ? $mapped_post_type : $parent->post_name; $dynamic = $parent->post_excerpt; //record_id or name // ACF FUNCTION - ON HOLD PENDING ACF UPDATES //$response['message'] .= aeropageACF($parentId,$post_type,$apiData['fields']); // CATEGORY MAPPING -------------------------------------------------- if (isset($apiData['status']['categories'])) { $categoryArray = array(); // build one array with all categories foreach ($apiData['status']['categories'] as $categoryFieldID) { $categoryFieldName = $fieldData[$categoryFieldID]['name']; $response['message'] .= " categories field is $categoryFieldName
"; $categoryChoices = $fieldData[$categoryFieldID]['options']['choices']; // create a parent category using the field name ----------- $parentTerm = get_term_by('name', $categoryFieldName, 'category'); // does a term with this name exist $parentTermID = $parentTerm->term_id; $parentCategoryMetaKey = "aero_category_$categoryFieldID"; if (is_int($parentTermID)) // if yes, update the choice with the existing term { update_post_meta($parentId,$parentCategoryMetaKey, $parentTermID); } else // if no, check if the choice had a category but was renamed { $parentTermID = get_post_meta ($parentId,$parentCategoryMetaKey); } if (is_int($parentTermID)) // if yes, rename the existing category { wp_update_term($parentTermID,'category', array('name' => $categoryFieldName )); } else // else create a new category { $term = wp_insert_term($categoryFieldName,'category'); if(is_array($term)){ $parentTermID = $term['term_id']; update_post_meta($parentId,$parentCategoryMetaKey, $parentTermID); } } foreach ($categoryChoices as $key => $choice) { if (isset($choice['id']) && isset($choice['name'])) { $choiceID = $choice['id']; $choiceName = $choice['name']; $choiceName = trim($choiceName);// get rid of spaces! //$response['message'] .= "category $choiceID has a name : $choiceName
"; if (strlen($choiceName > 0 )) { $response['message'] .= "category $choiceID is : $choiceName
"; $term = get_term_by('name', $choiceName, 'category'); // does a term with this name exist $termID = $term->term_id; if (is_int($termID)) // if yes, update the choice with the existing term { update_post_meta($parentId,"aero_terms_$choiceID", $termID); } else // if no, check if the choice had a category but was renamed { $termID = get_post_meta ($parentId,"aero_terms_$choiceID"); } if (is_int($termID)) // if yes, rename the existing category { wp_update_term($termID,'category', array('name' => $choiceName,'parent' => $parentTermID )); } else // else create a new category { $term = wp_insert_term($choiceName,'category',array('name' => $choiceName,'parent' => $parentTermID )); $termID = $term['term_id']; update_post_meta($parentId,"aero_terms_$choiceID", $termID); } if (is_int($termID)) { $categoryArray[$choiceName] = $termID; // build the array for reference in posts loop } unset($termID); } } // end if choices are set. } // end foreach choices } // end foreach categories } //end if categories ------------------------------------------------------------------ //execute only on first batch or when in hourly sync. if($_POST["firstBatch"] == 1 || $callFromBackend){ $trash = " UPDATE $wpdb->posts p INNER JOIN $wpdb->postmeta pm ON (p.ID = pm.post_id AND pm.meta_key = '_aero_cpt') SET p.post_status = 'trash' WHERE pm.meta_value = %d"; $results = $wpdb->get_results($wpdb->prepare($trash, $parentId)); } $count = 1; //Media array of the records to be downloaded. $response["media"] = array(); foreach ($apiData['records'] as $record) { $record_id = sanitize_text_field($record['id']); $record_name = sanitize_text_field($record['name']); $record_slug = sanitize_text_field($record['slug']); if ($dynamic !== 'name') { $record_slug = $record_id; } // find if theres a trashed post with this record id already $existing = get_posts([ 'post_type'=> $post_type, 'post_status' => 'trash', 'numberposts' => 1, 'meta_key' => '_aero_id', 'meta_value' => $record_id ]); $post_status = $record_post_status; //If there's a post, use that post ID otherwise just left it empty if ($existing){ $existing_id = $existing[0]->ID; // $post_status = "publish"; $post_status = $record_post_status; //We set the existing post content so that it won't be overwritten when saving/updating post $existing_post_content = $existing[0]->post_content; }else{ $existing_id = ""; $existing_post_content = ""; } if (strlen($record['post_title']) > 0) { $post_title = sanitize_text_field($record['post_title']); $post_title_msg = "
--> adding custom post_title as $post_title."; } else { $post_title = $record_name; $post_title_msg = "
--> no custom post title."; } if (strlen($record['post_excerpt']) > 0) { $post_excerpt = sanitize_text_field($record['post_excerpt']); $post_excerpt_msg = "
--> adding custom post_excerpt as $post_excerpt."; } else { $post_excerpt = $record_name; $post_excerpt_msg = "
--> no custom post excerpt."; } $record_post = array( 'ID' => $existing_id, 'post_title' => $post_title, 'post_excerpt' => $post_excerpt, 'post_name' => $record_slug, 'post_parent' => '', 'post_type' => $post_type, 'post_status' => $post_status, 'post_content' => $existing_post_content ); //If there is a post content, then we will set the post content to the value of these fields. if(count($postContentFieldNames) > 0){ //Reset the post content $record_post['post_content'] = ""; $post_content = "
--> Post Content added."; //Concatenate all the post content fields in the records foreach($postContentFieldNames as $key => $field){ $record_post["post_content"] .= html_entity_decode($record["fields"][$field]) . " "; $post_content .= "
------> Added Content for field $field"; } } $record_post_id = wp_insert_post($record_post); $count++; update_post_meta ($record_post_id, '_aero_cpt', $parentId); update_post_meta ($record_post_id, '_aero_id', $record_id); if ($existing) { $response['message'] .= "
record $record_id : $record_name already exists as $record_post_id and is being updated.".$post_title_msg.$post_excerpt_msg.$post_content; } else { $response['message'] .= "
record $record_id : $record_name has been created as $record_post_id.".$post_title_msg.$post_excerpt_msg.$post_content; } foreach ($record['fields'] as $key=>$value) { $type = $fieldTypeByName[$key]; // get the type if ($type == 'select_multiple' or $type == 'lookup_text_short' or $type == 'linked' ) { $value = implode(',',$value); // implode array into string before we add it $response['message'] .= "
---> field $key array to csv is $value."; } if (substr($type,0,11) == 'attachment_' ) { $value = sanitize_url($value[0]['url']); } if ($value) { $value = sanitize_text_field($value); update_post_meta ($record_post_id, "aero_$key", $value); $response['message'] .= "
---> field $key of type $type has been added."; } //for ACF --> update_field using the field key //$field_id = $fieldIDByName[$key]; // get the field id //$acf_field_key = 'field_'.$parentId.$field_id; //update_field( $acf_field_key, $value, $record_post_id ); } // end foreach field if($mapped_fields){ //For post meta and ACF foreach ($mapped_fields as $key => $mappedFieldData) { $value = $record['fields'][$mappedFieldData->airtableField]; $type = $fieldTypeByName[$mappedFieldData->airtableField]; if (($type == 'select_multiple' or $type == 'lookup_text_short' or $type == 'linked') && $value) { $value = implode(',',$value); // implode array into string before we add it } if (substr(str_replace("lookup_", "", $type) ,0,11) == 'attachment_' && $value) { $value = sanitize_url($value[0]['url']); } if(is_string($value)){ $value = html_entity_decode($value); } //Update ACF field if($mappedFieldData->metaValues->key && function_exists("update_field")){ update_field($mappedFieldData->metaValues->key, $value, $record_post_id); } update_post_meta($record_post_id, $key, $value); $response['message'] .= "
-----> Updated custom post field $key."; } } // POST MEDIA (ARRAY) DOWNLOAD if (is_array($apiData['status']['media'])) { $check = true; if ( defined( 'DOING_CRON' ) ) { $check = false; } $count = 0; $mediaArray = array(); foreach ($apiData['status']['media'] as $mediaFieldID) { $mediaFieldName = $fieldData[$mediaFieldID]['name']; $mediaFieldType = $fieldData[$mediaFieldID]['type']; $mediaFieldValue = $record['fields'][$mediaFieldName]; $response['message'] .= "
--> media field is $mediaFieldName "; foreach ($mediaFieldValue as $key => $mediaObject) { $response['message'] .= "
------> media $mediaFieldName $key is being checked... "; //Check if media needs to be downloaded $mediaResponse = aeropage_media_downloader($mediaObject,$record_post_id, $count, $check, $mediaFieldName); $mediaPostid = $mediaResponse[0]; $mediaPostMsg = $mediaResponse[1]; $mediaToBeDownloaded = $mediaResponse[3]; //If media needs to be downloaded we add it to the media array if($mediaToBeDownloaded){ $response["media"][] = $mediaToBeDownloaded; } if(count($acf_image_fields) > 0 && $mediaPostid){ //Check which acf field it is in $acfValues = $acf_image_fields[$mediaFieldName]->metaValues; if($acfValues->key && function_exists("update_field")){ update_field($acfValues->key, $mediaPostid, $record_post_id); $response['message'] .= "
-------->Updated ACF field"; } } $response['message'] .= "
-------->$mediaPostMsg"; $count++; } } // end foreach media field } elseif (strlen($record['post_image']) > 0) // this was using url, superceded by the above { $image_value = sanitize_url($record['post_image']); $thumbnail_id = get_post_meta( $record_post_id, '_thumbnail_id', true ); // check if this post already has thumbnails... if (!$thumbnail_id) // if we dont already have the thumb for this post { $response['message'] .= "
--> There is a post_image, but no thumbnail found. downloading now."; $thumbnail_id = aeropage_external_image($image_value,$record_post_id,$record_name); //Set the attachment as featured image. delete_post_meta( $record_post_id, '_thumbnail_id' ); add_post_meta( $record_post_id , '_thumbnail_id' , $thumbnail_id, true ); } unset($thumbnail_id); } //---------------------- // CATEGORY ASSIGNMENTS -------- if (is_array($categoryArray) && count($categoryArray) > 0) { $postCategories = array(); //make an array foreach ($apiData['status']['categories'] as $categoryFieldID) { $categoryFieldName = $fieldData[$categoryFieldID]['name']; $categoryValue = $record['fields'][$categoryFieldName]; // get the csv if (is_array($categoryValue)) // already an array { $categoryValueArray = $categoryValue; } else { $categoryValueArray = explode(',',$categoryValue); // comma sep, explode to array } if (count($categoryValueArray) == 0 && strlen($categoryValue) > 0 ) // still not array and string { $categoryValueArray = array($categoryValue); // } foreach ($categoryValueArray as $categoryName) { $categoryName = trim($categoryName);// get rid of spaces! $categoryTermId = $categoryArray[$categoryName]; // get the term id if (is_int($categoryTermId)){$postCategories[] = $categoryTermId;} // add to array if valid } //end foreach category if (is_array($postCategories)){ $response['message'] .= "
-----> Post Categories | $categoryFieldName : $categoryName"; wp_set_post_categories($record_post_id,$postCategories); } // set the post categories } // end foreach category field } // end if categories ---------- //---------- TAGS ASSIGNMENT -------------------------------- if (isset($apiData['status']['tags'])){ $postTags = array(); //make an array foreach ($apiData['status']['tags'] as $tagsFieldID){ $tagFieldName = $fieldData[$tagsFieldID]['name']; $tagValue = $record['fields'][$tagFieldName]; // get the value if (is_array($tagValue)){ // already an array merge it with the current elements of the array $postTags = array_merge($postTags, $tagValue); }else{ //Is a string so explode and add to array $postTags = array_merge($postTags, explode(',',$tagValue)); } } wp_set_post_tags($record_post_id, $postTags, false); }else{ //If it is not set, just set it to empty array to reset the tags. wp_set_post_tags($record_post_id, array(), false); } } //----------------------------------------------------------- // end foreach record update_post_meta ($parentId,'aero_sync_message', trimStrings($response['message'])); update_post_meta ($parentId,'aero_page_id', sanitize_text_field($apiData['status']['id'])); //If there's a website ID in the data, save it. if($apiData['status']['websiteID']){ update_post_meta ($parentId,'aero_website_id', sanitize_text_field($apiData['status']['websiteID'])); } update_post_meta ($parentId,'aero_connection', sanitize_text_field($apiData['status']["app"])."/".sanitize_text_field($apiData['status']["table"])."/".sanitize_text_field($apiData['status']["view"])); } else // some problem with api { $response['status'] = 'error'; update_post_meta ($parentId,'aero_sync_status','error'); $message = sanitize_text_field($apiData['message']); update_post_meta ($parentId,'aero_sync_message',$message); $response['message'] = $message; } //If doing AJAX if($isAjax) { $response["sync_time"] = $sync_time; $response["check"] = !$_POST["firstBatch"] && !$callFromBackend; $response["trash_check"] = $_POST["firstBatch"] == 1 || $callFromBackend; $response['$_POST["firstBatch"]'] = $_POST["firstBatch"]; $response['$callFromBackend'] = $callFromBackend; $response["results"] = $results; die(json_encode($response)); } else{ return $response; } }catch(Exception $e){ if($isAjax){ http_response_code(500); echo json_encode(array("status" => "error", "message" => $e->getMessage())); die(0); }else{ echo $e->getMessage(); } }catch (Error $e) { if($isAjax){ http_response_code(500); echo json_encode(array("status" => "error", "message" => $e->getMessage())); die(0); }else{ echo $e->getMessage(); } }catch (Throwable $e) { if($isAjax){ http_response_code(500); echo json_encode(array("status" => "error", "message" => $e->getMessage())); die(0); }else{ echo $e->getMessage(); } } } // end function /** * Trims a string to ensure it is less than 1MB. * * @param string $string The input string. * @return string The trimmed string if it exceeds 1MB, otherwise the original string. */ function trimStrings($string) { // Define the 1MB size limit // $maxSize = 1048576; // 1MB in bytes $maxSize = 900000; // Around 900KB in bytes // Check the size of the string in bytes $stringSize = strlen($string); // If the string exceeds the 1MB size limit, truncate it if ($stringSize > $maxSize) { // Truncate the string to the maximum allowed size $string = substr($string, 0, $maxSize); } return $string; } function aeropageCheckUserCapabilities(){ //Only allow for editors and above to perform this action. //This is to prevent unauthorized access to the and other actions. if(current_user_can("edit_pages") == false){ throw new Exception("You do not have permission to perform this action."); } } add_action("wp_ajax_aeropageMediaDownload", "aeropageMediaDownload"); function aeropageMediaDownload() { try{ aeropageRefererChecker(); aeropageCheckUserCapabilities(); $media = json_decode(stripslashes($_POST["media"]), true); if(is_array($media)){ $response = aeropage_media_downloader($media["media"], $media["record_post_id"], $media["field_index"]); $mediaPostid = $mediaResponse[0]; $mediaFieldName = $media["field_name"]; $acf_image_fields = (array) json_decode(stripslashes($_POST["acf_image_fields"])); if(count($acf_image_fields) > 0 && $mediaPostid){ //Check which acf field it is in $acfValues = $acf_image_fields[$mediaFieldName]->metaValues; if($acfValues->key && function_exists("update_field")){ update_field($acfValues->key, $mediaPostid, $media["record_post_id"]); } } die(json_encode(array("status" => "success", "message" => $response[1]))); }else{ throw new Exception("Invalid data type received by the request handler."); } }catch(Exception $e){ header('Status: 503 Service Temporarily Unavailable'); die(json_encode(array("status" => "error", "message" => $e->getMessage()))); } } function aeropageGetSupportedMimeTypes() { //Supported MIME types for Airtable attachments $supportedAirtableMimeTypes = [ // Images 'image/jpeg', 'image/png', 'image/gif', 'image/bmp', 'image/svg+xml', 'image/webp', // PDF 'application/pdf', // Word Documents 'application/msword', 'application/vnd.openxmlformats-officedocument.wordprocessingml.document', // Excel Spreadsheets 'application/vnd.ms-excel', 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', // Text and Data Files 'text/plain', 'text/csv', 'text/tab-separated-values', 'text/markdown', 'application/json', // PowerPoint 'application/vnd.ms-powerpoint', 'application/vnd.openxmlformats-officedocument.presentationml.presentation', // Audio 'audio/mpeg', 'audio/wav', 'audio/ogg', 'audio/mp4', // Video 'video/mp4', 'video/quicktime', 'video/x-msvideo', 'video/webm', 'video/x-matroska' ]; return $supportedAirtableMimeTypes; } function aeroppageValidateMediaType($mediaType, $mimeTypes = null) { return in_array($mediaType, $mimeTypes); } function aeropageRefererChecker(){ if(!check_ajax_referer("aeropage_nonce", false)){ throw new Exception("Invalid request. Please try again."); } } function aeropageIsPathSafe($input) { // Prevent directory traversal and enforce safe characters return ( strpos($input, '..') === false && strpos($input, '/') === false && strpos($input, '\\') === false && preg_match('/^[a-zA-Z0-9_\-\.]+$/', $input) ); } // MEDIA DOWNLOADER -------------------------------------- //$mediaObject => Airtable Media structure //$parent => wordpress post ID where the image will be attached //$field_index => index of the airtable field--will be used to set the featured image //$check => flag for checking if we add the image to an array when syncing. function aeropage_media_downloader($mediaObject, $parent, $field_index, $check = NULL, $fieldName = "") { global $wpdb; $mimeTypes = aeropageGetSupportedMimeTypes(); $response = array(); $attachmentURL = sanitize_url($mediaObject['url']); $attachmentID = sanitize_text_field($mediaObject['id']); $attachmentFileType = sanitize_text_field($mediaObject['type']); $fileNameExploded = explode(".", $mediaObject['filename']); $fileTypeExploded = explode("/", $attachmentFileType); $attachmentFileName = sanitize_file_name(strtolower($fileNameExploded[0])); $response[1] = "Checking Attachment : $attachmentID
"; $uploadDir = wp_upload_dir(); $uploadFolder = $uploadDir['basedir'].'/airtable/'; $uploadPathWithAttachmentID = $uploadFolder.$attachmentID.'_'.$attachmentFileName.'.'.$fileTypeExploded[1]; //$check is a flag that tells us if we are just adding to media array, if true we will add the media object to an array along with the record post ID //This will be used for the media download in the second step. //Otherwise if we are not checking the image if (!file_exists($uploadFolder)) wp_mkdir_p($uploadFolder); // if no folder, create. // CHECK IF FILE NEEDS TO BE DOWNLOADED if (!file_exists($uploadPathWithAttachmentID)) // file doesnt already exist in folder, download it { if($check){ $response[1] .= "Adding media for ".$parent."to be downloaded later."; $response[3] = array( "media" => $mediaObject, "record_post_id" => $parent, "field_index" => $field_index, "field_name" => $fieldName ); }else{ // ----------- UPLOAD VALUES VALIDATION ------------------ //Check for any traversal characters in the filename and attachment ID if(aeropageIsPathSafe($attachmentFileName) == false || aeropageIsPathSafe($attachmentID) == false){ throw new Exception("The file name or attachment ID contains invalid characters. Please check the file name and try again."); } //validate the $mediaObject['type'] against the list of supported mime types as well if(aeroppageValidateMediaType($mediaObject['type'], $mimeTypes) == false){ throw new Exception("The file type is not supported. Please check the file type and try again."); } //------------------------------------------------ //Fetch the file. $checkURL = wp_remote_get( $attachmentURL ); // check the url to make sure its valid if (! is_wp_error( $checkURL ) ) { // Validate Content-Type since the URL doesn't have any file extension in it. $content_type = wp_remote_retrieve_header($checkURL, 'content-type'); // get the content type of the file if (!in_array($content_type, $mimeTypes)) { throw new Exception("The file type is not supported. Please check the file type and try again."); } $response[1] .= "File doesnt exist and url is valid. Downloading."; $downloadedFile = wp_remote_retrieve_body( $checkURL ); // get the image file $fp = fopen($uploadPathWithAttachmentID , 'w' ); // set the path to save fwrite( $fp, $downloadedFile ); // write the contents to the file fclose( $fp ); // close the path } } }else{ $response[1] .= "File already exists in the folder."; $downloadedFile = true; // indicate that the file is already downloaded. } // CHECK IF THIS ATTACHMENT WAS ALREADY PROCESSED ON A PREVIOUS RUN $existingMediaCheck = get_posts(['meta_key' => "aero_media_$attachmentID",'post_parent' => $parent,'post_type' => 'attachment', 'numberposts' => 1]); $existingMedia = $existingMediaCheck[0]->ID; // check for an attachment with meta_key matching this if ($existingMedia) // attachment exists, skip this { $response[0] = $existingMedia; $response[1] .= " > Attachment Post already exists as : $existingMedia. Skipping."; } elseif ($downloadedFile) // no attachment exists and we have the file. If there's no downloaded file, then we just skip this code block. { $attachment = array( 'post_title' => $attachmentFileName, 'post_mime_type' => $attachmentFileType, 'post_status' => 'inherit' ); $mediaPostid = wp_insert_attachment( $attachment, $uploadPathWithAttachmentID, $parent ); //create / attach file to its parent post if (is_int($mediaPostid)) { require_once ABSPATH . 'wp-admin/includes/image.php'; //require for wp_generate_attachment_metadata $attachmentMeta = wp_generate_attachment_metadata( $mediaPostid, $uploadPathWithAttachmentID); wp_update_attachment_metadata( $mediaPostid, $attachmentMeta ); update_post_meta($mediaPostid,"aero_media_$attachmentID",'done');// mark media with attachment id. $response[0] = $mediaPostid; $response[1] .= " > Attachment Post was inserted as : $mediaPostid"; } else { $response[1] .= "The file was downloaded, but a problem occurred with wp_insert_attachment."; } } //If the field is the first index in the media/attachments and there is a mediaID, we will add the media as a feature image if (is_int($response[0]) and $field_index == 0) //set first as thumbnail { $response[1] .= "
----------->Setting as thumbnail -> $response[0]"; update_post_meta( $parent , '_thumbnail_id' , $response[0], true ); } return $response; } // IMAGE DOWNLOADER (DEPRECATED) -------------------------------------- function aeropage_external_image($ext_url,$parent,$record_name) { global $wpdb; //$ext_url -- the external url //$parent -- the parent post to attach to $upload_dir = wp_upload_dir(); $upload_folder = $upload_dir['basedir'].'/aeropage/'; if(!file_exists($upload_folder)) wp_mkdir_p($upload_folder); $ext_img = wp_remote_get( $ext_url ); // check the url to make sure its valid //$data['headers']['content-disposition'] $content_type = $ext_img['headers']['content-type']; $exploded = explode("/", $content_type); //'application/pdf' $extension = $exploded[1]; //Contains the extension i.e. "jpeg, png, etc" $image_title = $record_name; // this is already sanitized before being passed $image_filename = sanitize_file_name( $record_name.'.'.$extension); if (! is_wp_error( $ext_img ) ) { $img_content = wp_remote_retrieve_body( $ext_img ); // get the image file $fp = fopen( $upload_folder.$image_filename , 'w' ); // set the path to save fwrite( $fp, $img_content ); // write the contents to the file fclose( $fp ); // close the path $wp_filetype = wp_check_filetype( $image_filename , null ); // check the filename $attachment = array( 'post_mime_type' => $ext_img['headers']['content-type'], //We'll use the content type returned from response since this is more reliable //$wp_filetype['type'], // mimetype 'post_title' => $image_title, 'post_content' => '', 'post_status' => 'inherit' ); $image_filepath = $upload_folder.$image_filename; //require for wp_generate_attachment_metadata which generates image related meta-data also creates thumbs require_once ABSPATH . 'wp-admin/includes/image.php'; $thumbnail_id = wp_insert_attachment( $attachment, $image_filepath, $parent ); if ($thumbnail_id) { //Generate post thumbnail of different sizes. $attach_data = wp_generate_attachment_metadata( $thumbnail_id , $image_filepath); wp_update_attachment_metadata( $thumbnail_id, $attach_data ); return $thumbnail_id; } } } // end function // ADVANCED CUSTOM FIELDS (SUSPENDED) -------------------------------------- function aeropageACF($parentId, $post_type, $fields) { global $wpdb; if( function_exists('acf_add_local_field_group') ): //Note: Field Groups and Fields registered via code will not be visible/editable via the �Edit Field Groups� admin page. $response = "
ACF CONDITION IS EXECUTING for $post_type ($parentId), $acf_field_group_name"; $acf_field_group_name = 'group_'.$parentId; $acf_existing = get_posts([ 'post_type'=> 'acf-field-group', 'name' => "$acf_field_group_name", 'post_status' => 'publish' ]); if ($acf_existing){$acf_existing_id = $acf_existing[0]->ID;} $count = count($acf_existing); if ($acf_existing_id){$response .= "
-- $count group post $acf_existing_id was found by post name $acf_field_group_name";} $acf_field_group_content = array ( 'location' => array ( array ( array ( 'param' => 'post_type', 'operator' => '==', 'value' => $post_type, ), ), ), 'menu_order' => 0, 'position' => 'normal', 'style' => 'default', 'label_placement' => 'top', 'instruction_placement' => 'label', 'hide_on_screen' => '', 'description' => 'Automatically added by Aeropage', ); $acf_field_group_content = serialize($acf_field_group_content); $acf_field_group = array( 'ID' => $acf_existing_id, 'post_title' => $post_type, 'post_content' => $acf_field_group_content, 'post_excerpt' => sanitize_title( $post_type ), 'post_name' => $acf_field_group_name, 'post_date' => date( 'Y-m-d H:i:s' ), 'comment_status' => 'closed', 'post_status' => 'publish', 'post_type' => 'acf-field-group', ); $acf_field_group_id = wp_insert_post( $acf_field_group ); if ($acf_existing_id){$response .= "
-- group post $acf_existing_id was updated for group_$parentId";} else{$response .= "
-- group was created for group_$parentId";} //--- register each field --------------------------------------------- $order = 0; foreach ($fields as $key=>$value) { extract($value, EXTR_PREFIX_ALL, "field"); $acf_field_key = 'field_'.$parentId.$field_id; $response .= "
-- field $field_id $field_name of type $field_type is being registered as $acf_field_key ..."; // -- mapping aeropage types to acf types --- $acf_type = 'text'; // failover to text if ($field_type == 'number'){$acf_type = 'number';} if ($field_type == 'select_single') { $acf_type = 'select'; $choices = array(); foreach ($field_options['choices'] as $key=>$value) { $choice = $value['name']; $choices[$choice]= $choice; }; //$default_value = $field_options['choices'][0]; } $acf_field_existing = get_posts([ 'post_type'=> 'acf-field', 'name' => $acf_field_key, 'post_status' => 'publish' ]); if ($acf_field_existing){$acf_field_existing_id = $acf_field_existing[0]->ID;} $acf_field_content = array( 'key' => $acf_field_key, 'label' => 'My Field Title', 'name' => 'my_field_name', 'type' => $acf_type, 'instructions' => '', 'required' => 0, 'conditional_logic' => 0, 'wrapper' => array( 'width' => '', 'class' => '', 'id' => '', ), 'message' => '', 'default_value' => 0, 'ui' => 1, 'ui_on_text' => '', 'ui_off_text' => '', ); if ($choices){$acf_field_content['choices'] = $choices;} $acf_field_content = serialize($acf_field_content); $acf_field_post = array( 'ID' => $acf_field_existing_id, 'post_title' => $field_name, 'post_content' => $acf_field_content, 'post_excerpt' => sanitize_title( $field_name ), 'post_name' => $acf_field_key, 'post_date' => date( 'Y-m-d H:i:s' ), 'comment_status' => 'closed', 'post_status' => 'publish', 'post_type' => 'acf-field', 'post_parent' => $acf_field_group_id, 'menu_order' => $order, ); $acf_field_post_id = wp_insert_post( $acf_field_post ); if ($acf_field_existing_id){$response .= "
-- existing field was updated";} else{$response .= "
-- new field was created";} $order++; } //---- unset($acf_existing_id); endif; return $response; //------------------------------------------------------------ } // WOOCOMMERCE (FUTURE) -------------------------------------- /* $product = new WC_Product_Simple(); $product->set_name( 'Photo: ' . get_the_title( $image_id ) ); $product->set_status( 'publish' ); $product->set_catalog_visibility( 'visible' ); $product->set_price( 19.99 ); $product->set_regular_price( 19.99 ); $product->set_sold_individually( true ); $product->set_image_id( $image_id ); $product->set_downloadable( true ); $product->set_virtual( true ); $src_img = wp_get_attachment_image_src( $image_id, 'full' ); $file_url = reset( $src_img ); $file_md5 = md5( $file_url ); $download = new WC_Product_Download(); $download->set_name( get_the_title( $image_id ) ); $download->set_id( $file_md5 ); $download->set_file( $file_url ); $downloads[$file_md5] = $download; $product->set_downloads( $downloads ); */