/** * Encode places array in geojson compliant format * (refer to http://leafletjs.com/examples/geojson.html) * Define geomap boundaries according to $places * Default boundaries are defined using PHP_INT_MAX value * * @param array $places An array of place posts. * * @return array An array of markers and boundaries for Leaflet. */ function wl_shortcode_geomap_prepare_map($places) { // Prepare for min/max lat/long in case we need to define a view boundary for the client JavaScript. $min_latitude = PHP_INT_MAX; $min_longitude = PHP_INT_MAX; $max_latitude = ~PHP_INT_MAX; $max_longitude = ~PHP_INT_MAX; // Prepare an empty array of POIs in geoJSON format. $pois = array(); // And store list of points to allow Leaflet compute the optimal bounding box. // The main reason for this is that geoJSON has swapped coordinates (lon. lat) $boundaries = array(); // Add a POI for each entity that has coordinates. foreach ($places as $entity) { // Get the coordinates. $coordinates = wl_get_coordinates($entity->ID); // Don't show the widget if the coordinates aren't set. if ($coordinates['latitude'] == 0 || $coordinates['longitude'] == 0) { continue; } // TODO Map html rendering should be delegated to the wordlift js ui layer // This function should be focused on returning pure data instead // Get the title, URL and thumb of the entity. $title = esc_attr($entity->post_title); $link = esc_attr(get_permalink($entity->ID)); if ('' !== ($thumbnail_id = get_post_thumbnail_id($entity->ID)) && false !== ($attachment = wp_get_attachment_image_src($thumbnail_id))) { $img_src = esc_attr($attachment[0]); } // Build HTML popup. TODO: move thumb width in css $content = "<a href={$link}><h6>{$title}</h6>"; if (isset($img_src)) { $content = $content . "<img src={$img_src} style='width:100%'/>"; } $content = $content . "</a><ul>"; // Get the related posts (published) and print them in the popup. $related_posts = wl_core_get_related_post_ids($entity->ID, array('status' => 'publish')); foreach ($related_posts as $rp_id) { $rp = get_post($rp_id); $title = esc_attr($rp->post_title); $link = esc_attr(get_permalink($rp->ID)); $content = $content . "<li><a href={$link}>{$title}</a></li>"; } $content = $content . "</ul>"; // Formatting POI in geoJSON. // http://leafletjs.com/examples/geojson.html $poi = array('type' => 'Feature', 'properties' => array('popupContent' => $content), 'geometry' => array('type' => 'Point', 'coordinates' => array($coordinates['longitude'], $coordinates['latitude']))); $pois[] = $poi; // Formatting boundaries in a Leaflet-like format (see LatLngBounds). // http://leafletjs.com/reference.html#latlngbounds $boundaries[] = array($coordinates['latitude'], $coordinates['longitude']); } $map_data = array(); $map_data['features'] = $pois; $map_data['boundaries'] = $boundaries; return $map_data; }
/** * Render custom columns * @see https://codex.wordpress.org/Plugin_API/Action_Reference/manage_$post_type_posts_custom_column * * @since 3.3.0 * * @param string $column the current column. * @param int $entity_id An entity post id. * * @return true if the post is an entity otherwise false. */ public function render_custom_columns($column, $entity_id) { switch ($column) { case 'wl_column_related_posts': echo count(wl_core_get_related_post_ids($entity_id)); break; case 'wl_column_thumbnail': $edit_link = get_edit_post_link($entity_id); $thumb = get_the_post_thumbnail($entity_id, array(self::THUMB_SIZE, self::THUMB_SIZE)); if (!$thumb) { $thumb = "<img src='" . WL_DEFAULT_THUMBNAIL_PATH . "' width='" . self::THUMB_SIZE . "' />"; } echo "<a href='{$edit_link}'>{$thumb}</a>"; break; case 'wl_column_rating': $rating = $this->entity_service->get_rating_for($entity_id); echo '<i class="wl-traffic-light wl-tl-' . $rating['traffic_light_score'] . '">' . $rating['percentage_score'] . '%</i>'; break; } }
/** * Recursive function used to retrieve related content starting from a post ID. * * @uses wl_core_get_related_post_ids() to get the list of post ids that reference an entity. * * @param int $entity_id The entity post ID. * @param int $depth Max number of entities in output. * @param array $related An existing array of related entities. * @return array */ function wl_shortcode_chord_get_relations($entity_id, $depth = 2, $related = null) { // Search for more entities only if we did not exceed $depth or $max_size $max_size = 30; if (!is_null($related)) { if (count($related['entities']) > $max_size || $depth <= 0) { return $related; } } wl_write_log("wl_shortcode_chord_get_relations [ post id :: {$entity_id} ][ depth :: {$depth} ][ related? :: " . (is_null($related) ? 'yes' : 'no') . " ]"); // Create a related array which will hold entities and relations. if (is_null($related)) { $related = array('entities' => array($entity_id), 'relations' => array()); } // Get related entities $related_entity_ids = wl_core_get_related_entity_ids($entity_id, array('status' => 'publish')); // Get related posts (only id the current node is an entity) $related_post_ids = array(); if (get_post_type($entity_id) == WL_ENTITY_TYPE_NAME) { $related_post_ids = wl_core_get_related_post_ids($entity_id, array('status' => 'publish')); } // Merge results. $related_ids = array_merge($related_post_ids, $related_entity_ids); $related_ids = array_unique($related_ids); // TODO: List of entities ($rel) should be ordered by interest factors. shuffle($related_ids); // Now we have all the related IDs. foreach ($related_ids as $related_id) { // TODO: does it make sense to set an array post ID > related ID? The *wl_shortcode_chord_get_graph* // method is going anyway to *refactor* the data structure. So here the structure may be optimized in terms // of readability and performance. $related['relations'][] = array($entity_id, $related_id); if (!in_array($related_id, $related['entities'])) { //Found new related entity! $related['entities'][] = $related_id; $related = wl_shortcode_chord_get_relations($related_id, $depth - 1, $related); } } // End condition 2: no more entities to search for. return $related; }
/** * 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); }
function wl_shortcode_faceted_search_ajax($http_raw_data = null) { // Entity ID must be defined if (!isset($_GET['entity_id'])) { wp_die('No entity_id given'); return; } $entity_id = $_GET['entity_id']; // If the current post is not an entity post an exception needs to be raised $entity = get_post($entity_id); if (Wordlift_Entity_Service::TYPE_NAME !== $entity->post_type) { wp_die('Faceted search supports only entity posts'); return; } // Which type was requested? if (isset($_GET['type'])) { $required_type = $_GET['type']; } else { $required_type = null; } // Extract filtering conditions $filtering_entity_uris = null == $http_raw_data ? file_get_contents("php://input") : $http_raw_data; $filtering_entity_uris = json_decode($filtering_entity_uris); // Set up data structures $referencing_post_ids = wl_core_get_related_post_ids($entity_id, array('status' => 'publish')); $results = array(); if ('posts' == $required_type) { // Required filtered posts. wl_write_log("Going to find related posts for the current entity [ entity ID :: {$entity_id} ]"); if (empty($filtering_entity_uris)) { // No filter, just get referencing posts foreach ($referencing_post_ids as $post_obj_id) { $post_obj = get_post($post_obj_id); $thumbnail = wp_get_attachment_url(get_post_thumbnail_id($post_obj->ID, 'thumbnail')); $post_obj->thumbnail = $thumbnail ? $thumbnail : WL_DEFAULT_THUMBNAIL_PATH; $post_obj->permalink = get_post_permalink($post_obj->ID); $results[] = $post_obj; } } else { $filtering_entity_ids = wl_get_entity_post_ids_by_uris($filtering_entity_uris); // Search posts that reference all the filtering entities. $filtered_posts = wl_core_get_posts(array('get' => 'posts', 'post__in' => $referencing_post_ids, 'related_to__in' => $filtering_entity_ids, 'post_type' => 'post', 'as' => 'subject')); foreach ($filtered_posts as $post_obj) { $thumbnail = wp_get_attachment_url(get_post_thumbnail_id($post_obj->ID, 'thumbnail')); $post_obj->thumbnail = $thumbnail ? $thumbnail : WL_DEFAULT_THUMBNAIL_PATH; $post_obj->permalink = get_post_permalink($post_obj->ID); $results[] = $post_obj; } $results = $filtered_posts; } } else { global $wpdb; wl_write_log("Going to find related entities for the current entity [ entity ID :: {$entity_id} ]"); // Retrieve Wordlift relation instances table name $table_name = wl_core_get_relation_instances_table_name(); $ids = implode(',', $referencing_post_ids); // TODO - if an entity is related with different predicates each predicate impacts on counter $query = <<<EOF SELECT object_id as ID, count( object_id ) as counter FROM {$table_name} WHERE subject_id IN ({$ids}) and object_id != {$entity_id} GROUP BY object_id; EOF; wl_write_log("Going to find related entities for the current entity [ entity ID :: {$entity_id} ] [ query :: {$query} ]"); $entities = $wpdb->get_results($query, OBJECT); wl_write_log("Entities found " . count($entities)); foreach ($entities as $obj) { $entity = get_post($obj->ID); $entity = wl_serialize_entity($entity); $entity['counter'] = $obj->counter; $results[] = $entity; } } wl_core_send_json($results); }
/** * Calculate rating for a given entity * Rating depends from following criteria * * 1. Is the current entity related to at least 1 post? * 2. Is the current entity content post not empty? * 3. Is the current entity related to at least 1 entity? * 4. Is the entity published? * 5. There is a a thumbnail associated to the entity? * 6. Has the entity a sameas defined? * 7. Are all schema.org required metadata compiled? * * Each positive check means +1 in terms of rating score * * @since 3.3.0 * * @param int $post_id The entity post id. * * @return int An array representing the rating obj. */ public function calculate_rating_for($post_id) { // If it's not an entity, return. if (!$this->is_entity($post_id)) { return; } // Retrieve the post object $post = get_post($post_id); // Rating value $score = 0; // Store warning messages $warnings = array(); // Is the current entity related to at least 1 post? 0 < count(wl_core_get_related_post_ids($post->ID)) ? $score++ : array_push($warnings, __(self::RATING_WARNING_HAS_RELATED_POSTS, 'wordlift')); // Is the post content not empty? !empty($post->post_content) ? $score++ : array_push($warnings, __(self::RATING_WARNING_HAS_CONTENT_POST, 'wordlift')); // Is the current entity related to at least 1 entity? // Was the current entity already disambiguated? 0 < count(wl_core_get_related_entity_ids($post->ID)) ? $score++ : array_push($warnings, __(self::RATING_WARNING_HAS_RELATED_ENTITIES, 'wordlift')); // Is the entity published? 'publish' === get_post_status($post->ID) ? $score++ : array_push($warnings, __(self::RATING_WARNING_IS_PUBLISHED, 'wordlift')); // Has a thumbnail? has_post_thumbnail($post->ID) ? $score++ : array_push($warnings, __(self::RATING_WARNING_HAS_THUMBNAIL, 'wordlift')); // Get all post meta keys for the current post global $wpdb; $query = $wpdb->prepare("SELECT DISTINCT(meta_key) FROM {$wpdb->postmeta} WHERE post_id = %d", $post->ID); // Check intersection between available meta keys // and expected ones arrays to detect missing values $available_meta_keys = $wpdb->get_col($query); // If each expected key is contained in available keys array ... in_array(Wordlift_Schema_Service::FIELD_SAME_AS, $available_meta_keys) ? $score++ : array_push($warnings, __(self::RATING_WARNING_HAS_SAME_AS, 'wordlift')); $schema = wl_entity_type_taxonomy_get_type($post_id); $expected_meta_keys = null === $schema['custom_fields'] ? array() : array_keys($schema['custom_fields']); $intersection = array_intersect($expected_meta_keys, $available_meta_keys); // If each expected key is contained in available keys array ... count($intersection) === count($expected_meta_keys) ? $score++ : array_push($warnings, __(self::RATING_WARNING_HAS_COMPLETED_METADATA, 'wordlift')); // Finally return score and warnings return array('raw_score' => $score, 'traffic_light_score' => $this->convert_raw_score_to_traffic_light($score), 'percentage_score' => $this->convert_raw_score_to_percentage($score), 'warnings' => $warnings); }
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); }
function testWlCoreGetRelatedPostIdsForAPost() { // Create 2 posts and 1 entities $post_1_id = wl_create_post('', 'post1', 'A post'); $post_2_id = wl_create_post('', 'post2', 'A post'); $entity_1_id = wl_create_post('', 'entity1', 'An Entity', 'draft', 'entity'); // Insert relations wl_core_add_relation_instance($post_1_id, WL_WHERE_RELATION, $entity_1_id); wl_core_add_relation_instance($post_2_id, WL_WHO_RELATION, $entity_1_id); // Check relation are retrieved as expected $result = wl_core_get_related_post_ids($post_1_id); $this->assertCount(1, $result); $this->assertTrue(in_array($post_2_id, $result)); $result = wl_core_get_related_post_ids($post_1_id, array('predicate' => WL_WHERE_RELATION)); $this->assertCount(0, $result); $result = wl_core_get_related_post_ids($post_1_id, array('predicate' => WL_WHO_RELATION)); $this->assertCount(1, $result); $this->assertTrue(in_array($post_2_id, $result)); }
/** * Test saving entities passed via a metabox. */ function testEntitiesViaArray() { // Create a post. $post_id = $this->createPost(); $this->assertTrue(is_numeric($post_id)); $post = get_post($post_id); $this->assertNotNull($post); // Read the entities from the mock-up analysis. $analysis_results = wl_parse_file(dirname(__FILE__) . '/' . self::FILENAME . '.json'); $this->assertTrue(is_array($analysis_results)); // For each entity get the label, type, description and thumbnails. $this->assertTrue(isset($analysis_results['entities'])); // Get a reference to the entities. $text_annotations = $analysis_results['text_annotations']; $best_entities = array(); foreach ($text_annotations as $id => $text_annotation) { $entity_annotation = wl_get_entity_annotation_best_match($text_annotation['entities']); $entity = $entity_annotation['entity']; $entity_id = $entity->{'@id'}; if (!array_key_exists($entity_id, $best_entities)) { $best_entities[$entity_id] = $entity; } } // Accumulate the entities in an array. $entities = array(); foreach ($best_entities as $uri => $entity) { // Label if (!isset($entity->{'http://www.w3.org/2000/01/rdf-schema#label'}->{'@value'})) { var_dump($entity); } $this->assertTrue(isset($entity->{'http://www.w3.org/2000/01/rdf-schema#label'}->{'@value'})); $label = $entity->{'http://www.w3.org/2000/01/rdf-schema#label'}->{'@value'}; $this->assertFalse(empty($label)); // Type // $type = wl_get_entity_type($entity); // $this->assertFalse(empty($type)); // Description $description = wl_get_entity_description($entity); $this->assertNotNull($description); // Images $images = wl_get_entity_thumbnails($entity); $this->assertTrue(is_array($images)); // Save the entity to the entities array. $entities = array_merge_recursive($entities, array($uri => array('uri' => $uri, 'label' => $label, 'main_type' => 'http://schema.org/Thing', 'type' => array(), 'description' => $description, 'images' => $images))); } // Save the entities in the array. $entity_posts = wl_save_entities($entities); // Publish $entity_post_ids = array_map(function ($item) { return $item->ID; }, $entity_posts); foreach ($entity_post_ids as $entity_id) { wp_publish_post($entity_id); } // TODO: need to bind entities with posts. wl_core_add_relation_instances($post_id, WL_WHAT_RELATION, $entity_post_ids); $this->assertCount(sizeof($entity_post_ids), wl_core_get_related_entity_ids($post_id)); // TODO: synchronize data. // NOTICE: this requires a published post! wl_linked_data_push_to_redlink($post_id); // Check that the entities are created in WordPress. $this->assertCount(count($entities), $entity_posts); // Check that each entity is bound to the post. $entity_ids = array(); foreach ($entity_posts as $post) { // Store the entity IDs for future checks. array_push($entity_ids, $post->ID); // Get the related posts IDs. $rel_posts = wl_core_get_related_post_ids($post->ID); $this->assertCount(1, $rel_posts); // The post must be the one the test created. $this->assertEquals($post_id, $rel_posts[0]); } // Check that the post references the entities. $rel_entities = wl_core_get_related_entity_ids($post_id); $this->assertEquals(count($entity_ids), count($rel_entities)); foreach ($entity_ids as $id) { $this->assertTrue(in_array($id, $rel_entities)); } // Check that the locally saved entities and the remotely saved ones match. $this->checkEntities($entity_posts); // Check that the locally saved post data match the ones on Redlink. $this->checkPost($post_id); // Check the post references, that they match between local and remote. $this->checkPostReferences($post_id); // Delete the post. $this->deletePost($post_id); }