Commit 8b93e8b7 authored by Sebastien Rosset's avatar Sebastien Rosset

Semantic API

parent a9a7ec4e
......@@ -46,7 +46,17 @@ class ConfigurationFormAdmin extends ConfigurationAbstractAdmin
->add('elementFormGeocodingHelp', 'textarea' ,
array('required' => false,
'label' => "Texte d'aide pour la geolocalisation"))
->end()
->end()
->end()
->tab('Sémantique')
->with('Sémantique', array('class' => 'col-md-12', "description" => "Définir le contexte sémantique des données permet de plus facilement partager les données, afin qu'on puisse proposer un API sous forme de JSON-LD.<br/>Il vous faut aussi définir le type sémantique pour chaque champ que vous voulez partager dans l'onglet Formulaire de cette page."))
->add('elementFormSemanticContext', 'textarea',
array('required' => false, 'attr' => ['placeholder' => 'Exemple: https://schema.org'],
'label' => "Contexte sémantique des éléments", 'label_attr' => ['title' => "Vous pouvez définir plusieurs contextes sous format de JSON"]))
->add('elementFormSemanticType', 'text',
array('required' => false, 'attr' => ['placeholder' => 'Exemple: Place'],
'label' => "Type sémantique des éléments", 'label_attr' => ['title' => "Vous pouvez définir plusieurs types séparés par une virgule"]))
->end()
->end()
;
}
......
......@@ -310,6 +310,11 @@ class Configuration implements \JsonSerializable
/** @MongoDB\Field(type="string") */
protected $elementFormFieldsJson = "[{\"type\":\"taxonomy\",\"label\":\"Choisissez la ou les catégories par ordre d'importance\",\"name\":\"taxonomy\"},{\"type\":\"separator\",\"label\":\"Séparateur de section\",\"name\":\"separator-1539422234804\"},{\"type\":\"header\",\"subtype\":\"h1\",\"label\":\"Informations\"},{\"type\":\"title\",\"required\":true,\"label\":\"Titre de la fiche\",\"name\":\"name\",\"maxlength\":\"80\",\"icon\":\"gogo-icon-account-circle\"},{\"type\":\"textarea\",\"required\":true,\"label\":\"Description courte\",\"name\":\"description\",\"subtype\":\"textarea\",\"maxlength\":\"250\"},{\"type\":\"textarea\",\"label\":\"Description longue\",\"name\":\"descriptionMore\",\"subtype\":\"textarea\",\"maxlength\":\"600\"},{\"type\":\"address\",\"label\":\"Adresse complète\",\"name\":\"address\",\"icon\":\"gogo-icon-marker-symbol\"},{\"type\":\"separator\",\"label\":\"Séparateur de section\",\"name\":\"separator-1539423917238\"},{\"type\":\"header\",\"subtype\":\"h1\",\"label\":\"Contact (optionnel)\"},{\"type\":\"text\",\"subtype\":\"tel\",\"label\":\"Téléphone\",\"name\":\"telephone\"},{\"type\":\"email\",\"label\":\"Mail\",\"name\":\"email\"},{\"type\":\"text\",\"subtype\":\"url\",\"label\":\"Site web\",\"name\":\"website\"},{\"type\":\"separator\",\"label\":\"Séparateur de section\",\"name\":\"separator-1539424058076\"},{\"type\":\"header\",\"subtype\":\"h1\",\"label\":\"Horaires (optionnel)\"},{\"type\":\"openhours\",\"label\":\"Horaires\",\"name\":\"openhours\"}]";
/** @MongoDB\Field(type="string") */
protected $elementFormSemanticContext;
/** @MongoDB\Field(type="string") */
protected $elementFormSemanticType;
// ----------------------------
// -------- IMPORTS -----------
......@@ -3030,4 +3035,48 @@ class Configuration implements \JsonSerializable
{
return str_replace(' ', '', $this->searchExcludingWords);
}
/**
* Set elementFormSemanticContext
*
* @param string $elementFormSemanticContext
* @return $this
*/
public function setElementFormSemanticContext($elementFormSemanticContext)
{
$this->elementFormSemanticContext = $elementFormSemanticContext;
return $this;
}
/**
* Get elementFormSemanticContext
*
* @return string $elementFormSemanticContext
*/
public function getElementFormSemanticContext()
{
return $this->elementFormSemanticContext;
}
/**
* Set elementFormSemanticType
*
* @param string $elementFormSemanticType
* @return $this
*/
public function setElementFormSemanticType($elementFormSemanticType)
{
$this->elementFormSemanticType = $elementFormSemanticType;
return $this;
}
/**
* Get elementFormSemanticType
*
* @return string $elementFormSemanticType
*/
public function getElementFormSemanticType()
{
return $this->elementFormSemanticType;
}
}
......@@ -105,6 +105,7 @@ class ElementAdminShowEdit extends ElementAdminList
->add('baseJson')
->add('privateJson')
->add('adminJson')
->add('semanticJson')
->end();
}
}
\ No newline at end of file
......@@ -2,6 +2,7 @@
namespace Biopen\GeoDirectoryBundle\Controller;
use Biopen\GeoDirectoryBundle\Document\ElementJsonOntology;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Biopen\CoreBundle\Controller\GoGoController;
......@@ -18,7 +19,7 @@ class APIController extends GoGoController
* @bounds
* @categories (ids)
* @stamps (ids)
* @ontology ( gogofull or gogocompact )
* @ontology (gogofull, gogocompact or semantic) -> see ElementJsonOntology
**/
public function getElementsAction(Request $request, $id = null, $_format = 'json')
{
......@@ -26,8 +27,7 @@ class APIController extends GoGoController
$jsonLdRequest = $this->isJsonLdRequest($request, $_format);
$token = $request->get('token');
$ontology = $request->get('ontology') ? strtolower($request->get('ontology')) : "gogofull";
$fullRepresentation = $jsonLdRequest || $ontology != "gogocompact";
$ontology = $jsonLdRequest ? ElementJsonOntology::Semantic : ( $request->get('ontology') ? strtolower($request->get('ontology')) : ElementJsonOntology::Full );
$elementId = $id ? $id : $request->get('id');
$config = $em->getRepository('BiopenCoreBundle:Configuration')->findConfiguration();
$protectWithToken = $config->getApi()->getProtectPublicApiWithToken();
......@@ -62,7 +62,10 @@ class APIController extends GoGoController
if ($elementId)
{
$element = $elementRepo->findOneBy(array('id' => $elementId));
$elementsJson = $element ? $element->getJson($includePrivateFields, $isAdmin) : null;
$elementsJson = $element ? $element->getJson($ontology, $includePrivateFields, $isAdmin) : null;
if( $elementsJson && $ontology === ElementJsonOntology::Semantic ) {
$elementsJson = $this->appendSemanticContextAndType($elementsJson);
}
}
else
{
......@@ -71,13 +74,13 @@ class APIController extends GoGoController
$boxes = [];
$bounds = explode( ';' , $request->get('bounds'));
foreach ($bounds as $key => $bound) $boxes[] = explode( ',' , $bound);
$elementsFromDB = $elementRepo->findWhithinBoxes($boxes, $request, $fullRepresentation, $isAdmin);
$elementsFromDB = $elementRepo->findWhithinBoxes($boxes, $request, $ontology, $isAdmin);
}
else
{
$elementsFromDB = $elementRepo->findAllPublics($fullRepresentation, $isAdmin, $request);
$elementsFromDB = $elementRepo->findAllPublics($ontology, $isAdmin, $request);
}
$elementsJson = $this->encodeElementArrayToJsonArray($elementsFromDB, $fullRepresentation, $isAdmin, $includePrivateFields);
$elementsJson = $this->encodeElementArrayToJsonArray($elementsFromDB, $ontology, $isAdmin, $includePrivateFields, $config);
}
$status = 200;
......@@ -88,10 +91,7 @@ class APIController extends GoGoController
}
else if ($jsonLdRequest)
{
$responseJson = '{
"@context" : "https://rawgit.com/jmvanel/rdf-convert/master/context-gogo.jsonld",
"@graph" : '. $elementsJson . '
}';
$responseJson = $elementsJson;
}
else
{
......@@ -99,7 +99,7 @@ class APIController extends GoGoController
"licence": "' . $config->getDataLicenseUrl() . '",
"ontology":"'. $ontology . '"';
if (!$fullRepresentation)
if ($ontology === ElementJsonOntology::Compact )
{
$mapping = ['id', $config->getMarker()->getFieldsUsedByTemplate(), 'latitude', 'longitude', 'status', 'moderationState'];
$responseJson .= ', "mapping":' . json_encode($mapping);
......@@ -185,8 +185,8 @@ class APIController extends GoGoController
$elements = $em->getRepository('BiopenGeoDirectoryBundle:Element')->findElementsWithText($request->get('text'), true, $isAdmin);
$elementsJson = $this->encodeElementArrayToJsonArray($elements, true, $isAdmin, true);
$responseJson = '{ "data":'. $elementsJson . ', "ontology" : "gogofull"}';
$elementsJson = $this->encodeElementArrayToJsonArray($elements, ElementJsonOntology::Full, $isAdmin, true);
$responseJson = '{ "data":'. $elementsJson . ', "ontology" : "' . ElementJsonOntology::Full . '"}';
$config = $em->getRepository('BiopenCoreBundle:Configuration')->findConfiguration();
return $this->createResponse($responseJson, $config);
......@@ -204,28 +204,40 @@ class APIController extends GoGoController
return false;
}
private function encodeElementArrayToJsonArray($array, $fullRepresentation, $isAdmin = false, $includePrivateFields = false)
private function encodeElementArrayToJsonArray($array, $ontology, $isAdmin = false, $includePrivateFields = false, $config = null )
{
$elementsJson = '[';
$elementsJson = '[';
foreach ($array as $key => $value)
{
if ($fullRepresentation == 'true')
{
$elementJson = $value['baseJson'];
if ($includePrivateFields && $value['privateJson'] != '{}') {
$elementJson = substr($elementJson , 0, -1) . ',' . substr($value['privateJson'],1);
}
if ($isAdmin && $value['adminJson'] != '{}') {
$elementJson = substr($elementJson , 0, -1) . ',' . substr($value['adminJson'],1);
}
if (key_exists('score', $value)) {
// remove first '{'
$elementJson = substr($elementJson, 1);
$elementJson = '{"searchScore" : ' . $value['score'] . ',' . $elementJson;
{
switch($ontology) {
case ElementJsonOntology::Full:
$elementJson = $value['baseJson'];
if ($includePrivateFields && $value['privateJson'] != '{}') {
$elementJson = substr($elementJson , 0, -1) . ',' . substr($value['privateJson'],1);
}
if ($isAdmin && $value['adminJson'] != '{}') {
$elementJson = substr($elementJson , 0, -1) . ',' . substr($value['adminJson'],1);
}
if (key_exists('score', $value)) {
// remove first '{'
$elementJson = substr($elementJson, 1);
$elementJson = '{"searchScore" : ' . $value['score'] . ',' . $elementJson;
}
break;
case ElementJsonOntology::Compact:
$elementJson = $value['compactJson'];
break;
case ElementJsonOntology::Semantic:
$elementJson = $value['semanticJson'];
$elementJson = $this->appendSemanticContextAndType($elementJson);
break;
default:
throw new \Exception('Unknown ontology : ' . $ontology);
}
}
else $elementJson = $value['compactJson'];
$elementsJson .= $elementJson . ',';
$elementsJson .= $elementJson . ',';
}
$elementsJson = rtrim($elementsJson,",") . ']';
......@@ -241,5 +253,16 @@ class APIController extends GoGoController
return $this->createResponse(json_encode($gogocartoConf), $config);
}
public function appendSemanticContextAndType($semanticJson)
{
$em = $this->get('doctrine_mongodb')->getManager();
$config = $em->getRepository('BiopenCoreBundle:Configuration')->findConfiguration();
return '{
"@context" : "' . $config->getElementFormSemanticContext() . '",
"@type" : "' . $config->getElementFormSemanticType() . '",
' . substr($semanticJson, 1);
}
}
\ No newline at end of file
......@@ -302,6 +302,15 @@ class Element
*/
private $adminJson;
/**
* @var string
*
* The Json representation of all elements semantically-defined
*
* @MongoDB\Field(type="string")
*/
private $semanticJson;
/**
* @var date $createdAt
*
......@@ -505,14 +514,26 @@ class Element
public function isDynamicImported() { return $this->isExternal; }
public function getJson($includePrivateJson, $includeAdminJson)
public function getJson($ontology, $includePrivateJson, $includeAdminJson)
{
$result = $this->baseJson;
if ($includePrivateJson && $this->privateJson && $this->privateJson != '{}')
$result = substr($result , 0, -1) . ',' . substr($this->privateJson,1);
if ($includeAdminJson && $this->adminJson && $this->adminJson != '{}')
$result = substr($result , 0, -1) . ',' . substr($this->adminJson,1);
return $result;
switch($ontology) {
case ElementJsonOntology::Full:
$result = $this->baseJson;
if ($includePrivateJson && $this->privateJson && $this->privateJson != '{}')
$result = substr($result , 0, -1) . ',' . substr($this->privateJson,1);
if ($includeAdminJson && $this->adminJson && $this->adminJson != '{}')
$result = substr($result , 0, -1) . ',' . substr($this->adminJson,1);
return $result;
case ElementJsonOntology::Compact:
return $this->compactJson;
case ElementJsonOntology::Semantic:
return $this->semanticJson;
default:
throw new \Exception('Unknown ontology : ' . $ontology);
}
}
public function isPending()
......@@ -813,6 +834,28 @@ class Element
return $this->baseJson;
}
/**
* Set semanticJson
*
* @param string $semanticJson
* @return $this
*/
public function setSemanticJson($semanticJson)
{
$this->semanticJson = $semanticJson;
return $this;
}
/**
* Get semanticJson
*
* @return string $semanticJson
*/
public function getSemanticJson()
{
return $this->semanticJson;
}
/**
* Add report
*
......
<?php
namespace Biopen\GeoDirectoryBundle\Document;
abstract class ElementJsonOntology
{
const Full = 'gogofull';
const Compact = 'gogocompact';
const Semantic = 'semantic';
}
......@@ -3,18 +3,36 @@
namespace Biopen\GeoDirectoryBundle\EventListener;
use Biopen\GeoDirectoryBundle\Document\Element;
use Biopen\GeoDirectoryBundle\Document\ElementJsonOntology;
use Biopen\GeoDirectoryBundle\Document\ModerationState;
use Biopen\GeoDirectoryBundle\Document\ElementStatus;
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
use Symfony\Component\Routing\Router;
class ElementJsonGenerator
{
protected $currElementChangeset;
protected $config = null;
protected $options = null;
protected $semanticFields = [];
protected $router = null;
public function __construct(Router $router)
{
$this->router = $router;
}
public function getConfig($dm)
{
if (!$this->config) $this->config = $dm->getRepository('BiopenCoreBundle:Configuration')->findConfiguration();
$formFields = $this->config->getElementFormFields();
foreach( $formFields as $key => $field ) {
if( isset($field->semantic) && $field->semantic != "" ) {
$this->semanticFields[$field->name] = $field->semantic;
}
}
return $this->config;
}
......@@ -115,7 +133,7 @@ class ElementJsonGenerator
// MODIFIED ELEMENT (for pending modification)
if ($element->getModifiedElement()) {
$baseJson .= ', "modifiedElement": ' . $element->getModifiedElement()->getJson(true, false);
$baseJson .= ', "modifiedElement": ' . $element->getModifiedElement()->getJson(ElementJsonOntology::Full, true, false);
}
$baseJson .= '}';
......@@ -179,6 +197,24 @@ class ElementJsonGenerator
if ($element->getModerationState() != 0) $compactJson .= ','. $element->getModerationState();
$compactJson .= ']';
$element->setCompactJson($compactJson);
// -------------------- SEMANTIC JSON ----------------
$semanticJson = "{";
$semanticJson .= '"@id": "' . $this->router->generate('biopen_api_element_get', array('id'=>$element->id, '_format'=>'jsonld'), UrlGeneratorInterface::ABSOLUTE_URL) . '",';
if ($element->getData()) {
foreach ($element->getData() as $key => $value) {
if( array_key_exists($key, $this->semanticFields) && $value ) {
$semanticJson .= '"' . $this->semanticFields[$key] . '": ' . json_encode($value) . ',';
}
}
}
$semanticJson = rtrim($semanticJson, ',');
$semanticJson .= '}';
$element->setSemanticJson($semanticJson);
}
// private function attrChanged($attrs)
......
......@@ -12,6 +12,7 @@
namespace Biopen\GeoDirectoryBundle\Repository;
use Biopen\GeoDirectoryBundle\Document\ElementJsonOntology;
use Doctrine\ODM\MongoDB\DocumentRepository;
use Biopen\GeoDirectoryBundle\Document\ElementStatus;
use Biopen\GeoDirectoryBundle\Document\ModerationState;
......@@ -46,7 +47,7 @@ class ElementRepository extends DocumentRepository
->hydrate($hydrate)->getQuery()->execute()->toArray();
}
public function findWhithinBoxes($bounds, $request, $getFullRepresentation, $isAdmin = false)
public function findWhithinBoxes($bounds, $request, $ontology, $isAdmin = false)
{
$qb = $this->createQueryBuilder('BiopenGeoDirectoryBundle:Element');
......@@ -59,7 +60,7 @@ class ElementRepository extends DocumentRepository
$qb->addOr($qb->expr()->field('geo')->withinBox((float) $bound[1], (float) $bound[0], (float) $bound[3], (float) $bound[2]));
if ($request) $this->filterWithRequest($qb, $request);
$this->selectJson($qb, $getFullRepresentation, $isAdmin);
$this->selectJson($qb, $ontology, $isAdmin);
// execute request
$results = $this->queryToArray($qb);
......@@ -135,7 +136,7 @@ class ElementRepository extends DocumentRepository
return $qb->getQuery()->execute();
}
public function findAllPublics($getFullRepresentation, $isAdmin, $request = null)
public function findAllPublics($ontology, $isAdmin, $request = null)
{
$qb = $this->createQueryBuilder('BiopenGeoDirectoryBundle:Element');
......@@ -143,7 +144,7 @@ class ElementRepository extends DocumentRepository
$qb->field('moderationState')->equals(ModerationState::NotNeeded);
if ($request) $this->filterWithRequest($qb, $request);
$this->selectJson($qb, $getFullRepresentation, $isAdmin);
$this->selectJson($qb, $ontology, $isAdmin);
return $this->queryToArray($qb);
}
......@@ -208,17 +209,24 @@ class ElementRepository extends DocumentRepository
return $qb;
}
private function selectJson($qb, $getFullRepresentation, $isAdmin)
private function selectJson($qb, $ontology, $isAdmin)
{
// get json representation
if ($getFullRepresentation == 'true')
{
$qb->select(['baseJson', 'privateJson']);
if ($isAdmin) $qb->select('adminJson');
}
else
{
$qb->select('compactJson');
switch($ontology) {
case ElementJsonOntology::Full:
$qb->select(['baseJson', 'privateJson']);
if ($isAdmin) $qb->select('adminJson');
break;
case ElementJsonOntology::Compact:
$qb->select('compactJson');
break;
case ElementJsonOntology::Semantic:
$qb->select('semanticJson');
break;
default:
throw new \Exception('Unknown ontology: ' . $ontology);
}
}
......
......@@ -48,6 +48,7 @@ services:
biopen.element_json_generator:
class: Biopen\GeoDirectoryBundle\EventListener\ElementJsonGenerator
arguments: [ "@router" ]
tags:
- { name: doctrine_mongodb.odm.event_listener, event: preFlush }
......
......@@ -40,6 +40,7 @@
var iconAttr = { label: 'Icone', placeholder: 'Choisissez une icone' }
var errorMsgAttrs = { label: "Msg. Erreur", placeholder: "Oups ce texte est un peu long ! // Veuillez renseigner une adresse email valide // ..." }
var semanticAttrs = { label: "Champ sémantique" }
var typeUserAttrs = {
text: {
icon: iconAttr,
......@@ -51,12 +52,14 @@
'url': 'Url'
},
},
errorMsg: errorMsgAttrs
errorMsg: errorMsgAttrs,
semantic: semanticAttrs
},
textarea: {
icon: iconAttr,
errorMsg: errorMsgAttrs,
separator: { label: '' }, // separate important attrs from others
semantic: semanticAttrs,
},
select: { icon: iconAttr, errorMsg: errorMsgAttrs },
number: { icon: iconAttr, errorMsg: errorMsgAttrs },
......@@ -65,6 +68,7 @@
icon: iconAttr,
errorMsg: errorMsgAttrs,
separator: { label: '' }, // separate important attrs from others
semantic: semanticAttrs,
},
address: { icon: iconAttr },
'checkbox-group': {
......@@ -83,20 +87,24 @@
icon: iconAttr,
errorMsg: errorMsgAttrs,
separator: { label: '' }, // separate important attrs from others
semantic: semanticAttrs
},
image: {
icon: iconAttr,
errorMsg: errorMsgAttrs,
separator: { label: '' }, // separate important attrs from others
semantic: semanticAttrs
},
images: {
icon: iconAttr,
separator: { label: '' }, // separate important attrs from others
semantic: semanticAttrs
},
files: {
icon: iconAttr,
accept: { label: "Fichier acceptés", placeholder: ".pdf audio/* .mp3 (séparés par des espaces)"},
separator: { label: '' }, // separate important attrs from others
semantic: semanticAttrs
}
};
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment