function testWL_Metabox_group_properties_by_input_field()
 {
     // Create an entity of type Place
     $business_id = wl_create_post('', 'p', 'A place', 'publish', Wordlift_Entity_Service::TYPE_NAME);
     wl_set_entity_main_type($business_id, 'http://schema.org/LocalBusiness');
     // Create Metabox and its Fields
     $metabox = new WL_Metabox();
     $entity_type = wl_entity_taxonomy_get_custom_fields($business_id);
     $simple_and_grouped_fields = $metabox->group_properties_by_input_field($entity_type);
     // Verify properties have been distinguished (single vs special/grouped)
     $this->assertArrayHasKey(Wordlift_Schema_Service::FIELD_FOUNDER, $simple_and_grouped_fields[0]);
     // Simple Field
     $this->assertArrayHasKey('address', $simple_and_grouped_fields[1]);
     // Special/grouped Field
     $this->assertArrayHasKey('coordinates', $simple_and_grouped_fields[1]);
     // Special/grouped Field
     $this->assertArrayHasKey('sameas', $simple_and_grouped_fields[1]);
     // Special/grouped Field
 }
/**
 * Push the provided entity post to Redlink.
 *
 * @param object $entity_post An entity post instance.
 */
function wl_push_entity_post_to_redlink($entity_post)
{
    // Get the Entity service instance to perform actions related to entities.
    $entity_service = Wordlift_Entity_Service::get_instance();
    // Only handle published entities.
    if (!$entity_service->is_entity($entity_post->ID) || 'publish' !== $entity_post->post_status) {
        wl_write_log("wl_push_entity_post_to_redlink : not an entity or not published [ post type :: {$entity_post->post_type} ][ post status :: {$entity_post->post_status} ]");
        return;
    }
    // get the entity URI and the SPARQL escaped version.
    $uri = wl_get_entity_uri($entity_post->ID);
    $uri_e = wl_sparql_escape_uri($uri);
    // If the URI ends with a trailing slash, then we have a problem.
    if ('/' === substr($uri, -1, 1)) {
        wl_write_log("wl_push_entity_post_to_redlink : the URI is invalid [ post ID :: {$entity_post->ID} ][ URI :: {$uri} ]");
        return;
    }
    // Get the site language in order to define the literals language.
    $site_language = wl_configuration_get_site_language();
    // get the title and content as label and description.
    $label = wordlift_esc_sparql($entity_post->post_title);
    $descr = wordlift_esc_sparql(wp_strip_all_tags(strip_shortcodes($entity_post->post_content)));
    $permalink = wl_sparql_escape_uri(get_permalink($entity_post->ID));
    // wl_write_log( "wl_push_entity_post_to_redlink [ entity post id :: $entity_post->ID ][ uri :: $uri ][ label :: $label ]" );
    // create a new empty statement.
    $delete_stmt = '';
    $sparql = '';
    // delete on RL all statements regarding properties set from WL (necessary when changing entity type)
    $all_custom_fields = wl_entity_taxonomy_get_custom_fields();
    $predicates_to_be_deleted = array();
    foreach ($all_custom_fields as $type => $fields) {
        foreach ($fields as $cf) {
            $predicate = $cf['predicate'];
            if (!in_array($predicate, $predicates_to_be_deleted)) {
                $predicates_to_be_deleted[] = $predicate;
                $delete_stmt .= "DELETE { <{$uri_e}> <{$predicate}> ?o } WHERE  { <{$uri_e}> <{$predicate}> ?o };\n";
            }
        }
    }
    // set the same as.
    $same_as = wl_schema_get_value($entity_post->ID, 'sameAs');
    foreach ($same_as as $same_as_uri) {
        $same_as_uri_esc = wl_sparql_escape_uri($same_as_uri);
        $sparql .= "<{$uri_e}> owl:sameAs <{$same_as_uri_esc}> . \n";
    }
    // set the label
    $sparql .= "<{$uri_e}> rdfs:label \"{$label}\"@{$site_language} . \n";
    // Set the alternative labels.
    $alt_labels = $entity_service->get_alternative_labels($entity_post->ID);
    foreach ($alt_labels as $alt_label) {
        $sparql .= sprintf('<%s> rdfs:label "%s"@%s . ', $uri_e, Wordlift_Query_Builder::escape_value($alt_label), $site_language);
    }
    // set the URL
    $sparql .= "<{$uri_e}> schema:url <{$permalink}> . \n";
    // set the description.
    if (!empty($descr)) {
        $sparql .= "<{$uri_e}> schema:description \"{$descr}\"@{$site_language} . \n";
    }
    $main_type = wl_entity_type_taxonomy_get_type($entity_post->ID);
    if (null != $main_type) {
        $main_type_uri = wl_sparql_escape_uri($main_type['uri']);
        $sparql .= " <{$uri_e}> a <{$main_type_uri}> . \n";
        // The type define custom fields that hold additional data about the entity.
        // For example Events may have start/end dates, Places may have coordinates.
        // The value in the export fields must be rewritten as triple predicates, this
        // is what we're going to do here.
        //		wl_write_log( 'wl_push_entity_post_to_redlink : checking if entity has export fields [ type :: ' . var_export( $main_type, true ) . ' ]' );
        if (isset($main_type['custom_fields'])) {
            foreach ($main_type['custom_fields'] as $field => $settings) {
                // wl_write_log( "wl_push_entity_post_to_redlink : entity has export fields" );
                $predicate = wordlift_esc_sparql($settings['predicate']);
                if (!isset($settings['export_type']) || empty($settings['export_type'])) {
                    $type = null;
                } else {
                    $type = $settings['export_type'];
                }
                foreach (get_post_meta($entity_post->ID, $field) as $value) {
                    $sparql .= " <{$uri_e}> <{$predicate}> ";
                    if (!is_null($type) && substr($type, 0, 4) == 'http') {
                        // Type is defined by a raw uri (es. http://schema.org/PostalAddress)
                        // Extract uri if the value is numeric
                        if (is_numeric($value)) {
                            $value = wl_get_entity_uri($value);
                        }
                        $sparql .= '<' . wl_sparql_escape_uri($value) . '>';
                    } else {
                        // Type is defined in another way (es. xsd:double)
                        $sparql .= '"' . wordlift_esc_sparql($value) . '"^^' . wordlift_esc_sparql($type);
                    }
                    $sparql .= " . \n";
                }
            }
        }
    }
    // Get the entity types.
    $type_uris = wl_get_entity_rdf_types($entity_post->ID);
    // Support type are only schema.org ones: it could be null
    foreach ($type_uris as $type_uri) {
        $type_uri = wl_sparql_escape_uri($type_uri);
        $sparql .= "<{$uri_e}> a <{$type_uri}> . \n";
    }
    // get related entities.
    $related_entities_ids = wl_core_get_related_entity_ids($entity_post->ID);
    if (is_array($related_entities_ids)) {
        foreach ($related_entities_ids as $entity_post_id) {
            $related_entity_uri = wl_sparql_escape_uri(wl_get_entity_uri($entity_post_id));
            // create a two-way relationship.
            $sparql .= " <{$uri_e}> dct:relation <{$related_entity_uri}> . \n";
            $sparql .= " <{$related_entity_uri}> dct:relation <{$uri_e}> . \n";
        }
    }
    // Add SPARQL stmts to write the schema:image.
    $sparql .= wl_get_sparql_images($uri, $entity_post->ID);
    $query = rl_sparql_prefixes() . <<<EOF
    {$delete_stmt}
    DELETE { <{$uri_e}> rdfs:label ?o } WHERE  { <{$uri_e}> rdfs:label ?o };
    DELETE { <{$uri_e}> owl:sameAs ?o . } WHERE  { <{$uri_e}> owl:sameAs ?o . };
    DELETE { <{$uri_e}> schema:description ?o . } WHERE  { <{$uri_e}> schema:description ?o . };
    DELETE { <{$uri_e}> schema:url ?o . } WHERE  { <{$uri_e}> schema:url ?o . };
    DELETE { <{$uri_e}> a ?o . } WHERE  { <{$uri_e}> a ?o . };
    DELETE { <{$uri_e}> dct:relation ?o . } WHERE  { <{$uri_e}> dct:relation ?o . };
    DELETE { <{$uri_e}> schema:image ?o . } WHERE  { <{$uri_e}> schema:image ?o . };
    INSERT DATA { {$sparql} };
EOF;
    rl_execute_sparql_update_query($query);
}
/**
 * Retrieve entity property constraints, starting from the schema.org's property name
 * or from the WL_CUSTOM_FIELD_xxx name.
 *
 * @param $property_name as defined by schema.org or WL internal constants
 *
 * @return array containing constraint(s) or null (in case of error or no constraint).
 */
