diff --git a/src/JsonLd/JsonLdNode.php b/src/JsonLd/JsonLdNode.php index 88b7ca8..3e4ae6b 100644 --- a/src/JsonLd/JsonLdNode.php +++ b/src/JsonLd/JsonLdNode.php @@ -6,6 +6,7 @@ use ActivityPub\JsonLd\Dereferencer\DereferencerInterface; use ActivityPub\JsonLd\Exceptions\NodeNotFoundException; use ActivityPub\JsonLd\Exceptions\PropertyNotDefinedException; use ActivityPub\JsonLd\TripleStore\TypedRdfTriple; +use ActivityPub\Utils\Util; use ArrayAccess; use BadMethodCallException; use InvalidArgumentException; @@ -136,7 +137,11 @@ class JsonLdNode implements ArrayAccess { $expandedName = $this->expandName( $name ); if ( property_exists( $this->expanded, $expandedName ) ) { - return $this->resolveProperty( $expandedName, $this->expanded->$expandedName[0] ); + $resolved = $this->resolveProperty( $expandedName, $this->expanded->$expandedName[0] ); + if ( is_array( $resolved ) ) { + $resolved = $resolved[0]; + } + return $resolved; } throw new PropertyNotDefinedException( $name ); } @@ -153,7 +158,11 @@ class JsonLdNode implements ArrayAccess { $expandedName = $this->expandName( $name ); if ( property_exists( $this->expanded, $expandedName ) ) { - return $this->resolveProperty( $expandedName, $this->expanded->$expandedName ); + $resolved = $this->resolveProperty( $expandedName, $this->expanded->$expandedName ); + if ( ! is_array( $resolved ) ) { + $resolved = array( $resolved ); + } + return $resolved; } throw new PropertyNotDefinedException( $name ); } @@ -181,10 +190,11 @@ class JsonLdNode implements ArrayAccess private function resolveProperty( $expandedName, &$property ) { if ( is_array( $property ) ) { - for ( $i = 0; $i < count( $property); $i += 1) { - $names[] = $expandedName; + $properties = []; + foreach ( $property as $subProperty ) { + $properties[] = $this->resolveProperty( $expandedName, $subProperty ); } - return array_map( array( $this, 'resolveProperty'), $names, $property ); + return $properties; } else if ( $property instanceof stdClass && property_exists( $property, '@id') ) { // Lazy-load if we only have the @id property $idProp = '@id'; @@ -201,6 +211,8 @@ class JsonLdNode implements ArrayAccess } return $referencedNode; } else if ( $property instanceof stdClass && property_exists( $property, '@value' ) ) { + // TODO check for an @type and return an appropriate object type if present, e.g. Datetime for dates or + // number for nonNegativeInteger etc. $value = '@value'; return $property->$value; } else if ( $property instanceof stdClass ) { @@ -260,7 +272,7 @@ class JsonLdNode implements ArrayAccess $referencedNode = $this->graph->getNode( $id ); if ( is_null( $referencedNode ) ) { $backrefs = array( $expandedName => array( $this ) ); - $referencedNode = $this->factory->newNode( $expandedValue, $this->graph, $backrefs ); + $this->factory->newNode( $expandedValue, $this->graph, $backrefs ); } else { $referencedNode->addBackReference( $expandedName, $this ); } @@ -323,7 +335,7 @@ class JsonLdNode implements ArrayAccess */ public function isBlankNode() { - return property_exists( $this->expanded, '@id' ); + return ! property_exists( $this->expanded, '@id' ); } /** @@ -357,40 +369,39 @@ class JsonLdNode implements ArrayAccess */ public function toRdfTriples() { - $cloned = clone $this->expanded; - // First serialize this node - $quads = JsonLD::toRdf( $cloned ); + $idProp = '@id'; + $valueProp = '@value'; + $typeProp = '@type'; $triples = array(); - foreach ( $quads as $quad ) { - if ( (string)$quad->getSubject() === $this->getId() ) { - $objectType = null; - if ( $quad->getObject() instanceof Value ) { - $object = $quad->getObject()->getValue(); - if ( $quad->getObject() instanceof TypedValue ) { - $objectType = $quad->getObject()->getType(); - } - } else { - $objectIri = $quad->getObject(); - if ( $objectIri->getScheme() === '_' ) { - // TODO resolve the associated value to force the generation of a UUID, then set $object to the uuid - } else { - $object = (string)$quad->getObject(); - } - $objectType = '@id'; - } - $triples[] = TypedRdfTriple::create( - (string)$quad->getSubject(), (string)$quad->getProperty(), $object, $objectType - ); - } - } - // Then serialize any sub-nodes - foreach ( $this->expanded as $name => $values ) { - if ( $name === '@id' ) { + foreach ( get_object_vars( $this->expanded ) as $attribute => $values ) { + if ( ! is_array( $values ) ) { + // If $values is not an array, this is the @id property + $triples[] = TypedRdfTriple::create( $this->getId(), $attribute, $values ); continue; } - foreach ( $this->getMany( $name ) as $subNode ) { - if ( $subNode instanceof JsonLdNode ) { - $triples = array_merge( $triples, $subNode->toRdfTriples() ); + foreach ( $values as $value ) { + if ( ! is_object( $value ) ) { + // If $value is not an object, this is the @type property + $triples[] = TypedRdfTriple::create( $this->getId(), $attribute, $value ); + } + if ( property_exists( $value, '@id' ) ) { + $triples[] = TypedRdfTriple::create( $this->getId(), $attribute, $value->$idProp, '@id' ); + } else if ( property_exists( $value, '@value' ) ) { + $jsonLdValue = Value::fromJsonLd( $value ); + $triple = TypedRdfTriple::create( $this->getId(), $attribute, $jsonLdValue->getValue() ); + if ( $jsonLdValue instanceof TypedValue ) { + $triple->setObjectType( $jsonLdValue->getType() ); + } + $triples[] = $triple; + } + } + // Check if we should serialize any child nodes + foreach ( $this->getMany( $attribute ) as $childNode ) { + if ( $childNode instanceof JsonLdNode ) { + // Serialize the child node if it is local, i.e. has a local URI or is anonymous + if ( $childNode->isBlankNode() || Util::isLocalUri( $childNode->getId() ) ) { + $triples = array_merge( $triples, $childNode->toRdfTriples() ); + } } } } diff --git a/src/JsonLd/TripleStore/TypedRdfTriple.php b/src/JsonLd/TripleStore/TypedRdfTriple.php index f71baac..415a2e9 100644 --- a/src/JsonLd/TripleStore/TypedRdfTriple.php +++ b/src/JsonLd/TripleStore/TypedRdfTriple.php @@ -4,8 +4,8 @@ namespace ActivityPub\JsonLd\TripleStore; /** * A triple represents a single fact in an RDF graph. A triple is made up a subject, a predicate, and an object. - * The object can also have a type, e.g. @id for references to other resources or - * http://www.w3.org/2001/XMLSchema#dateTime for date-time values. + * The object can also have a type, e.g. "@id" for references to other resources or + * "http://www.w3.org/2001/XMLSchema#dateTime" for date-time values. * * See https://www.w3.org/TR/rdf11-concepts/#data-model. * diff --git a/test/JsonLd/JsonLdNodeTest.php b/test/JsonLd/JsonLdNodeTest.php index e9c9600..0b8ecff 100644 --- a/test/JsonLd/JsonLdNodeTest.php +++ b/test/JsonLd/JsonLdNodeTest.php @@ -11,6 +11,7 @@ use ActivityPub\Test\TestConfig\APTestCase; use ActivityPub\Test\TestUtils\TestUuidProvider; use ActivityPub\Utils\Logger; use stdClass; +use Symfony\Component\HttpFoundation\Request; class JsonLdNodeTest extends APTestCase { @@ -509,9 +510,8 @@ class JsonLdNodeTest extends APTestCase array( TypedRdfTriple::create( 'https://example.org/collections/1', - 'http://www.w3.org/1999/02/22-rdf-syntax-ns#type', - 'https://www.w3.org/ns/activitystreams#Collection', - '@id' + '@id', + 'https://example.org/collections/1' ), TypedRdfTriple::create( 'https://example.org/collections/1', @@ -525,6 +525,26 @@ class JsonLdNodeTest extends APTestCase 'https://example.org/collections/1/items/2', '@id' ), + TypedRdfTriple::create( + 'https://example.org/collections/1/items/1', + '@id', + 'https://example.org/collections/1/items/1' + ), + TypedRdfTriple::create( + 'https://example.org/collections/1/items/1', + '@type', + 'https://www.w3.org/ns/activitystreams#Note', + ), + TypedRdfTriple::create( + 'https://example.org/collections/1/items/2', + '@id', + 'https://example.org/collections/1/items/2' + ), + TypedRdfTriple::create( + 'https://example.org/collections/1/items/2', + '@type', + 'https://www.w3.org/ns/activitystreams#Note', + ), TypedRdfTriple::create( 'https://example.org/collections/1', 'https://www.w3.org/ns/activitystreams#name', @@ -538,16 +558,9 @@ class JsonLdNodeTest extends APTestCase 'http://www.w3.org/2001/XMLSchema#dateTime' ), TypedRdfTriple::create( - 'https://example.org/collections/1/items/1', - 'http://www.w3.org/1999/02/22-rdf-syntax-ns#type', - 'https://www.w3.org/ns/activitystreams#Note', - '@id' - ), - TypedRdfTriple::create( - 'https://example.org/collections/1/items/2', - 'http://www.w3.org/1999/02/22-rdf-syntax-ns#type', - 'https://www.w3.org/ns/activitystreams#Note', - '@id' + 'https://example.org/collections/1', + '@type', + 'https://www.w3.org/ns/activitystreams#Collection', ), ), array( @@ -575,25 +588,29 @@ class JsonLdNodeTest extends APTestCase 'publicKeyPem' => 'the_public_key', ) ), - $this->asContext, + array( 'https://www.w3.org/ns/activitystreams', 'https://w3id.org/security/v1' ), array( TypedRdfTriple::create( 'https://example.org/sally', - 'http://www.w3.org/1999/02/22-rdf-syntax-ns#type', - 'https://www.w3.org/ns/activitystreams#Actor', - '@id' + '@id', + 'https://example.org/sally' + ), + TypedRdfTriple::create( + $this->uuids[0], + 'https://w3id.org/security#publicKeyPem', + 'the_public_key', + 'http://www.w3.org/2001/XMLSchema#string' + ), + TypedRdfTriple::create( + 'https://example.org/sally', + '@type', + 'https://www.w3.org/ns/activitystreams#Actor' ), TypedRdfTriple::create( 'https://example.org/sally', 'https://w3id.org/security/v1#publicKey', $this->uuids[0] ), - TypedRdfTriple::create( - $this->uuids[0], - 'https://w3id.org/security/v1#publicKeyPem', - 'the_public_key', - '@id' - ), ), ), ); @@ -604,6 +621,8 @@ class JsonLdNodeTest extends APTestCase */ public function testToRdfTriple( $inputObj, $context, $expectedTriples, $nodeGraph = array() ) { + $r = Request::create( 'https://example.org' ); + $r->overrideGlobals(); $node = $this->makeJsonLdNode( $inputObj, $context, $nodeGraph ); $triples = $node->toRdfTriples(); $this->assertEquals( $expectedTriples, $triples ); @@ -616,4 +635,4 @@ class JsonLdNodeTest extends APTestCase ); return $factory->newNode( $inputObj ); } -} \ No newline at end of file +}