/** * Serialize an entity post. * * @param array $entity The entity post or the entity post id. * * @return array mixed The entity data array. */ function wl_serialize_entity($entity) { $entity = is_numeric($entity) ? get_post($entity) : $entity; $type = wl_entity_type_taxonomy_get_type($entity->ID); $images = wl_get_image_urls($entity->ID); return array('id' => wl_get_entity_uri($entity->ID), 'label' => $entity->post_title, 'description' => wp_strip_all_tags($entity->post_content), 'sameAs' => wl_schema_get_value($entity->ID, 'sameAs'), 'mainType' => str_replace('wl-', '', $type['css_class']), 'types' => wl_get_entity_rdf_types($entity->ID), 'images' => $images); }
function testFindByURI() { $entity_post_id = wl_create_post('', 'test_entity', 'Test Entity', 'draft', 'entity'); $entity_uri = wl_get_entity_uri($entity_post_id); wl_schema_set_value($entity_post_id, 'sameAs', 'http://example.org/entity/test_entity'); $same_as_array = wl_schema_get_value($entity_post_id, 'sameAs'); $this->assertTrue(is_array($same_as_array)); $this->assertEquals('http://example.org/entity/test_entity', $same_as_array[0]); wl_schema_set_value($entity_post_id, 'sameAs', array('http://example.org/entity/test_entity', 'http://data.example.org/entity/test_entity')); $same_as_array = wl_schema_get_value($entity_post_id, 'sameAs'); $this->assertTrue(is_array($same_as_array)); $this->assertEquals('http://example.org/entity/test_entity', $same_as_array[0]); $this->assertEquals('http://data.example.org/entity/test_entity', $same_as_array[1]); $post = wl_get_entity_post_by_uri('http://example.org/entity/test_entity'); $this->assertNotNull($post); $post = wl_get_entity_post_by_uri('http://data.example.org/entity/test_entity'); $this->assertNotNull($post); $same_as_uri = 'http://example.org/entity/test_entity2'; $entity_post_id = wl_create_post('', 'test_entity_2', 'Test Entity 2', 'draft', 'entity'); $entity_uri = wl_get_entity_uri($entity_post_id); wl_schema_set_value($entity_post_id, 'sameAs', $same_as_uri); $same_as_array = wl_schema_get_value($entity_post_id, 'sameAs'); $this->assertTrue(is_array($same_as_array)); $this->assertEquals($same_as_uri, $same_as_array[0]); $post = wl_get_entity_post_by_uri('http://example.org/entity/test_entity'); $this->assertNotNull($post); }
/** * Displays the meta box contents (called by *add_meta_box* callback). * * @param WP_Post $post The current post. */ function wl_entities_box_content($post) { // wl_write_log( "wl_entities_box_content [ post id :: $post->ID ]" ); // Angularjs edit-post widget wrapper echo '<div id="wordlift-edit-post-outer-wrapper"></div>'; // Angularjs edit-post widget classification boxes configuration $classification_boxes = unserialize(WL_CORE_POST_CLASSIFICATION_BOXES); // Array to store all related entities ids $all_referenced_entities_ids = array(); // Add selected entities to classification_boxes foreach ($classification_boxes as $i => $box) { // Build the proper relation name $relation_name = $box['id']; // wl_write_log( "Going to related of $relation_name" ); // Get entity ids related to the current post for the given relation name (both draft and published entities) $draft_entity_ids = wl_core_get_related_entity_ids($post->ID, array('predicate' => $relation_name, 'status' => 'draft')); $publish_entity_ids = wl_core_get_related_entity_ids($post->ID, array('predicate' => $relation_name, 'status' => 'publish')); $entity_ids = array_unique(array_merge($draft_entity_ids, $publish_entity_ids)); // Store the entity ids for all the 4W $all_referenced_entities_ids = array_merge($all_referenced_entities_ids, $entity_ids); // Transform entity ids array in entity uris array array_walk($entity_ids, function (&$entity_id) { // Retrieve the entity uri for the given entity id $entity_id = wl_get_entity_uri($entity_id); }); // Enhance current box selected entities $classification_boxes[$i]['selectedEntities'] = $entity_ids; } // Json encoding for classification boxes structure $classification_boxes = json_encode($classification_boxes); // Ensure there are no repetitions of the referenced entities $all_referenced_entities_ids = array_unique($all_referenced_entities_ids); // Build the entity storage object $referenced_entities_obj = array(); foreach ($all_referenced_entities_ids as $referenced_entity) { $entity = wl_serialize_entity($referenced_entity); $referenced_entities_obj[$entity['id']] = $entity; } $referenced_entities_obj = empty($referenced_entities_obj) ? '{}' : json_encode($referenced_entities_obj); $default_thumbnail_path = WL_DEFAULT_THUMBNAIL_PATH; $dataset_uri = wl_configuration_get_redlink_dataset_uri(); echo <<<EOF <script type="text/javascript"> jQuery( function() { \tif ('undefined' == typeof window.wordlift) { \twindow.wordlift = {} \twindow.wordlift.entities = {} \t\t \t} \twindow.wordlift.classificationBoxes = {$classification_boxes}; \twindow.wordlift.entities = {$referenced_entities_obj}; \twindow.wordlift.currentPostId = {$post->ID}; \t\t\twindow.wordlift.defaultThumbnailPath = '{$default_thumbnail_path}'; \t\t\twindow.wordlift.datasetUri = '{$dataset_uri}'; }); </script> EOF; }
public function testPostsSelectionWithFilters() { // Create 2 posts and 2 entities $entity_1_id = wl_create_post('', 'entity0', 'An Entity', 'draft', 'entity'); $post_1_id = wl_create_post('', 'post1', 'A post', 'publish'); $post_2_id = wl_create_post('', 'post2', 'A post', 'publish'); // Insert relations wl_core_add_relation_instance($post_1_id, WL_WHAT_RELATION, $entity_1_id); wl_core_add_relation_instance($post_2_id, WL_WHAT_RELATION, $entity_1_id); // Set $_GET variable: this means we will perform data selection for $entity_1_id $_GET['post_id'] = $post_1_id; // Mock php://input $mock_http_raw_data = json_encode(array(wl_get_entity_uri($entity_1_id))); try { $this->_handleAjax('wordlift_related_posts', $mock_http_raw_data); } catch (WPAjaxDieContinueException $e) { } $response = json_decode($this->_last_response); $this->assertInternalType('array', $response); $this->assertCount(1, $response); $this->assertEquals('post', $response[0]->post_type); $this->assertEquals($post_2_id, $response[0]->ID); $this->assertEquals(get_edit_post_link($post_2_id, 'none'), $response[0]->link); $this->assertEquals(get_post_permalink($post_2_id), $response[0]->permalink); }
function wl_shortcode_faceted_search($atts) { $div_id = 'wordlift-faceted-entity-search-widget'; wp_enqueue_style('wordlift-faceted-search', dirname(plugin_dir_url(__FILE__)) . '/css/wordlift-faceted-entity-search-widget.min.css'); wp_enqueue_script('angularjs', dirname(plugin_dir_url(__FILE__)) . '/bower_components/angular/angular.min.js'); wp_enqueue_script('wordlift-faceted-search', dirname(plugin_dir_url(__FILE__)) . '/js/wordlift-faceted-entity-search-widget.js'); wp_localize_script('wordlift-faceted-search', 'wl_faceted_search_params', array('ajax_url' => admin_url('admin-ajax.php'), 'action' => 'wl_faceted_search', 'entity_id' => get_the_ID(), 'entity_uri' => wl_get_entity_uri(get_the_ID()), 'div_id' => $div_id, 'defaultThumbnailPath' => WL_DEFAULT_THUMBNAIL_PATH)); return '<div id="' . $div_id . '" style="width:100%"></div>'; }
/** * Add custom buttons to the buttons below the post title. * * @param string $html The current html. * @param int $post_id The post ID. * @param string $new_title Optional. New title. * @param string $new_slug Optional. New slug. * * @return The enhanced html. */ function wl_admin_permalink_html($html, $post_id, $new_title, $new_slug) { // If the post is published, add the button view on Redlink. if ('publish' == get_post_status($post_id)) { if ($uri = wl_get_entity_uri($post_id)) { $uri_esc = esc_attr(wl_get_entity_uri($post_id)); $html .= "<span id='view-post-btn'><a href='{$uri_esc}' class='button button-small' target='_blank'>" . __('View on Redlink', 'wordlift') . "</a></span>\n"; } $html .= "<span id='view-post-btn'><a href='" . WL_CONFIG_TEST_GOOGLE_RICH_SNIPPETS_URL . urlencode(get_permalink($post_id)) . "' class='button button-small' target='_blank'>" . __('Test Google Rich Snippets', 'wordlift') . "</a></span>\n"; } return $html; }
/** * Displays the content of the entity URL box (called from the *entity_url* method). * * @param WP_Post $post The post. */ function wl_entity_type_meta_boxes_content($post) { wp_nonce_field('wordlift_entity_box', 'wordlift_entity_box_nonce'); $value = wl_get_entity_uri($post->ID); echo '<label for="entity_url">' . __('entity-url-label', 'wordlift') . '</label>'; echo '<input type="text" id="entity_url" name="entity_url" placeholder="enter a URL" value="' . esc_attr($value) . '" style="width: 100%;" />'; /* $entity_types = implode( "\n", wl_get_entity_rdf_types( $post->ID ) ); echo '<label for="entity_types">' . __( 'entity-types-label', 'wordlift' ) . '</label>'; echo '<textarea style="width: 100%;" id="entity_types" name="entity_types" placeholder="Entity Types URIs">' . esc_attr( $entity_types ) . '</textarea>'; */ }
function testEntityUriWithMissingDatasetUri() { $post_id = wl_create_post('A body', 'post-1', uniqid('post', true), 'draft', 'post'); $dataset_uri = wl_configuration_get_redlink_dataset_uri(); $this->assertNotEmpty($dataset_uri); // Remove the dataset uri wl_configuration_set_redlink_dataset_uri(''); $entity_uri = wl_get_entity_uri($post_id); // Check the wl_get_entity_uri properly returns null $this->assertNull($entity_uri); // Check the are not custom meta set for the current post $this->assertEmpty(get_post_meta($post_id, WL_ENTITY_URL_META_NAME)); // Set the dataset uri again wl_configuration_set_redlink_dataset_uri($dataset_uri); }
function testContentParsing() { // Create the sample entity for testing. $this->createSampleEntity(); $content = '<span class="textannotation highlight organization disambiguated" id="urn:enhancement-16e9b0f6-e792-5b75-ffb7-ec40916d8753" itemid="http://dbpedia.org/resource/Honda" itemscope="itemscope" itemtype="organization">Honda</span> is recalling nearly 900,000 minivans for a defect that could increase fire risk.'; $matches = array(); if (0 < preg_match_all('/ itemid="([^"]+)"/im', $content, $matches, PREG_SET_ORDER)) { foreach ($matches as $match) { $item_id = $match[1]; $post = wl_get_entity_post_by_uri($item_id); $this->assertNotNull($post); $uri = wl_get_entity_uri($post->ID); $this->assertNotNull($uri); $this->assertNotEquals($item_id, $uri); } } $this->assertTrue(0 < count($matches)); }
/** * Delete the specified post from the triple store. * * @param array|int $post An array of post data */ function rl_delete_post($post) { $post_id = is_numeric($post) ? $post : $post->ID; // hide all entities that are not referenced by any published post. foreach (wl_core_get_related_entity_ids($post_id) as $entity_id) { // check if there is at least one referencing post published. $is_published = array_reduce(wl_core_get_related_post_ids($entity_id), function ($carry, $item) { $post = get_post($item); return $carry || 'publish' === $post->post_status; }); // set the entity to draft if no referencing posts are published. if (!$is_published) { wl_update_post_status($entity_id, 'draft'); } } // get the entity URI (valid also for posts) $uri_esc = wl_sparql_escape_uri(wl_get_entity_uri($post_id)); wl_write_log("rl_delete_post [ post id :: {$post_id} ][ uri esc :: {$uri_esc} ]"); // create the SPARQL statement by joining the SPARQL prefixes and deleting any known predicate. $stmt = rl_sparql_prefixes(); foreach (wl_predicates() as $predicate) { $stmt .= "DELETE { <{$uri_esc}> {$predicate} ?o . } WHERE { <{$uri_esc}> {$predicate} ?o . };\n" . "DELETE { ?s {$predicate} <{$uri_esc}> . } WHERE { ?s {$predicate} <{$uri_esc}> . };\n"; } // if the post is an entity and has exported properties, delete the related predicates. if (WL_ENTITY_TYPE_NAME === $post->post_type) { $type = wl_entity_type_taxonomy_get_type($post->ID); if (isset($type['custom_fields'])) { foreach ($type['custom_fields'] as $field => $params) { // TODO: enclose in <> only if predicate starts with http(s):// $predicate = '<' . $params['predicate'] . '>'; $stmt .= "DELETE { <{$uri_esc}> {$predicate} ?o . } WHERE { <{$uri_esc}> {$predicate} ?o . };\n"; } } } // finally execute the query. rl_execute_sparql_update_query($stmt); }
/** * Optimize and convert retrieved content to JSON. * * @used-by wl_shortcode_chord_ajax * * @param $data * @return mixed|string|void */ function wl_shortcode_chord_get_graph($data) { // Refactor the entities array in order to provide entities relevant data (uri, url, label, type, css_class). array_walk($data['entities'], function (&$item) { $post = get_post($item); // Skip non-existing posts. if (is_null($post)) { wl_write_log("wl_shortcode_chord_get_graph : post not found [ post id :: {$item} ]"); return $item; } // Get the entity taxonomy bound to this post (if there's no taxonomy, no stylesheet will be set). $term = wl_entity_type_taxonomy_get_type($item); wl_write_log("wl_shortcode_chord_get_graph [ post id :: {$post->ID} ][ term :: " . var_export($term, true) . " ]"); $entity = array('uri' => wl_get_entity_uri($item), 'url' => get_permalink($item), 'label' => $post->post_title, 'type' => $post->post_type, 'thumbnails' => wl_get_image_urls($post->ID), 'css_class' => isset($term['css_class']) ? $term['css_class'] : ''); $item = $entity; }); // Refactor the relations. array_walk($data['relations'], function (&$item) { $relation = array('s' => wl_get_entity_uri($item[0]), 'o' => wl_get_entity_uri($item[1])); $item = $relation; }); // Return the JSON representation. return $data; }
/** * Saves the values of wordlift metaboxes set in the entity editor page */ function wl_entity_metabox_save($post_id) { if (!isset($_POST['wl_metaboxes'])) { return; } // Loop over the wl_metaboxes array and save metaboxes values foreach ($_POST['wl_metaboxes'] as $meta_name => $meta_values) { // First, verify nonce is set for this meta $nonce_name = 'wordlift_' . $meta_name . '_entity_box_nonce'; $nonce_verify = 'wordlift_' . $meta_name . '_entity_box'; if (!isset($_POST[$nonce_name])) { return $post_id; } // Verify that the nonce is valid. if (!wp_verify_nonce($_POST[$nonce_name], $nonce_verify)) { return $post_id; } // Delete values before updating delete_post_meta($post_id, $meta_name); // Save the property value(s) if (isset($meta_name) && isset($meta_values) && $meta_values !== '') { // There can be one or more property values, so we force to array: if (!is_array($meta_values)) { $meta_values = array($meta_values); } foreach ($meta_values as $meta_value) { // If the meta expects an entity... $expecting_uri = wl_get_meta_type($meta_name) === WL_DATA_TYPE_URI; // ...and the user inputs an entity that is not present in the db... $absent_from_db = is_null(wl_get_entity_post_by_uri($meta_value)); // ...and that is not a external uri $name_is_uri = strpos($meta_value, 'http') === 0; if ($expecting_uri && $absent_from_db && !$name_is_uri) { // ...we create a new entity! $new_entity = wl_save_entity('', $meta_value, WL_ENTITY_TYPE_NAME, ''); // Assign type $constraints = wl_get_meta_constraints($meta_name); $type = 'http://schema.org/' . $constraints['uri_type']; wl_set_entity_main_type($new_entity->ID, $type); // TODO: directly publish the new entity // Update the value that will be saved as meta $meta_value = wl_get_entity_uri($new_entity->ID); } // TODO: use WL methods add_post_meta($post_id, $meta_name, $meta_value); } } } // Push changes on RedLink wl_linked_data_push_to_redlink($post_id); }
/** * Fills up the microdata_template with entity's values. * * @param string $entity_id An entity ID. * @param string $entity_type Entity type structure. * @param integer $recursion_level Recursion depth level in microdata compiling. Recursion depth limit is defined by WL_MAX_NUM_RECURSIONS_WHEN_PRINTING_MICRODATA constant. * * @return string The content with embedded microdata. */ function wl_content_embed_compile_microdata_template($entity_id, $entity_type, $recursion_level = 0) { global $wl_logger; if (WP_DEBUG) { $wl_logger->trace("Embedding microdata [ entity id :: {$entity_id} ][ entity type :: " . var_export($entity_type, true) . " ][ recursion level :: {$recursion_level} ]"); } $regex = '/{{(.*?)}}/'; $matches = array(); if (null === $entity_type) { return ''; } $template = $entity_type['microdata_template']; // Return empty string if template fields have not been found. if (false === preg_match_all($regex, $template, $matches, PREG_SET_ORDER)) { return ''; } foreach ($matches as $match) { $placeholder = $match[0]; $field_name = $match[1]; // Get property value. $meta_collection = wl_schema_get_value($entity_id, $field_name); // If no value is given, just remove the placeholder from the template if (null == $meta_collection) { $template = str_replace($placeholder, '', $template); continue; } // What kind of value is it? // TODO: Performance issue here: meta type retrieving should be centralized $expected_type = wl_get_meta_type($field_name); if (WP_DEBUG) { $wl_logger->trace("Embedding microdata [ placeholder :: {$placeholder} ][ field name :: {$field_name} ][ meta collection :: " . (is_array($meta_collection) ? var_export($meta_collection, true) : $meta_collection) . " ][ expected type :: {$expected_type} ]"); } foreach ($meta_collection as $field_value) { // Quick and dirty patch for #163: // - only apply to URIs, i.e. to properties pointing to another post ( $field_value should be a post ID ), // - check that $field_value is actually a number, // - check that the referenced post is published. // OR // - if the value is empty then we don't display it. if (Wordlift_Schema_Service::DATA_TYPE_URI === $expected_type && is_numeric($field_value) && 'publish' !== ($post_status = get_post_status($field_value)) || empty($field_value)) { if (WP_DEBUG) { $wl_logger->trace("Microdata refers to a non-published post [ field value :: {$field_value} ][ post status :: {$post_status} ]"); } // Remove the placeholder. $template = str_replace($placeholder, '', $template); continue; } if (Wordlift_Schema_Service::DATA_TYPE_URI == $expected_type) { // If is a numeric value we assume it is an ID referencing for an internal entity. if (is_numeric($field_value)) { // Found id, get uri. $field_value = wl_get_entity_uri($field_value); } // Just if the linked entity does exist I can go further with template compiling $nested_entity = wl_get_entity_post_by_uri($field_value); if (!is_null($nested_entity)) { $content = '<span itemid="' . esc_attr($field_value) . '">' . $nested_entity->post_title . '</span>'; $compiled_template = wl_content_embed_item_microdata($content, $field_value, $field_name, ++$recursion_level); $template = str_replace($placeholder, $compiled_template, $template); } else { $template = str_replace($placeholder, '', $template); } continue; } // Standard condition: field containing a raw value // For non visible test, schema.org dictates to use the *meta* tag. // see http://schema.org/docs/gs.html#advanced_missing $value = '<meta itemprop="' . esc_attr($field_name) . '" content="' . esc_attr($field_value) . '" />'; $template = str_replace($placeholder, $value, $template); } } return $template; }
/** * Check that the post is referencing the related entities. * * @param int $post_id The post ID. */ function checkPostReferences($post_id) { // Get the post. $post = get_post($post_id); $this->assertNotNull($post); // Get the post Redlink URI. $uri = wordlift_esc_sparql(wl_get_entity_uri($post->ID)); // Prepare the SPARQL query to select label and URL. $sparql = "SELECT DISTINCT ?uri WHERE { <{$uri}> dct:references ?uri . }"; // Send the query and get the response. $response = rl_sparql_select($sparql); $this->assertFalse(is_wp_error($response)); $body = $response['body']; $matches = array(); $count = preg_match_all('/^(?P<uri>[^\\r]*)/im', $body, $matches, PREG_SET_ORDER); $this->assertTrue(is_numeric($count)); $entity_ids = wl_core_get_related_entity_ids($post->ID); // wl_write_log( "[ entity IDs :: " . join( ', ', $entity_ids ) . " ][ size of entity IDs :: " . sizeof( $entity_ids ) . " ][ count :: $count ][ post ID :: $post->ID ]" ); // // if ( $count !== ( 1 + sizeof( $entity_ids ) ) ) { // wl_write_log( "[ sparql :: $sparql ][ body :: $body ]" ); // } // Expect only one match (headers + expected entities). $this->assertEquals($count, sizeof($entity_ids) + 1); $entity_uris = wl_post_ids_to_entity_uris($entity_ids); for ($i = 1; $i < $count; $i++) { $entity_uri = $matches[$i]['uri']; // Check that the URI is in the array. $this->assertTrue(in_array($entity_uri, $entity_uris)); } }
function testMicrodataCompilingRecursivityLimitation() { // A place $place_id = wl_create_post('Just a place', 'my-place', 'MyPlace', 'publish', 'entity'); wl_set_entity_main_type($place_id, 'http://schema.org/Place'); // Trying out both the schema API and the classic WP method wl_schema_set_value($place_id, 'latitude', 40.12); add_post_meta($place_id, Wordlift_Schema_Service::FIELD_GEO_LONGITUDE, 72.3, true); // An Event having as location the place above $event_id = wl_create_post('Just an event', 'my-event', 'MyEvent', 'publish', 'entity'); wl_set_entity_main_type($event_id, Wordlift_Schema_Service::SCHEMA_EVENT_TYPE); add_post_meta($event_id, Wordlift_Schema_Service::FIELD_DATE_START, '2014-10-21', true); add_post_meta($event_id, Wordlift_Schema_Service::FIELD_DATE_END, '2015-10-26', true); wl_schema_set_value($event_id, 'sameAs', array('http://rdf.freebase.com/my-event', 'http://dbpedia.org/resource/my-event')); //wl_schema_set_value($event_id, 'sameAs', 'http://dbpedia.org/resource/my-event'); add_post_meta($event_id, Wordlift_Schema_Service::FIELD_LOCATION, $place_id, true); // Create an annotated post containing the entities $entity_uri = wl_get_entity_uri($event_id); $content = <<<EOF <span itemid="{$entity_uri}">MyEvent</span> EOF; $post_id = wl_create_post($content, 'post', 'A post'); // Set to 0 the recursivity limitation on entity metadata compiling $this->setRecursionDepthLimit(0); // Compile markup for the given content $compiled_markup = _wl_content_embed_microdata($post_id, $content); $expected_markup = file_get_contents(dirname(__FILE__) . '/assets/microdata_compiling_recursivity_limitation.txt'); // Verify correct markup $this->assertEquals($this->prepareMarkup($expected_markup), $this->prepareMarkup($compiled_markup)); $this->setRecursionDepthLimit(1); // Compile markup for the given content $compiled_markup = _wl_content_embed_microdata($post_id, $content); $expected_markup = file_get_contents(dirname(__FILE__) . '/assets/microdata_compiling_for_an_entity_with_nested_entities.txt'); // Verify correct markup $this->assertEquals($this->prepareMarkup($expected_markup), $this->prepareMarkup($compiled_markup)); }
/** * Get the SPARQL fragment to set the dc:references statements. * * @param int $post_id The post ID. * * @return string The SPARQL fragment (or an empty string). */ function wl_get_sparql_post_references($post_id) { // Get the post URI. $post_uri = wordlift_esc_sparql(wl_get_entity_uri($post_id)); // Get the related entities IDs. $related = wl_core_get_related_entity_ids($post_id); // Build the SPARQL fragment. $sparql = ''; foreach ($related as $id) { $uri = wordlift_esc_sparql(wl_get_entity_uri($id)); $sparql .= "<{$post_uri}> dct:references <{$uri}> . "; } return $sparql; }
/** * Fills up the microdata_template with entity's values. * * @param string $entity_id An entity ID. * @param string $entity_type Entity type stracture. * @param integer $recursion_level Recursion depth level in microdata compiling. Recursion depth limit is defined by WL_MAX_NUM_RECURSIONS_WHEN_PRINTING_MICRODATA constant. * * @return string The content with embedded microdata. */ function wl_content_embed_compile_microdata_template($entity_id, $entity_type, $recursion_level = 0) { wl_write_log("[ entity id :: {$entity_id} ][ entity type :: " . var_export($entity_type, true) . " ][ recursion level :: {$recursion_level} ]"); $regex = '/{{(.*?)}}/'; $matches = array(); if (null === $entity_type) { return ''; } $template = $entity_type['microdata_template']; // Return empty string if template fields have not been found. if (false === preg_match_all($regex, $template, $matches, PREG_SET_ORDER)) { return ''; } foreach ($matches as $match) { $placeholder = $match[0]; $field_name = $match[1]; // Get property value. $meta_collection = wl_schema_get_value($entity_id, $field_name); // If no value is given, just remove the placeholder from the template if (null == $meta_collection) { $template = str_replace($placeholder, '', $template); continue; } // What kind of value is it? // TODO: Performance issue here: meta type retrieving should be centralized $expected_type = wl_get_meta_type($field_name); foreach ($meta_collection as $field_value) { if (WL_DATA_TYPE_URI == $expected_type) { // If is a numeric value we assume it is an ID referencing for an internal entity. if (is_numeric($field_value)) { // Found id, get uri. $field_value = wl_get_entity_uri($field_value); } // Just if the linked entity does exist I can go further with template compiling $nested_entity = wl_get_entity_post_by_uri($field_value); if (!is_null($nested_entity)) { $content = '<span itemid="' . esc_attr($field_value) . '">' . $nested_entity->post_title . '</span>'; $compiled_template = wl_content_embed_item_microdata($content, $field_value, $field_name, ++$recursion_level); $template = str_replace($placeholder, $compiled_template, $template); } else { $template = str_replace($placeholder, '', $template); } continue; } // Standard condition: field containing a raw value $value = '<span itemprop="' . esc_attr($field_name) . '" content="' . esc_attr($field_value) . '"></span>'; $template = str_replace($placeholder, $value, $template); } } return $template; }
/** * Save the specified data as an entity in WordPress. This method only create new entities. When an existing entity is * found (by its URI), then the original post is returned. * * @param array $entity_data, associative array containing: * string 'uri' The entity URI. * string 'label' The entity label. * string 'main_type' The entity type URI. * array 'type' An array of entity type URIs. * string 'description' The entity description. * array 'images' An array of image URLs. * int 'related_post_id' A related post ID. * array 'same_as' An array of sameAs URLs. * * @return null|WP_Post A post instance or null in case of failure. */ function wl_save_entity($entity_data) { $uri = $entity_data['uri']; $label = $entity_data['label']; $type_uri = $entity_data['main_type']; $entity_types = isset($entity_data['type']) ? $entity_data['type'] : array(); $description = $entity_data['description']; $images = isset($entity_data['image']) ? wl_force_to_array($entity_data['image']) : array(); $same_as = isset($entity_data['sameas']) ? wl_force_to_array($entity_data['sameas']) : array(); $related_post_id = isset($entity_data['related_post_id']) ? $entity_data['related_post_id'] : null; $other_properties = isset($entity_data['properties']) ? $entity_data['properties'] : array(); // wl_write_log( "[ uri :: $uri ][ label :: $label ][ type uri :: $type_uri ]" ); // Prepare properties of the new entity. $params = array('post_status' => is_numeric($related_post_id) ? get_post_status($related_post_id) : 'draft', 'post_type' => Wordlift_Entity_Service::TYPE_NAME, 'post_title' => $label, 'post_content' => $description, 'post_excerpt' => ''); // Check whether an entity already exists with the provided URI. $post = wl_get_entity_post_by_uri($uri); if (null !== $post) { // We insert into the params the entity ID, so it will be updated and not inserted. $params['ID'] = $post->ID; // Preserve the current entity status if ('public' === $post->post_status) { $params['post_status'] = $post->post_status; } // Preserve the current entity post_content. $params['post_content'] = $post->post_content; // Preserve the entity post_title to avoid de-synch between WP and RL // See: https://github.com/insideout10/wordlift-plugin/issues/221 $params['post_title'] = $post->post_title; } // If Yoast is installed and active, we temporary remove the save_postdata hook which causes Yoast to "pass over" // the local SEO form values to the created entity (https://github.com/insideout10/wordlift-plugin/issues/156) // Same thing applies to SEO Ultimate (https://github.com/insideout10/wordlift-plugin/issues/148) // This does NOT affect saving an entity from the entity admin page since this function is called when an entity // is created when saving a post. global $wpseo_metabox, $seo_ultimate; if (isset($wpseo_metabox)) { remove_action('wp_insert_post', array($wpseo_metabox, 'save_postdata')); } if (isset($seo_ultimate)) { remove_action('save_post', array($seo_ultimate, 'save_postmeta_box')); } // The fact that we're calling here wp_insert_post is causing issues with plugins (and ourselves too) that hook to // save_post in order to save additional inputs from the edit page. In order to avoid issues, we pop all the hooks // to the save_post and restore them after we saved the entity. // see https://github.com/insideout10/wordlift-plugin/issues/203 // see https://github.com/insideout10/wordlift-plugin/issues/156 // see https://github.com/insideout10/wordlift-plugin/issues/148 global $wp_filter; $save_post_filters = $wp_filter['save_post']; $wp_filter['save_post'] = array(); // create or update the post. $post_id = wp_insert_post($params, true); // Restore all the existing filters. $wp_filter['save_post'] = $save_post_filters; // If Yoast is installed and active, we restore the Yoast save_postdata hook (https://github.com/insideout10/wordlift-plugin/issues/156) if (isset($wpseo_metabox)) { add_action('wp_insert_post', array($wpseo_metabox, 'save_postdata')); } // If SEO Ultimate is installed, add back the hook we removed a few lines above. if (isset($seo_ultimate)) { add_action('save_post', array($seo_ultimate, 'save_postmeta_box'), 10, 2); } // TODO: handle errors. if (is_wp_error($post_id)) { wl_write_log(': error occurred'); // inform an error occurred. return null; } wl_set_entity_main_type($post_id, $type_uri); // Save the entity types. wl_set_entity_rdf_types($post_id, $entity_types); // Get a dataset URI for the entity. $wl_uri = wl_build_entity_uri($post_id); // Save the entity URI. wl_set_entity_uri($post_id, $wl_uri); // Add the uri to the sameAs data if it's not a local URI. if ($wl_uri !== $uri) { array_push($same_as, $uri); } $new_uri = wl_get_entity_uri($post_id); // Save the sameAs data for the entity. wl_schema_set_value($post_id, 'sameAs', $same_as); // Save the other properties (latitude, langitude, startDate, endDate, etc.) foreach ($other_properties as $property_name => $property_value) { wl_schema_set_value($post_id, $property_name, $property_value); } // Call hooks. do_action('wl_save_entity', $post_id); wl_write_log("[ post id :: {$post_id} ][ uri :: {$uri} ][ label :: {$label} ][ wl uri :: {$wl_uri} ][ types :: " . implode(',', $entity_types) . " ][ images count :: " . count($images) . " ][ same_as count :: " . count($same_as) . " ]"); foreach ($images as $image_remote_url) { // Check if image is already present in local DB if (strpos($image_remote_url, site_url()) !== false) { // Do nothing. continue; } // Check if there is an existing attachment for this post ID and source URL. $existing_image = wl_get_attachment_for_source_url($post_id, $image_remote_url); // Skip if an existing image is found. if (null !== $existing_image) { continue; } // Save the image and get the local path. $image = wl_save_image($image_remote_url); // Get the local URL. $filename = $image['path']; $url = $image['url']; $content_type = $image['content_type']; $attachment = array('guid' => $url, 'post_title' => $label, 'post_content' => '', 'post_status' => 'inherit', 'post_mime_type' => $content_type); // Create the attachment in WordPress and generate the related metadata. $attachment_id = wp_insert_attachment($attachment, $filename, $post_id); // Set the source URL for the image. wl_set_source_url($attachment_id, $image_remote_url); $attachment_data = wp_generate_attachment_metadata($attachment_id, $filename); wp_update_attachment_metadata($attachment_id, $attachment_data); // Set it as the featured image. set_post_thumbnail($post_id, $attachment_id); } // The entity is pushed to Redlink on save by the function hooked to save_post. // save the entity in the triple store. wl_linked_data_push_to_redlink($post_id); // finally return the entity post. return get_post($post_id); }
/** * Get an array of entity URIs given their post IDs. * * @param array $post_ids The post IDs. * * @return array An array of entity URIs. */ function wl_post_ids_to_entity_uris($post_ids) { $uris = array(); foreach ($post_ids as $id) { array_push($uris, wl_get_entity_uri($id)); } return $uris; }
/** * Save the specified data as an entity in WordPress. This method only create new entities. When an existing entity is * found (by its URI), then the original post is returned. * * @param array $entity_properties, associative array containing: * string 'uri' The entity URI. * string 'label' The entity label. * string 'main_type_uri' The entity type URI. * string 'description' The entity description. * array 'type_uris' An array of entity type URIs. * array 'images' An array of image URLs. * int 'related_post_id' A related post ID. * array 'same_as' An array of sameAs URLs. * * @return null|WP_Post A post instance or null in case of failure. */ function wl_save_entity($entity_properties) { $uri = $entity_properties['uri']; $label = $entity_properties['label']; $type_uri = $entity_properties['main_type_uri']; $description = $entity_properties['description']; $entity_types = $entity_properties['type_uris']; $images = $entity_properties['images']; $related_post_id = $entity_properties['related_post_id']; $same_as = $entity_properties['same_as']; // Avoid errors due to null. if (is_null($entity_types)) { $entity_types = array(); } wl_write_log("[ uri :: {$uri} ][ label :: {$label} ][ type uri :: {$type_uri} ]"); // Prepare properties of the new entity. $params = array('post_status' => is_numeric($related_post_id) ? get_post_status($related_post_id) : 'draft', 'post_type' => WL_ENTITY_TYPE_NAME, 'post_title' => $label, 'post_content' => $description, 'post_excerpt' => ''); // Check whether an entity already exists with the provided URI. $post = wl_get_entity_post_by_uri($uri); if (null !== $post) { // We insert into the params the entity ID, so it will be updated and not inserted. $params['ID'] = $post->ID; } // create or update the post. $post_id = wp_insert_post($params, true); // TODO: handle errors. if (is_wp_error($post_id)) { wl_write_log(': error occurred'); // inform an error occurred. return null; } wl_set_entity_main_type($post_id, $type_uri); // Save the entity types. wl_set_entity_rdf_types($post_id, $entity_types); // Get a dataset URI for the entity. $wl_uri = wl_build_entity_uri($post_id); // Save the entity URI. wl_set_entity_uri($post_id, $wl_uri); // Add the uri to the sameAs data if it's not a local URI. if ($wl_uri !== $uri) { array_push($same_as, $uri); } $new_uri = wl_get_entity_uri($post_id); // Save the sameAs data for the entity. wl_schema_set_value($post_id, 'sameAs', $same_as); // Call hooks. do_action('wl_save_entity', $post_id); wl_write_log("[ post id :: {$post_id} ][ uri :: {$uri} ][ label :: {$label} ][ wl uri :: {$wl_uri} ][ types :: " . implode(',', $entity_types) . " ][ images count :: " . count($images) . " ][ same_as count :: " . count($same_as) . " ]"); foreach ($images as $image_remote_url) { // Check if image is already present in local DB if (strpos($image_remote_url, site_url()) !== false) { // Do nothing. continue; } // Check if there is an existing attachment for this post ID and source URL. $existing_image = wl_get_attachment_for_source_url($post_id, $image_remote_url); // Skip if an existing image is found. if (null !== $existing_image) { continue; } // Save the image and get the local path. $image = wl_save_image($image_remote_url); // Get the local URL. $filename = $image['path']; $url = $image['url']; $content_type = $image['content_type']; $attachment = array('guid' => $url, 'post_title' => $label, 'post_content' => '', 'post_status' => 'inherit', 'post_mime_type' => $content_type); // Create the attachment in WordPress and generate the related metadata. $attachment_id = wp_insert_attachment($attachment, $filename, $post_id); // Set the source URL for the image. wl_set_source_url($attachment_id, $image_remote_url); $attachment_data = wp_generate_attachment_metadata($attachment_id, $filename); wp_update_attachment_metadata($attachment_id, $attachment_data); // Set it as the featured image. set_post_thumbnail($post_id, $attachment_id); } // The entity is pushed to Redlink on save by the function hooked to save_post. // save the entity in the triple store. wl_linked_data_push_to_redlink($post_id); // finally return the entity post. return get_post($post_id); }
function testEntityWithAlternativeLabelIsProperlyOverridden() { $original_label = uniqid('entity-original', true); // Create an entity $entity_id = wl_create_post('', 'entity-1', $original_label, 'draft', 'entity'); // Check that there are no related posts for the entity $related_post_ids = wl_core_get_related_post_ids($entity_id, array("predicate" => "what")); $this->assertCount(0, $related_post_ids); // Generate e label and set it as alternative label for the new entity $alternative_label = uniqid('entity-alternative', true); Wordlift_Entity_Service::get_instance()->set_alternative_labels($entity_id, $alternative_label); // Check that the alternative label is properly set $labels = Wordlift_Entity_Service::get_instance()->get_alternative_labels($entity_id); $this->assertCount(1, $labels); $this->assertContains($alternative_label, $labels); // Force post status to publish: this triggers the save_post hook wl_update_post_status($entity_id, 'publish'); // Check that entity label is properly mapped on entity post title $this->assertEquals($original_label, get_post($entity_id)->post_title); // Notice that the uri is generated trough the original label // while the current label is the alternative one $fake = $this->prepareFakeGlobalPostArrayFromFile('/assets/fake_global_post_array_with_one_existing_entity_linked_as_what.json', array('CURRENT_URI' => $this->buildEntityUriForLabel($original_label), 'CURRENT_LABEL' => $alternative_label)); $_POST = $fake; // Retrieve the entity uri (the first key in wl_entities associative aray) $original_entity_uri = current(array_keys($fake['wl_entities'])); // Reference the entity to the post content trough its alternative label $content = <<<EOF <span itemid="{$original_entity_uri}">{$alternative_label}</span> EOF; // Create a post referincing to the created entity $post_id = wl_create_post($content, 'my-post', 'A post', 'draft'); // Check that entity label is STILL properly mapped on entity post title $this->assertEquals($original_label, get_post($entity_id)->post_title); $expected_entity_uri = $this->buildEntityUriForLabel($original_label); $entity_uri = wl_get_entity_uri($entity_id); $this->assertEquals($entity_uri, $expected_entity_uri); // And it should be related to the post as what predicate $related_entity_ids = wl_core_get_related_entity_ids($post_id, array("predicate" => "what")); $this->assertCount(1, $related_entity_ids); $this->assertContains($entity_id, $related_entity_ids); }
/** * Replaces the *itemid* attributes URIs with the WordLift URIs. * * @param string $content The post content. * * @return string The updated post content. */ function wl_replace_item_id_with_uri($content) { // wl_write_log( "wl_replace_item_id_with_uri" ); // Strip slashes, see https://core.trac.wordpress.org/ticket/21767 $content = stripslashes($content); // If any match are found. $matches = array(); if (0 < preg_match_all('/ itemid="([^"]+)"/i', $content, $matches, PREG_SET_ORDER)) { foreach ($matches as $match) { // Get the item ID. $item_id = $match[1]; // Get the post bound to that item ID (looking both in the 'official' URI and in the 'same-as' . $post = wl_get_entity_post_by_uri($item_id); // If no entity is found, continue to the next one. if (null === $post) { continue; } // Get the URI for that post. $uri = wl_get_entity_uri($post->ID); // wl_write_log( "wl_replace_item_id_with_uri [ item id :: $item_id ][ uri :: $uri ]" ); // If the item ID and the URI differ, replace the item ID with the URI saved in WordPress. if ($item_id !== $uri) { $uri_e = esc_html($uri); $content = str_replace(" itemid=\"{$item_id}\"", " itemid=\"{$uri_e}\"", $content); } } } // Reapply slashes. $content = addslashes($content); return $content; }
function testEntityMetadataAreProperlyUpdated() { $fake = $this->prepareFakeGlobalPostArrayFromFile('/assets/fake_global_post_array_with_one_entity_linked_as_what.json'); $_POST = $fake; // Retrieve the entity uri (the first key in wl_entities associative aray) $original_entity_uri = current(array_keys($fake['wl_entities'])); // Reference the entity to the post content $content = <<<EOF <span itemid="{$original_entity_uri}">My entity</span> EOF; // Create a post referincing to the created entity $post_id = wl_create_post($content, 'my-post', 'A post', 'draft'); // Here the entity should have been created $original_entity = wl_get_entity_post_by_uri($original_entity_uri); $this->assertNotNull($original_entity); // Store entity type, images and sameAs (needed later) $original_type = wl_schema_get_types($original_entity->ID); $original_thumbnails = $this->getThumbs($original_entity->ID); $original_sameAs = wl_schema_get_value($original_entity->ID, 'sameAs'); // Query the same entity using the Redlink URI $entity_uri = wl_get_entity_uri($original_entity->ID); $e = wl_get_entity_post_by_uri($entity_uri); $this->assertEquals($original_entity, $e); // The entity description should be the same we expect $raw_entity = current(array_values($fake['wl_entities'])); $this->assertEquals($raw_entity['description'], $original_entity->post_content); // The entity is related as what predicate $related_entity_ids = wl_core_get_related_entity_ids($post_id, array("predicate" => "what")); $this->assertCount(1, $related_entity_ids); // Ensure there are no other relation instances $relation_instances = wl_tests_get_relation_instances_for($post_id); $this->assertCount(1, $relation_instances); /* Now Post is saved again with the same mentioned entity: * - with different type * - with different description * - with one more image * - with one modified sameAs * - as WHO instead fo WHAT */ $fake = $this->prepareFakeGlobalPostArrayFromFile('/assets/fake_global_post_array_with_one_entity_linked_as_who_and_modified_data.json'); $_POST = $fake; // The entity url should be the same we expect $raw_entity = current(array_values($fake['wl_entities'])); $raw_entity_uri = $raw_entity['uri']; $new_content = <<<EOF <span itemid="{$raw_entity_uri}">My entity</span> EOF; // Update the post content (to force existing entities update) wp_update_post(array('ID' => $post_id, 'post_content' => $new_content)); // Verify the mentioned entity was already into DB... $updated_entity = wl_get_entity_post_by_uri($raw_entity_uri); $this->assertEquals($original_entity->ID, $updated_entity->ID); $this->assertEquals($original_entity->post_title, $updated_entity->post_title); // ... but some properties changed! $this->assertNotEquals($original_entity, $updated_entity); // Verify entity type has been updated $updated_type = wl_schema_get_types($updated_entity->ID); $this->assertNotEquals($original_type, $updated_type); $this->assertEquals(array('http://schema.org/Organization'), $updated_type); // Verify entity description has been updated $this->assertEquals($raw_entity['description'], $updated_entity->post_content); // Verify entity images have been updated (one was added) $updated_thumbnails = $this->getThumbs($updated_entity->ID); $this->assertNotEquals($original_thumbnails, $updated_thumbnails); $this->assertContains($original_thumbnails[0], $updated_thumbnails); $this->assertCount(2, $updated_thumbnails); // There is one more $this->assertContains('Netherlands_vs_Ivory_Coast', $updated_thumbnails[1]); // ... about soccer // Verify entity sameAs have been updated $updated_sameAs = wl_schema_get_value($updated_entity->ID, 'sameAs'); $this->assertNotEquals($original_sameAs, $updated_sameAs); $this->assertContains($original_sameAs[1], $updated_sameAs); $this->assertNotContains($original_sameAs[0], $updated_sameAs); $this->assertContains('http://sv.dbpedia.org/page/Reason', $updated_sameAs); // Verify the entity is now related as who predicate $related_entity_ids = wl_core_get_related_entity_ids($post_id, array("predicate" => "who")); $this->assertCount(1, $related_entity_ids); // Ensure there are no other relation instances $relation_instances = wl_tests_get_relation_instances_for($post_id); $this->assertCount(1, $relation_instances); }
/** * Receive post meta events immediately after a post metadata has been added. * * @since 3.1.5 * * @param int $mid The meta ID after successful update. * @param int $object_id Object ID. * @param string $meta_key Meta key. * @param mixed $_meta_value Meta value. */ public function added_post_meta($mid, $object_id, $meta_key, $_meta_value) { $this->log_service->trace("A post meta has been updated [ meta id :: {$mid} ][ object id :: {$object_id} ][ meta key :: {$meta_key} ][ meta value :: " . var_export($_meta_value, true) . " ]"); // Return if it's not the Thumbnail id meta key. if (self::THUMBNAIL_ID_META_KEY !== $meta_key) { return; } // The meta value must be the numeric id of the attachment. If it isn't, return. if (!is_numeric($_meta_value)) { return; } // Do not perform any action is the post is not published. if ('publish' !== get_post_status($object_id)) { return; } // Get the post uri and return if it's null. if (null === ($uri = wl_get_entity_uri($object_id))) { return; } // Get the attachment url and return if not found. if (false === ($attachment_url = wp_get_attachment_url($_meta_value))) { return; } // Prepare the query and execute it. We don't buffer the query since we're not going to reindex. $query = sprintf('DELETE { <%1$s> <%2$s> ?o . } WHERE { <%1$s> <%2$s> ?o . }; INSERT DATA { <%1$s> <%2$s> <%3$s> . };', $uri, self::THUMBNAIL_RDF_PREDICATE, $attachment_url); if (false === rl_execute_sparql_update_query($query, false)) { $this->log_service->error("An error occurred removing the post thumbnail [ meta ids :: {$mid} ][ object id :: {$object_id} ][ meta key :: {$meta_key} ][ meta value :: " . var_export($_meta_value, true) . " ][ query :: {$query} ]"); } }
public function testFacetsSelection() { // Create 2 posts and 2 entities $entity_1_id = wl_create_post('', 'entity0', 'An Entity', 'draft', 'entity'); $entity_2_id = wl_create_post('', 'entity1', 'Another Entity', 'draft', 'entity'); $post_1_id = wl_create_post('', 'post1', 'A post', 'publish'); $post_2_id = wl_create_post('', 'post2', 'A post', 'publish'); // Insert relations wl_core_add_relation_instance($post_1_id, WL_WHAT_RELATION, $entity_1_id); wl_core_add_relation_instance($post_2_id, WL_WHAT_RELATION, $entity_1_id); wl_core_add_relation_instance($post_2_id, WL_WHAT_RELATION, $entity_2_id); // Set $_GET variable: this means we will perform data selection for $entity_1_id $_GET['entity_id'] = $entity_1_id; $_GET['type'] = 'facets'; try { $this->_handleAjax('wl_faceted_search'); } catch (WPAjaxDieContinueException $e) { } $response = json_decode($this->_last_response); $this->assertInternalType('array', $response); $this->assertCount(1, $response); $entity_uris = array($response[0]->id); $this->assertNotContains(wl_get_entity_uri($entity_1_id), $entity_uris); $this->assertContains(wl_get_entity_uri($entity_2_id), $entity_uris); }
/** * Test saving a post without a title. Check the URI. */ function testSavePostWithoutTitle() { $post_id = wl_create_post('Sample Post', 'post-1', '', 'publish'); $uri = wl_get_entity_uri($post_id); $expected_uri = wl_configuration_get_redlink_dataset_uri() . "/post/id/{$post_id}"; $this->assertEquals($expected_uri, $uri); }