function wl_get_meta_constraints($property_name)
{
    // Property name must be defined.
    if (!isset($property_name) || is_null($property_name)) {
        return null;
    }
    // store eventual schema name in  different variable
    $property_schema_name = wl_build_full_schema_uri_from_schema_slug($property_name);
    // Get WL taxonomy mapping.
    $types = wl_entity_taxonomy_get_custom_fields();
    // Loop over types
    foreach ($types as $type) {
        // Loop over custom fields of this type
        foreach ($type as $property => $field) {
            if (isset($field['constraints']) && !empty($field['constraints'])) {
                // Is this the property we are searhing for?
                if ($property == $property_name || $field['predicate'] == $property_schema_name) {
                    return $field['constraints'];
                }
            }
        }
    }
    return null;
}
/**
 * Retrieves the list of supported properties for the specified type.
 * @uses wl_entity_taxonomy_get_custom_fields() to retrieve all custom fields (type properties)
 * @uses wl_build_full_schema_uri_from_schema_slug() to convert a schema slug to full uri
 *
 * @param $type_name string Name of the type (e.g. Type, for the http://schema.org/Type)
 *
 * @return array The method returns an array of supported properties for the type, e.g. (‘startDate’, ‘endDate’) for an Event.
 * You can call wl_schema_get_property_expected_type on each to know which data type they expect.
 */
function wl_schema_get_type_properties($type_name)
{
    // Build full schema uri if necessary
    $type_name = wl_build_full_schema_uri_from_schema_slug($type_name);
    // Get all custom fields
    $all_types_and_fields = wl_entity_taxonomy_get_custom_fields();
    $schema_root_address = 'http://schema.org/';
    $type_properties = array();
    // Search for the entity type which has the requested name as uri
    if (isset($all_types_and_fields[$type_name])) {
        foreach ($all_types_and_fields[$type_name] as $field) {
            // Convert to schema slug and store in array
            $type_properties[] = str_replace($schema_root_address, '', $field['predicate']);
        }
    }
    return $type_properties;
}
/**
 * Retrieves the property expected type, according to the schema.org specifications, where:
 * 
 * @param $property_name string Name of the property (e.g. name, for the http://schema.org/name property)
 * 
 * @return array of allowed types or NULL in case of property not found.
 * 
 * The following types are supported (defined as constants):
 * - WL_DATA_TYPE_URI
 * - WL_DATA_TYPE_DATE
 * - WL_DATA_TYPE_INTEGER
 * - WL_DATA_TYPE_DOUBLE
 * - WL_DATA_TYPE_BOOLEAN
 * - WL_DATA_TYPE_STRING
 * - a schema.org URI when the property type supports a schema.org entity (e.g. http://schema.org/Place)
 */
function wl_schema_get_property_expected_type($property_name)
{
    // This is the actual structure of a custom_field.
    /*
     * WL_CUSTOM_FIELD_LOCATION       => array(
     *      'predicate'   => 'http://schema.org/location',
     *      'type'        => WL_DATA_TYPE_URI,
     *      'export_type' => 'http://schema.org/PostalAddress',
     *      'constraints' => array(
     *              'uri_type' => 'Place'
     *      )
     *  )
     */
    // Build full schema uri if necessary
    $property_name = wl_build_full_schema_uri_from_schema_slug($property_name);
    // Get all custom fields
    $all_types_and_fields = wl_entity_taxonomy_get_custom_fields();
    $expected_types = null;
    // Search for the entity type which has the requested name as uri
    $found = false;
    foreach ($all_types_and_fields as $type_fields) {
        foreach ($type_fields as $field) {
            if ($field['predicate'] == $property_name) {
                $expected_types = array();
                // Does the property accept a specific schema type?
                if (isset($field['constraints']) && isset($field['constraints']['uri_type'])) {
                    // Take note of expected schema type
                    $expected_types[] = wl_build_full_schema_uri_from_schema_slug($field['constraints']['uri_type']);
                } else {
                    // Take note of expected type
                    $expected_types[] = $field['type'];
                }
                // We found the property, we can exit the cycles
                $found = true;
            }
            if ($found) {
                break;
            }
        }
        if ($found) {
            break;
        }
    }
    return $expected_types;
}
 /**
  * Tests the *wl_get_meta_constraints* function
  */
 function testEntityGetMetaConstraints()
 {
     // TODO: complete this test
     $fields = wl_entity_taxonomy_get_custom_fields();
 }
 /**
  * Read the WL <-> Schema mapping and build the Fields for the entity being edited.
  *
  * Note: the first function that calls this method will instantiate the fields.
  * Why it isn't called from the constructor? Because we need to hook this process as late as possible.
  *
  * @since 3.1.0
  *
  * @param int $post_id The post id.
  */
 public function instantiate_fields($post_id)
 {
     // This function must be called only once. Not called from the constructor because WP hooks have a rococo ordering
     if (isset($this->fields)) {
         return;
     }
     $entity_type = wl_entity_taxonomy_get_custom_fields($post_id);
     if (isset($entity_type)) {
         /**
          * In some special case, properties must be grouped in one field (e.g. coordinates) or dealed with custom methods.
          * We must divide fields in two groups:
          * - simple: accept values for one property
          * - grouped: accept values for more properties, or for one property that needs a specific metabox.
          */
         $metaboxes = $this->group_properties_by_input_field($entity_type);
         $simple_metaboxes = $metaboxes[0];
         $grouped_metaboxes = $metaboxes[1];
         // Loop over simple entity properties
         foreach ($simple_metaboxes as $key => $property) {
             // Info passed to the metabox
             $info = array();
             $info[$key] = $property;
             // Build the requested field as WL_Metabox_Field_ object
             $this->add_field($info);
         }
         // Loop over grouped properties
         foreach ($grouped_metaboxes as $key => $property) {
             // Info passed to the metabox
             $info = array();
             $info[$key] = $property;
             // Build the requested field group as WL_Metabox_Field_ object
             $this->add_field($info, true);
         }
     }
 }