├── .gitignore
├── tests
├── Stubs
│ └── ContextStub.php
├── ContextTypes
│ ├── ContactPointTest.php
│ ├── AggregateRatingTest.php
│ ├── CorporationTest.php
│ ├── AbstractContextTest.php
│ ├── PlaceTest.php
│ ├── PersonSimpleAddressTest.php
│ ├── BlogPostingTest.php
│ ├── Organization2ContactsTest.php
│ ├── MusicGroupTest.php
│ ├── MusicPlaylistTest.php
│ ├── OrganizationTest.php
│ ├── EventTest.php
│ ├── MusicRecordingTest.php
│ ├── SculptureTest.php
│ ├── WebSiteTest.php
│ ├── RecipeTest.php
│ ├── MusicAlbumTest.php
│ ├── PersonTest.php
│ ├── ProductTest.php
│ ├── BookTest.php
│ ├── CreativeWorkTest.php
│ ├── AudioBookTest.php
│ └── ArticleTest.php
├── TestCase.php
└── ContextTest.php
├── src
├── Contracts
│ └── ContextTypeInterface.php
├── ContextTypes
│ ├── BlogPosting.php
│ ├── GeoCoordinates.php
│ ├── PriceSpecification.php
│ ├── Duration.php
│ ├── ContactPoint.php
│ ├── QuantitativeValue.php
│ ├── MusicPlaylist.php
│ ├── ImageObject.php
│ ├── PostalAddress.php
│ ├── MusicGroup.php
│ ├── Rating.php
│ ├── AggregateRating.php
│ ├── Invoice.php
│ ├── MusicAlbum.php
│ ├── NutritionInformation.php
│ ├── Order.php
│ ├── Offer.php
│ ├── Beach.php
│ ├── Review.php
│ ├── WebSite.php
│ ├── WebPage.php
│ ├── ListItem.php
│ ├── Sculpture.php
│ ├── Corporation.php
│ ├── Comment.php
│ ├── BreadcrumbList.php
│ ├── Place.php
│ ├── SearchBox.php
│ ├── VideoObject.php
│ ├── Enumeration.php
│ ├── Audiobook.php
│ ├── NewsArticle.php
│ ├── LocalBusiness.php
│ ├── Book.php
│ ├── Thing.php
│ ├── Recipe.php
│ ├── Event.php
│ ├── Organization.php
│ ├── Product.php
│ ├── MusicRecording.php
│ ├── Article.php
│ ├── MediaObject.php
│ ├── BookFormatType.php
│ ├── Person.php
│ ├── MusicAbstractContext.php
│ ├── CreativeWork.php
│ └── AbstractContext.php
└── Context.php
├── .travis.yml
├── phpunit.xml
├── composer.json
├── docs
├── search-box.md
└── breadcrumbs.md
├── LICENSE
└── README.md
/.gitignore:
--------------------------------------------------------------------------------
1 | /vendor
2 | composer.phar
3 | composer.lock
4 | .DS_Store
5 | .idea*
6 |
--------------------------------------------------------------------------------
/tests/Stubs/ContextStub.php:
--------------------------------------------------------------------------------
1 | CreativeWork::class,
14 | ];
15 | }
--------------------------------------------------------------------------------
/src/ContextTypes/GeoCoordinates.php:
--------------------------------------------------------------------------------
1 | '',
14 | 'longitude' => '',
15 | ];
16 | }
--------------------------------------------------------------------------------
/src/ContextTypes/PriceSpecification.php:
--------------------------------------------------------------------------------
1 | null,
14 | 'priceCurrency' => null,
15 | ];
16 | }
17 |
--------------------------------------------------------------------------------
/src/ContextTypes/Duration.php:
--------------------------------------------------------------------------------
1 | null,
14 | 'name' => null,
15 | 'image' => null,
16 | ];
17 | }
--------------------------------------------------------------------------------
/src/ContextTypes/ContactPoint.php:
--------------------------------------------------------------------------------
1 | null,
14 | 'contactType' => null,
15 | 'email' => null,
16 | ];
17 | }
18 |
--------------------------------------------------------------------------------
/src/ContextTypes/QuantitativeValue.php:
--------------------------------------------------------------------------------
1 | null,
14 | 'minValue' => null,
15 | 'value' => null,
16 | 'unitText' => null,
17 | ];
18 | }
--------------------------------------------------------------------------------
/src/ContextTypes/MusicPlaylist.php:
--------------------------------------------------------------------------------
1 | null,
14 | 'url' => null,
15 | 'name' => null,
16 | 'numTracks' => null,
17 | 'track' => null,
18 | ];
19 | }
20 |
--------------------------------------------------------------------------------
/src/ContextTypes/ImageObject.php:
--------------------------------------------------------------------------------
1 | null,
14 | 'height' => null,
15 | 'width' => null,
16 | 'caption' => null,
17 | 'thumbnail' => ImageObject::class,
18 | ];
19 | }
--------------------------------------------------------------------------------
/src/ContextTypes/PostalAddress.php:
--------------------------------------------------------------------------------
1 | '',
14 | 'addressLocality' => '',
15 | 'addressRegion' => '',
16 | 'addressCountry' => '',
17 | 'postalCode' => '',
18 | ];
19 | }
--------------------------------------------------------------------------------
/src/ContextTypes/MusicGroup.php:
--------------------------------------------------------------------------------
1 | null,
14 | 'url' => null,
15 | 'name' => null,
16 | 'description' => null,
17 | 'track' => null,
18 | 'image' => null,
19 | ];
20 | }
21 |
--------------------------------------------------------------------------------
/src/ContextTypes/Rating.php:
--------------------------------------------------------------------------------
1 | null,
14 | 'bestRating' => null, // Required if the rating system is not on a 5-point scale
15 | 'worstRating' => null, // Required if the rating system is not on a 5-point scale
16 | ];
17 | }
--------------------------------------------------------------------------------
/src/ContextTypes/AggregateRating.php:
--------------------------------------------------------------------------------
1 | null,
14 | 'ratingValue' => null,
15 | 'bestRating' => null,
16 | 'worstRating' => null,
17 | 'ratingCount' => null,
18 | 'itemReviewed' => Thing::class,
19 | ];
20 | }
21 |
--------------------------------------------------------------------------------
/src/ContextTypes/Invoice.php:
--------------------------------------------------------------------------------
1 | PriceSpecification::class,
14 | 'provider' => Organization::class,
15 | 'paymentDueDate' => null,
16 | 'paymentStatus' => null,
17 | 'url' => null,
18 | 'referencesOrder' => Order::class
19 | ];
20 | }
21 |
--------------------------------------------------------------------------------
/src/ContextTypes/MusicAlbum.php:
--------------------------------------------------------------------------------
1 | null,
14 | 'url' => null,
15 | 'name' => null,
16 | 'description' => null,
17 | 'numTracks' => null,
18 | 'byArtist' => null,
19 | 'genre' => null,
20 | 'track' => null,
21 | 'image' => null,
22 | ];
23 | }
24 |
--------------------------------------------------------------------------------
/src/ContextTypes/NutritionInformation.php:
--------------------------------------------------------------------------------
1 | null,
14 | 'fatContent' => null,
15 | 'cholesterolContent' => null,
16 | 'sodiumContent' => null,
17 | 'carbohydrateContent' => null,
18 | 'fiberContent' => null,
19 | 'proteinContent' => null,
20 | ];
21 | }
22 |
--------------------------------------------------------------------------------
/src/ContextTypes/Order.php:
--------------------------------------------------------------------------------
1 | Organization::class,
14 | 'orderNumber' => null,
15 | 'orderStatus' => null,
16 | 'priceCurrency' => null,
17 | 'price' => null,
18 | 'acceptedOffer' => Offer::class,
19 | 'url' => null,
20 | 'priceSpecification.name' => null
21 | ];
22 | }
23 |
--------------------------------------------------------------------------------
/src/ContextTypes/Offer.php:
--------------------------------------------------------------------------------
1 | Product::class,
14 | 'price' => null,
15 | 'priceCurrency' => null,
16 | 'priceValidUntil' => null,
17 | 'url' => null,
18 | 'itemCondition' => null,
19 | 'availability' => null,
20 | 'eligibleQuantity' => QuantitativeValue::class,
21 | ];
22 | }
23 |
--------------------------------------------------------------------------------
/src/ContextTypes/Beach.php:
--------------------------------------------------------------------------------
1 | null,
14 | 'openingHours' => null,
15 | 'description' => null,
16 | 'image' => null,
17 | 'url' => null,
18 | 'address' => PostalAddress::class,
19 | 'geo' => GeoCoordinates::class,
20 | 'review' => Review::class,
21 | 'aggregateRating' => AggregateRating::class,
22 | ];
23 | }
24 |
--------------------------------------------------------------------------------
/src/ContextTypes/Review.php:
--------------------------------------------------------------------------------
1 | Thing::class,
14 | 'reviewRating' => Rating::class,
15 | 'aggregateRating' => AggregateRating::class,
16 | 'name' => null,
17 | 'author' => Person::class,
18 | 'reviewBody' => null,
19 | 'publisher' => Organization::class,
20 | 'duration' => Duration::class,
21 | 'datePublished' => null,
22 | ];
23 | }
--------------------------------------------------------------------------------
/src/ContextTypes/WebSite.php:
--------------------------------------------------------------------------------
1 | null,
14 | 'headline' => null,
15 | 'image' => null,
16 | 'name' => null,
17 | 'url' => null,
18 | 'publisher' => Organization::class,
19 | 'keywords' => null,
20 | 'inLanguage' => null,
21 | 'dateCreated' => null,
22 | 'dateModified' => null,
23 | 'datePublished' => null,
24 | 'sameAs' => null,
25 | ];
26 | }
--------------------------------------------------------------------------------
/src/ContextTypes/WebPage.php:
--------------------------------------------------------------------------------
1 | null,
14 | 'url' => null,
15 | ];
16 |
17 | /**
18 | * Set the canonical URL of the article page.
19 | *
20 | * @param string $url
21 | *
22 | * @return array
23 | */
24 | protected function setUrlAttribute($url)
25 | {
26 | // The URL is used as an ID
27 | $this->properties['@id'] = $url;
28 |
29 | return null;
30 | }
31 | }
--------------------------------------------------------------------------------
/src/ContextTypes/ListItem.php:
--------------------------------------------------------------------------------
1 | null,
14 | 'item' => null,
15 | ];
16 |
17 | /**
18 | * Set item in list.
19 | *
20 | * @param array $item
21 | *
22 | * @return array
23 | */
24 | protected function setItemAttribute($item)
25 | {
26 | return [
27 | '@id' => $this->getArrValue($item, 'url'),
28 | 'name' => $this->getArrValue($item, 'name')
29 | ];
30 | }
31 | }
--------------------------------------------------------------------------------
/src/ContextTypes/Sculpture.php:
--------------------------------------------------------------------------------
1 | structure, $this->extendedStructure, $extendedStructure)
24 | );
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/src/ContextTypes/Corporation.php:
--------------------------------------------------------------------------------
1 | null,
14 | ];
15 |
16 | /**
17 | * Corporation constructor. Merges extendedStructure up
18 | *
19 | * @param array $attributes
20 | * @param array $extendedStructure
21 | */
22 | public function __construct(array $attributes, array $extendedStructure = [])
23 | {
24 | parent::__construct(
25 | $attributes, array_merge($this->structure, $this->extendedStructure, $extendedStructure)
26 | );
27 | }
28 | }
--------------------------------------------------------------------------------
/phpunit.xml:
--------------------------------------------------------------------------------
1 |
2 |
14 |
15 |
16 | ./src
17 |
18 |
19 |
20 |
21 | ./tests
22 |
23 |
24 |
--------------------------------------------------------------------------------
/src/ContextTypes/Comment.php:
--------------------------------------------------------------------------------
1 | null,
17 | 'upvoteCount' => null,
18 | ];
19 |
20 | /**
21 | * Constructor. Merges extendedStructure up
22 | *
23 | * @param array $attributes
24 | * @param array $extendedStructure
25 | */
26 | public function __construct(array $attributes, array $extendedStructure = [])
27 | {
28 | parent::__construct(
29 | $attributes, array_merge($this->structure, $this->extendedStructure, $extendedStructure)
30 | );
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/src/ContextTypes/BreadcrumbList.php:
--------------------------------------------------------------------------------
1 | null,
14 | ];
15 |
16 | /**
17 | * Set the canonical URL of the article page.
18 | *
19 | * @param array $items
20 | *
21 | * @return array
22 | */
23 | protected function setItemListElementAttribute($items)
24 | {
25 | foreach ($items as $pos => $item) {
26 | $items[$pos] = $this->getNestedContext(ListItem::class, [
27 | 'position' => $pos + 1,
28 | 'item' => $item
29 | ]);
30 | }
31 |
32 | return $items;
33 | }
34 | }
--------------------------------------------------------------------------------
/src/ContextTypes/Place.php:
--------------------------------------------------------------------------------
1 | PostalAddress::class,
14 | 'review' => Review::class,
15 | 'aggregateRating' => AggregateRating::class,
16 | 'geo' => GeoCoordinates::class,
17 | ];
18 |
19 | /**
20 | * Constructor. Merges extendedStructure up
21 | *
22 | * @param array $attributes
23 | * @param array $extendedStructure
24 | */
25 | public function __construct(array $attributes, array $extendedStructure = [])
26 | {
27 | parent::__construct($attributes, array_merge($this->structure, $this->extendedStructure, $extendedStructure));
28 | }
29 |
30 | }
31 |
--------------------------------------------------------------------------------
/src/ContextTypes/SearchBox.php:
--------------------------------------------------------------------------------
1 | null,
14 | 'potentialAction' => null,
15 | ];
16 |
17 | /**
18 | * After fill event.
19 | *
20 | * @param array $attributes
21 | */
22 | public function afterFill($attributes)
23 | {
24 | $this->setType('WebSite');
25 | }
26 |
27 | /**
28 | * Set potential action.
29 | *
30 | * @param array $properties
31 | *
32 | * @return array
33 | */
34 | protected function setPotentialActionAttribute($properties)
35 | {
36 | return array_merge(['@type' => 'SearchAction'], $properties);
37 | }
38 | }
--------------------------------------------------------------------------------
/src/ContextTypes/VideoObject.php:
--------------------------------------------------------------------------------
1 | Person::class,
14 | 'director' => Person::class,
15 | 'associatedArticle' => NewsArticle::class,
16 | 'bitrate' => null,
17 | 'contentSize' => null,
18 | 'contentUrl' => null,
19 | 'duration' => null,
20 | 'embedUrl' => null,
21 | 'url' => null,
22 | 'height' => null,
23 | 'width' => null,
24 | 'uploadDate' => null,
25 | 'caption' => null,
26 | 'thumbnail' => ImageObject::class,
27 | 'description' => null,
28 | 'thumbnailUrl' => null,
29 | 'name' => null,
30 | ];
31 | }
32 |
--------------------------------------------------------------------------------
/composer.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "torann/json-ld",
3 | "description": "Extremely simple JSON-LD markup generator.",
4 | "keywords": ["json-ld", "generator", "schema", "structured-data"],
5 | "license": "BSD-2-Clause",
6 | "authors": [
7 | {
8 | "name": "Daniel Stainback",
9 | "email": "torann@gmail.com"
10 | }
11 | ],
12 | "require": {
13 | "php": ">=5.5"
14 | },
15 | "require-dev": {
16 | "phpunit/phpunit": "~5.0",
17 | "mockery/mockery": "^0.9.4"
18 | },
19 | "autoload": {
20 | "psr-4": {
21 | "JsonLd\\": "src"
22 | }
23 | },
24 | "autoload-dev": {
25 | "psr-4": {
26 | "JsonLd\\Test\\": "tests"
27 | }
28 | },
29 | "extra": {
30 | "branch-alias": {
31 | "dev-master": "0.1-dev"
32 | }
33 | },
34 | "archive" : {
35 | "exclude": [
36 | "/docs",
37 | "/tests"
38 | ]
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/src/ContextTypes/Enumeration.php:
--------------------------------------------------------------------------------
1 | Enumeration::class,
19 | ];
20 |
21 | /**
22 | * Constructor. Merges extendedStructure up
23 | *
24 | * @param array $attributes
25 | * @param array $extendedStructure
26 | */
27 | public function __construct(array $attributes, array $extendedStructure = [])
28 | {
29 | parent::__construct(
30 | $attributes, array_merge($this->structure, $this->extendedStructure, $extendedStructure)
31 | );
32 | }
33 | }
--------------------------------------------------------------------------------
/tests/ContextTypes/ContactPointTest.php:
--------------------------------------------------------------------------------
1 | '18009999999',
14 | 'contactType' => 'customer service',
15 | ];
16 |
17 | /**
18 | * @test
19 | */
20 | public function shouldHaveTelephone()
21 | {
22 | $context = $this->make();
23 |
24 | $this->assertEquals('18009999999', $context->getProperty('telephone'));
25 | }
26 |
27 | /**
28 | * @test
29 | */
30 | public function shouldHaveContactType()
31 | {
32 | $context = $this->make();
33 |
34 | $this->assertEquals('customer service', $context->getProperty('contactType'));
35 | }
36 |
37 | }
38 |
--------------------------------------------------------------------------------
/src/ContextTypes/Audiobook.php:
--------------------------------------------------------------------------------
1 | MediaObject::class,
17 | 'transcript' => null,
18 | 'duration' => Duration::class,
19 | 'readBy' => Person::class,
20 | ];
21 |
22 | /**
23 | * Constructor. Merges extendedStructure up
24 | *
25 | * @param array $attributes
26 | * @param array $extendedStructure
27 | */
28 | public function __construct(array $attributes, array $extendedStructure = [])
29 | {
30 | parent::__construct(
31 | $attributes, array_merge($this->structure, $this->extendedStructure, $extendedStructure)
32 | );
33 | }
34 | }
--------------------------------------------------------------------------------
/src/ContextTypes/NewsArticle.php:
--------------------------------------------------------------------------------
1 | null,
14 | 'description' => null,
15 | 'url' => null,
16 | 'mainEntityOfPage' => WebPage::class,
17 | 'image' => ImageObject::class,
18 | 'video' => VideoObject::class,
19 | 'dateCreated' => null,
20 | 'dateModified' => null,
21 | 'datePublished' => null,
22 | 'author' => Person::class,
23 | 'publisher' => Organization::class,
24 | 'articleBody' => null,
25 | ];
26 |
27 | /**
28 | * Set the description attribute.
29 | *
30 | * @param string $txt
31 | *
32 | * @return string
33 | */
34 | protected function setDescriptionAttribute($txt)
35 | {
36 | return $this->truncate($txt, 260);
37 | }
38 | }
--------------------------------------------------------------------------------
/tests/ContextTypes/AggregateRatingTest.php:
--------------------------------------------------------------------------------
1 | 'http://google.com/profile',
13 | 'reviewCount' => 5,
14 | 'ratingValue' => 2.4,
15 | 'bestRating' => 4.5,
16 | 'worstRating' => 1,
17 | 'ratingCount' => 4,
18 | 'itemReviewed' => [
19 | '@type' => 'Thing',
20 | 'name' => 'Fluff Hut',
21 | ],
22 | ];
23 |
24 | /**
25 | * @test
26 | */
27 | public function shouldGetProperties()
28 | {
29 | $context = $this->make();
30 |
31 | $this->assertEquals(array_merge([
32 | '@context' => 'http://schema.org',
33 | '@type' => 'AggregateRating',
34 | ], $this->attributes), $context->getProperties());
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/src/ContextTypes/LocalBusiness.php:
--------------------------------------------------------------------------------
1 | null,
14 | 'description' => null,
15 | 'image' => null,
16 | 'telephone' => null,
17 | 'email' => null,
18 | 'openingHours' => null,
19 | 'address' => PostalAddress::class,
20 | 'geo' => GeoCoordinates::class,
21 | 'review' => Review::class,
22 | 'aggregateRating' => AggregateRating::class,
23 | 'url' => null,
24 | 'priceRange' => null,
25 | 'areaServed' => null,
26 | 'hasMap' => null,
27 | ];
28 |
29 | /**
30 | * Set the opening hours of the business.
31 | *
32 | * @param array $items
33 | *
34 | * @return array
35 | */
36 | protected function setOpeningHoursAttribute($items)
37 | {
38 | return $items;
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/tests/ContextTypes/CorporationTest.php:
--------------------------------------------------------------------------------
1 | 'Acme Corp.',
14 | 'url' => 'https://example.com',
15 | 'description' => 'Lorem ipsum dolor sit amet',
16 | 'tickerSymbol' => 'ACME',
17 | 'address' => [
18 | 'streetAddress' => '1785 East Sahara Avenue, Suite 490-423',
19 | 'addressLocality' => 'Las Vegas',
20 | 'addressRegion' => 'NV',
21 | 'postalCode' => '89104',
22 | ],
23 | ];
24 |
25 | /**
26 | * @test
27 | */
28 | public function shouldHaveUrl()
29 | {
30 | $context = $this->make();
31 |
32 | $this->assertEquals('https://example.com', $context->getProperty('url'));
33 | $this->assertEquals('PostalAddress', $context->getProperty('address')['@type']);
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/src/ContextTypes/Book.php:
--------------------------------------------------------------------------------
1 | null,
20 | 'bookEdition' => null,
21 | 'bookFormat' => BookFormatType::class,
22 | 'illustrator' => Person::class,
23 | 'isbn' => null,
24 | 'numberOfPages' => null,
25 | ];
26 |
27 | /**
28 | * Constructor. Merges extendedStructure up
29 | *
30 | * @param array $attributes
31 | * @param array $extendedStructure
32 | */
33 | public function __construct(array $attributes, array $extendedStructure = [])
34 | {
35 | parent::__construct(
36 | $attributes, array_merge($this->structure, $this->extendedStructure, $extendedStructure)
37 | );
38 | }
39 | }
--------------------------------------------------------------------------------
/src/ContextTypes/Thing.php:
--------------------------------------------------------------------------------
1 | null,
17 | 'description' => null,
18 | 'image' => ImageObject::class,
19 | 'mainEntityOfPage' => WebPage::class,
20 | 'name' => null,
21 | 'sameAs' => null,
22 | 'url' => null,
23 | ];
24 |
25 | /**
26 | * Thing constructor. Merges extendedStructure up
27 | *
28 | * @param array $attributes
29 | * @param array $extendedStructure
30 | */
31 | public function __construct(array $attributes, array $extendedStructure = [])
32 | {
33 | $this->structure = array_merge($this->structure, $extendedStructure);
34 |
35 | parent::__construct($attributes);
36 | }
37 |
38 | /**
39 | * Set type attribute.
40 | *
41 | * @param string $type
42 | *
43 | * @return array
44 | */
45 | protected function setTypeAttribute($type)
46 | {
47 | // TODO: Add type validation
48 | return $type;
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/docs/search-box.md:
--------------------------------------------------------------------------------
1 | # Search Box
2 |
3 | With Google Sitelinks search box, from search results. Search users sometimes use navigational queries, typing in the brand name or URL of a known site or app, only to do a more detailed search once they reach their destination. For example, users searching for pizza pins on Pinterest would type Pinterest or pinterest.com into Google Search--either from the Google App or from their web browser--then load the site or Android app, and finally search for pizza.
4 |
5 | ## Example
6 |
7 | ```php
8 | $context = \JsonLd\Context::create('search_box', [
9 | 'url' => 'https://www.example.com/',
10 | 'potentialAction' => [
11 | 'target' => 'https://query.example.com/search?q={search_term_string}',
12 | 'query-input' => 'required name=search_term_string',
13 | ],
14 | ]);
15 | ```
16 |
17 | **Output**
18 |
19 | ```javascript
20 |
32 | ```
--------------------------------------------------------------------------------
/tests/ContextTypes/AbstractContextTest.php:
--------------------------------------------------------------------------------
1 | 'http://google.com/profile',
13 | 'name' => 'My Profile Page',
14 | ];
15 |
16 | /**
17 | * @test
18 | */
19 | public function shouldGetProperties()
20 | {
21 | $context = $this->make();
22 |
23 | $this->assertEquals(array_merge([
24 | '@context' => 'http://schema.org',
25 | '@type' => 'ContextStub',
26 | ], $this->attributes), $context->getProperties());
27 | }
28 |
29 | /**
30 | * @test
31 | */
32 | public function shouldSetContext()
33 | {
34 | $context = $this->make();
35 |
36 | $properties = $context->getProperties();
37 |
38 | $this->assertEquals('http://schema.org', $properties['@context']);
39 | }
40 |
41 | /**
42 | * @test
43 | */
44 | public function shouldSetTypeUsingClassName()
45 | {
46 | $context = $this->make();
47 |
48 | $properties = $context->getProperties();
49 |
50 | $this->assertEquals('ContextStub', $properties['@type']);
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The BSD 2-Clause License
2 | Copyright (c) 2015-2020, Daniel Stainback
3 | All rights reserved.
4 |
5 | Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
6 |
7 | 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
8 |
9 | 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
10 |
11 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
12 |
--------------------------------------------------------------------------------
/src/ContextTypes/Recipe.php:
--------------------------------------------------------------------------------
1 | null,
17 | 'prepTime' => null,
18 | 'cookTime' => null,
19 | 'totalTime' => null,
20 | 'image' => null,
21 | 'recipeCategory' => null,
22 | 'description' => null,
23 | 'recipeIngredient' => null,
24 | 'recipeInstructions' => null,
25 | 'recipeYield' => null,
26 | 'recipeCuisine' => null,
27 | 'author' => Person::class,
28 | 'nutrition' => NutritionInformation::class,
29 | 'aggregateRating' => AggregateRating::class,
30 | 'review' => Review::class,
31 | 'video' => VideoObject::class,
32 | ];
33 |
34 | /**
35 | * Set the reviews
36 | *
37 | * @param array $items
38 | *
39 | * @return array
40 | */
41 | protected function setReviewAttribute($items)
42 | {
43 | if (is_array($items) === false) {
44 | return $items;
45 | }
46 |
47 | return array_map(function ($item) {
48 | return $this->getNestedContext(Review::class, $item);
49 | }, $items);
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/tests/ContextTypes/PlaceTest.php:
--------------------------------------------------------------------------------
1 | 'Fluff Hut',
13 | 'address' => [
14 | 'streetAddress' => '112 Apple St.',
15 | 'addressLocality' => 'Hamden',
16 | 'addressRegion' => 'CT',
17 | 'postalCode' => '06514',
18 | ],
19 | 'review' => [
20 | 'reviewBody' => 'beautifull place',
21 | 'reviewRating' => 10,
22 | ],
23 | ];
24 |
25 | public function test_should_have_properties() {
26 |
27 | $this->assertPropertyEquals('name', 'Fluff Hut');
28 |
29 | $this->assertPropertyEquals('address',
30 | [
31 | '@type' => 'PostalAddress',
32 | 'streetAddress' => '112 Apple St.',
33 | 'addressLocality' => 'Hamden',
34 | 'addressRegion' => 'CT',
35 | 'postalCode' => '06514',
36 | ]);
37 |
38 | $this->assertPropertyEquals('review',
39 | [
40 | '@type' => 'Review',
41 | 'reviewBody' => 'beautifull place',
42 | 'reviewRating' => 10,
43 | ]);
44 | }
45 |
46 | }
--------------------------------------------------------------------------------
/src/ContextTypes/Event.php:
--------------------------------------------------------------------------------
1 | null,
14 | 'startDate' => null,
15 | 'endDate' => null,
16 | 'url' => null,
17 | 'offers' => [],
18 | 'location' => Place::class,
19 | ];
20 |
21 | /**
22 | * Constructor. Merges extendedStructure up
23 | *
24 | * @param array $attributes
25 | * @param array $extendedStructure
26 | */
27 | public function __construct(array $attributes, array $extendedStructure = [])
28 | {
29 | parent::__construct(
30 | $attributes, array_merge($this->structure, $this->extendedStructure, $extendedStructure)
31 | );
32 | }
33 |
34 | /**
35 | * Set offers attributes.
36 | *
37 | * @param mixed $values
38 | *
39 | * @return array
40 | */
41 | protected function setOffersAttribute($values)
42 | {
43 | if (is_array($values)) {
44 | foreach ($values as $key => $value) {
45 | $values[$key] = $this->mapProperty([
46 | 'name' => '',
47 | 'price' => '',
48 | 'url' => '',
49 | ], $value);
50 | }
51 | }
52 |
53 | return $values;
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/tests/TestCase.php:
--------------------------------------------------------------------------------
1 | class($this->attributes);
22 | }
23 |
24 | protected function assertPropertyEquals($property, $expectedValue)
25 | {
26 | $context = $this->make();
27 |
28 | $assertMessage = 'asserting \''.$this->class.'\' property \''.$property.'\'';
29 | $this->assertEquals($expectedValue, $context->getProperty($property), $assertMessage);
30 | }
31 |
32 |
33 | protected function makeJsonLdContext()
34 | {
35 | return \JsonLd\Context::create($this->class, $this->attributes);
36 | }
37 |
38 | public function testGenerateLdJson()
39 | {
40 | $context = $this->make();
41 | $jsonLdContext = $this->makeJsonLdContext();
42 |
43 | $html = $jsonLdContext->generate();
44 | $properties = $context->getProperties();
45 |
46 | $this->assertNotNull($html);
47 | foreach ($properties as $k => $v) {
48 | if ($v != null) {
49 | $this->assertContains(json_encode($k), $html);
50 | $this->assertContains(json_encode($v), $html);
51 | }
52 | }
53 | }
54 | }
--------------------------------------------------------------------------------
/docs/breadcrumbs.md:
--------------------------------------------------------------------------------
1 | # Breadcrumbs
2 |
3 | Breadcrumb trails on a page indicate the page's position in the site hierarchy. A user can navigate all the way up in the site hierarchy, one level at a time, by starting from the last breadcrumb in the breadcrumb trail.
4 |
5 | ## Example
6 |
7 | ```php
8 | $context = \JsonLd\Context::create('breadcrumb_list', [
9 | 'itemListElement' => [
10 | [
11 | 'url' => 'https://example.com/arts',
12 | 'name' => 'Arts',
13 | ],
14 | [
15 | 'url' => 'https://example.com/arts/books',
16 | 'name' => 'Books',
17 | ],
18 | [
19 | 'url' => 'https://example.com/arts/books/poetry',
20 | 'name' => 'Poetry',
21 | ],
22 | ]
23 | ]);
24 | ```
25 |
26 | **Output**
27 |
28 | ```javascript
29 |
61 | ```
--------------------------------------------------------------------------------
/src/ContextTypes/Organization.php:
--------------------------------------------------------------------------------
1 | PostalAddress::class,
14 | 'logo' => ImageObject::class,
15 | 'contactPoint' => ContactPoint::class,
16 | 'email' => null,
17 | 'telephone' => null,
18 | ];
19 |
20 | /**
21 | * Organization constructor. Merges extendedStructure up
22 | *
23 | * @param array $attributes
24 | * @param array $extendedStructure
25 | */
26 | public function __construct(array $attributes, array $extendedStructure = [])
27 | {
28 | parent::__construct(
29 | $attributes, array_merge($this->structure, $this->extendedStructure, $extendedStructure)
30 | );
31 | }
32 |
33 | /**
34 | * Set the contactPoints
35 | *
36 | * @param array $items
37 | *
38 | * @return array
39 | */
40 | protected function setContactPointAttribute($items)
41 | {
42 | if (is_array($items) === false) {
43 | return $items;
44 | }
45 |
46 | //Check if it is an array with one dimension
47 | if (is_array(reset($items)) === false) {
48 | return $this->getNestedContext(ContactPoint::class, $items);
49 | }
50 |
51 | //Process multi dimensional array
52 | return array_map(function ($item) {
53 | return $this->getNestedContext(ContactPoint::class, $item);
54 | }, $items);
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/src/ContextTypes/Product.php:
--------------------------------------------------------------------------------
1 | null,
14 | 'description' => null,
15 | 'brand' => null,
16 | 'image' => null,
17 | 'sku' => null,
18 | 'productID' => null,
19 | 'url' => null,
20 | 'review' => Review::class,
21 | 'aggregateRating' => AggregateRating::class,
22 | 'offers' => Offer::class,
23 | 'gtin8' => null,
24 | 'gtin13' => null,
25 | 'gtin14' => null,
26 | 'mpn' => null,
27 | 'category' => null,
28 | 'model' => null,
29 | 'isSimilarTo' => Product::class,
30 | 'height' => QuantitativeValue::class,
31 | 'width' => QuantitativeValue::class,
32 | 'weight' => QuantitativeValue::class,
33 | ];
34 |
35 |
36 | /**
37 | * Set isSimilarTo attributes.
38 | *
39 | * @param mixed $values
40 | *
41 | * @return array
42 | */
43 | protected function setIsSimilarToAttribute($values)
44 | {
45 | if (is_array($values)) {
46 | foreach ($values as $key => $value) {
47 | $product = new self($value);
48 |
49 | $properties = $product->getProperties();
50 |
51 | unset($properties['@context']);
52 |
53 | $properties = array_filter($properties, 'strlen');
54 |
55 | $values[$key] = $properties;
56 | }
57 | }
58 |
59 | return $values;
60 | }
61 | }
62 |
--------------------------------------------------------------------------------
/tests/ContextTypes/PersonSimpleAddressTest.php:
--------------------------------------------------------------------------------
1 | 'Anonymous tester',
13 | 'mainEntityOfPage' => [
14 | 'url' => 'https://example.com/anonymous.html'
15 | ],
16 | 'additionalName' => 'phpUnit hacker',
17 | 'address' => 'rue de gauche'
18 |
19 | ];
20 |
21 | /**
22 | * @test
23 | */
24 | public function shouldHaveThingName()
25 | {
26 | $context = $this->make();
27 |
28 | $this->assertEquals('Anonymous tester', $context->getProperty('name'));
29 | }
30 |
31 | /**
32 | * @test
33 | */
34 | public function shouldHaveThingMainEntityOfPageObject()
35 | {
36 | $context = $this->make();
37 |
38 | $this->assertEquals([
39 | '@type' => 'WebPage',
40 | '@id' => 'https://example.com/anonymous.html',
41 | ], $context->getProperty('mainEntityOfPage'));
42 | }
43 |
44 | /**
45 | * @test
46 | */
47 | public function shouldHaveAdditionalName()
48 | {
49 | $context = $this->make();
50 |
51 | $this->assertEquals('phpUnit hacker', $context->getProperty('additionalName'));
52 | }
53 |
54 | /**
55 | * @test
56 | */
57 | public function shouldHaveAddress()
58 | {
59 | $context = $this->make();
60 |
61 | $this->assertEquals('rue de gauche', $context->getProperty('address'));
62 | }
63 |
64 |
65 | }
66 |
--------------------------------------------------------------------------------
/src/ContextTypes/MusicRecording.php:
--------------------------------------------------------------------------------
1 | null,
14 | 'url' => null,
15 | 'name' => null,
16 | 'duration' => null,
17 | 'genre' => null,
18 | 'description' => null,
19 | 'byArtist' => null,
20 | 'inAlbum' => null,
21 | 'inPlaylist' => null,
22 | 'isrcCode' => null,
23 | ];
24 |
25 | /**
26 | * Set in album attribute
27 | *
28 | * @param array|string $items
29 | *
30 | * @return array
31 | */
32 | protected function setInAlbumAttribute($items)
33 | {
34 | if (is_array($items) === false) {
35 | return $items;
36 | }
37 |
38 | //Check if not multidimensional array (for backward compatibility)
39 | if ((count($items) == count($items, COUNT_RECURSIVE))) {
40 | return $this->getNestedContext(MusicAlbum::class, $items);
41 | }
42 |
43 | //multiple albums
44 | return array_map(function ($item) {
45 | return $this->getNestedContext(MusicAlbum::class, $item);
46 | }, $items);
47 | }
48 |
49 | /**
50 | * Set in playlist attribute
51 | *
52 | * @param array|string $item
53 | *
54 | * @return array
55 | */
56 | protected function setInPlaylistAttribute($item)
57 | {
58 | if (is_array($item) === false) {
59 | return $item;
60 | }
61 |
62 | return $this->getNestedContext(MusicPlaylist::class, $item);
63 | }
64 | }
65 |
--------------------------------------------------------------------------------
/tests/ContextTypes/BlogPostingTest.php:
--------------------------------------------------------------------------------
1 | 'More than That',
13 | 'url' => 'https://google.com/1-article',
14 | 'description' => 'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur.',
15 | 'sharedContent' => [
16 | 'url' => 'https://google.com/thumbnail1.jpg',
17 | 'name' => 'My Post',
18 | ],
19 | ];
20 |
21 | /**
22 | * @test
23 | */
24 | public function shouldTruncateDescription()
25 | {
26 | $context = $this->make();
27 |
28 | $this->assertEquals('Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit...', $context->getProperty('description'));
29 | }
30 |
31 | /**
32 | * @test
33 | */
34 | public function shouldHaveSharedContentObject()
35 | {
36 | $context = $this->make();
37 |
38 | $this->assertEquals([
39 | '@type' => 'CreativeWork',
40 | 'name' => 'My Post',
41 | 'url' => 'https://google.com/thumbnail1.jpg',
42 | ], $context->getProperty('sharedContent'));
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/src/ContextTypes/Article.php:
--------------------------------------------------------------------------------
1 | null,
17 | 'articleSection' => null,
18 | 'pageEnd' => null,
19 | 'pageStart' => null,
20 | 'pagination' => null,
21 | 'wordCount' => null,
22 | 'mainEntityOfPage' => WebPage::class,
23 | ];
24 |
25 | /**
26 | * Constructor. Merges extendedStructure up
27 | *
28 | * @param array $attributes
29 | * @param array $extendedStructure
30 | */
31 | public function __construct(array $attributes, array $extendedStructure = [])
32 | {
33 | parent::__construct(
34 | $attributes, array_merge($this->structure, $this->extendedStructure, $extendedStructure)
35 | );
36 | }
37 |
38 | /**
39 | * Set the description attribute.
40 | *
41 | * @param string $txt
42 | *
43 | * @return string
44 | */
45 | protected function setDescriptionAttribute($txt)
46 | {
47 | return $this->truncate($txt, 260);
48 | }
49 |
50 | /**
51 | * Set the text attribute.
52 | *
53 | * @param string $txt
54 | *
55 | * @return string
56 | */
57 | protected function setTextAttribute($txt)
58 | {
59 | return $this->truncate($txt, 260);
60 | }
61 |
62 | /**
63 | * Set the article body attribute.
64 | *
65 | * @param string $txt
66 | *
67 | * @return string
68 | */
69 | protected function setArticleBodyAttribute($txt)
70 | {
71 | return $this->truncate($txt, 260);
72 | }
73 | }
74 |
--------------------------------------------------------------------------------
/src/ContextTypes/MediaObject.php:
--------------------------------------------------------------------------------
1 | NewsArticle::class,
25 | 'bitrate' => null,
26 | 'contentSize' => null,
27 | 'contentUrl' => null,
28 | 'duration' => Duration::class,
29 | 'embedUrl' => null,
30 | 'encodesCreativeWork' => CreativeWork::class,
31 | 'encodingFormat' => null,
32 | 'endTime' => null,
33 | 'height' => QuantitativeValue::class,
34 | 'playerType' => null,
35 | 'productionCompany' => Organization::class,
36 | 'regionsAllowed' => Place::class,
37 | 'requiresSubscription' => null,
38 | 'startTime' => DateTime::class,
39 | 'uploadDate' => DateTime::class,
40 | 'width' => QuantitativeValue::class,
41 | ];
42 |
43 | /**
44 | * @param array $attributes
45 | * @param array $extendedStructure
46 | */
47 | public function __construct(array $attributes, array $extendedStructure = [])
48 | {
49 | parent::__construct(
50 | $attributes, array_merge($this->structure, $this->extendedStructure, $extendedStructure)
51 | );
52 | }
53 | }
--------------------------------------------------------------------------------
/src/ContextTypes/BookFormatType.php:
--------------------------------------------------------------------------------
1 | structure, $this->extendedStructure, $extendedStructure)
65 | );
66 | }
67 | }
--------------------------------------------------------------------------------
/tests/ContextTypes/Organization2ContactsTest.php:
--------------------------------------------------------------------------------
1 | 'Said Organization',
13 | 'url' => 'https://google.com/organization/22',
14 | 'email' => 'info@nomail.com',
15 | 'address' => [
16 | 'streetAddress' => '112 Apple St.',
17 | 'addressLocality' => 'Hamden',
18 | 'addressRegion' => 'CT',
19 | 'postalCode' => '06514',
20 | ],
21 | 'logo' => 'https://google.com/thumbnail1.jpg',
22 | 'contactPoint' => [
23 | ['@type' => 'contactPoint',
24 | 'email' => 'support@nomail.com',
25 | 'telephone' => '18008888888',
26 | 'contactType' => 'customer service',
27 | ],
28 | ['@type' => 'contactPoint',
29 | 'email' => 'sales@nomail.com',
30 | 'telephone' => '18009999999',
31 | 'contactType' => 'sales',
32 | ],
33 | ],
34 | ];
35 |
36 | /**
37 | * @test
38 | */
39 | public function shouldHave2ContactsArray()
40 | {
41 | $context = $this->make();
42 |
43 | $this->assertEquals([
44 | '@type' => 'ContactPoint',
45 | 'email' => 'support@nomail.com',
46 | 'telephone' => '18008888888',
47 | 'contactType' => 'customer service',
48 | ], $context->getProperty('contactPoint')[0]);
49 | $this->assertEquals([
50 | '@type' => 'ContactPoint',
51 | 'email' => 'sales@nomail.com',
52 | 'telephone' => '18009999999',
53 | 'contactType' => 'sales',
54 | ], $context->getProperty('contactPoint')[1]);
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/tests/ContextTypes/MusicGroupTest.php:
--------------------------------------------------------------------------------
1 | 'exercitation ullamco laboris nisi ut',
14 | 'url' => 'https://google.com/1-musicgroup',
15 | 'description' => 'Lorem ipsum dolor sit amet',
16 | 'track' => [
17 | [
18 | '@type' => 'MusicRecording',
19 | 'name' => 'magni dolores eo',
20 | 'url' => 'https://google.com/1-musicrecording',
21 | 'duration' => 'PT1M33S', // 1 minute 33 seconds
22 | 'genre' => ['Ambient', 'Classical', 'Folk'],
23 | ]
24 | ],
25 | ];
26 |
27 | /**
28 | * @test
29 | */
30 | public function shouldHaveName()
31 | {
32 | $context = $this->make();
33 |
34 | $this->assertEquals('exercitation ullamco laboris nisi ut', $context->getProperty('name'));
35 | }
36 |
37 | /**
38 | * @test
39 | */
40 | public function shouldHaveDescription()
41 | {
42 | $context = $this->make();
43 |
44 | $this->assertEquals('Lorem ipsum dolor sit amet', $context->getProperty('description'));
45 | }
46 |
47 | /**
48 | * @test
49 | */
50 | public function shouldHaveTrackObject()
51 | {
52 | $context = $this->make();
53 |
54 | $this->assertEquals([
55 | [
56 | '@type' => 'MusicRecording',
57 | 'name' => 'magni dolores eo',
58 | '@id' => 'https://google.com/1-musicrecording',
59 | 'duration' => 'PT1M33S', // 1 minute 33 seconds
60 | 'genre' => 'Ambient, Classical, Folk',
61 | ]
62 | ], $context->getProperty('track'));
63 | }
64 |
65 | }
66 |
--------------------------------------------------------------------------------
/src/ContextTypes/Person.php:
--------------------------------------------------------------------------------
1 | null,
17 | 'address' => null, // PostalAddress or Text
18 | 'affiliation' => null,
19 | 'alumniOf' => null,
20 | 'award' => null,
21 | 'birthDate' => null,
22 | 'birthPlace' => Place::class,
23 | 'brand' => null,
24 | 'children' => Person::class,
25 | 'colleague' => null,
26 | 'contactPoint' => null,
27 | 'deathDate' => null,
28 | 'deathPlace' => Place::class,
29 | 'duns' => null,
30 | 'email' => null,
31 | 'familyName' => null,
32 | 'faxNumber' => null,
33 | 'follows' => Person::class,
34 | 'homeLocation' => Place::class,
35 | 'givenName' => null,
36 | 'jobTitle' => null,
37 | 'parent' => Person::class,
38 | 'telephone' => null,
39 | 'workLocation' => Place::class,
40 | ];
41 |
42 | /**
43 | * Constructor. Merges extendedStructure up
44 | *
45 | * @param array $attributes
46 | * @param array $extendedStructure
47 | */
48 | public function __construct(array $attributes, array $extendedStructure = [])
49 | {
50 | parent::__construct($attributes, array_merge($this->structure, $this->extendedStructure, $extendedStructure));
51 | }
52 |
53 | /**
54 | * Set the address
55 | *
56 | * @param array $items
57 | *
58 | * @return array
59 | */
60 | protected function setAddressAttribute($items)
61 | {
62 | if (is_array($items) === false) {
63 | return $items;
64 | }
65 |
66 | return $this->getNestedContext(PostalAddress::class, $items);
67 | }
68 | }
69 |
--------------------------------------------------------------------------------
/tests/ContextTypes/MusicPlaylistTest.php:
--------------------------------------------------------------------------------
1 | 'magni dolores eo',
14 | 'url' => 'https://google.com/1-musicplaylist',
15 | 'numTracks' => 2,
16 | 'track' => [
17 | [
18 | '@type' => 'MusicRecording',
19 | 'name' => 'magni dolores eo',
20 | 'url' => 'https://google.com/1-musicrecording',
21 | 'duration' => 'PT1M33S', // 1 minute 33 seconds
22 | 'genre' => ['Ambient', 'Classical', 'Folk'],
23 | ],
24 | [
25 | '@type' => 'MusicRecording',
26 | 'name' => 'totam rem aperiam',
27 | 'url' => 'https://google.com/2-musicrecording',
28 | 'duration' => 'PT3M33S', // 3 minute 33 seconds
29 | 'genre' => 'Classical',
30 | ]
31 | ],
32 | ];
33 |
34 | /**
35 | * @test
36 | */
37 | public function shouldHaveName()
38 | {
39 | $context = $this->make();
40 |
41 | $this->assertEquals('magni dolores eo', $context->getProperty('name'));
42 | }
43 |
44 | /**
45 | * @test
46 | */
47 | public function shouldHaveTrackObject()
48 | {
49 | $context = $this->make();
50 |
51 | $this->assertEquals([
52 | [
53 | '@type' => 'MusicRecording',
54 | '@id' => 'https://google.com/1-musicrecording',
55 | 'name' => 'magni dolores eo',
56 | 'duration' => 'PT1M33S', // 1 minute 33 seconds
57 | 'genre' => 'Ambient, Classical, Folk',
58 | ],
59 | [
60 | '@type' => 'MusicRecording',
61 | '@id' => 'https://google.com/2-musicrecording',
62 | 'name' => 'totam rem aperiam',
63 | 'duration' => 'PT3M33S', // 3 minute 33 seconds
64 | 'genre' => 'Classical',
65 | ],
66 | ], $context->getProperty('track'));
67 | }
68 |
69 | }
70 |
--------------------------------------------------------------------------------
/tests/ContextTypes/OrganizationTest.php:
--------------------------------------------------------------------------------
1 | 'Said Organization',
13 | 'url' => 'https://google.com/organization/22',
14 | 'email' => 'info@nomail.com',
15 | 'address' => [
16 | 'streetAddress' => '112 Apple St.',
17 | 'addressLocality' => 'Hamden',
18 | 'addressRegion' => 'CT',
19 | 'postalCode' => '06514',
20 | ],
21 | 'logo' => 'https://google.com/thumbnail1.jpg',
22 | 'contactPoint' => [
23 | 'email' => 'support@nomail.com',
24 | 'telephone' => '18009999999',
25 | 'contactType' => 'customer service',
26 | ],
27 | ];
28 |
29 | /**
30 | * @test
31 | */
32 | public function test_should_have_properties() {
33 |
34 | $this->assertPropertyEquals('name', 'Said Organization');
35 |
36 | $this->assertPropertyEquals('url', 'https://google.com/organization/22');
37 |
38 | $this->assertPropertyEquals('email', 'info@nomail.com');
39 |
40 | $this->assertPropertyEquals('logo', 'https://google.com/thumbnail1.jpg');
41 | }
42 |
43 | /**
44 | * @test
45 | */
46 | public function shouldHaveContactPointObject()
47 | {
48 | $context = $this->make();
49 |
50 | $this->assertEquals([
51 | '@type' => 'ContactPoint',
52 | 'email' => 'support@nomail.com',
53 | 'telephone' => '18009999999',
54 | 'contactType' => 'customer service',
55 | ], $context->getProperty('contactPoint'));
56 | }
57 |
58 | /**
59 | * @test
60 | */
61 | public function shouldHaveAddressArray()
62 | {
63 | $context = $this->make();
64 |
65 | $this->assertEquals([
66 | '@type' => 'PostalAddress',
67 | 'streetAddress' => '112 Apple St.',
68 | 'addressLocality' => 'Hamden',
69 | 'addressRegion' => 'CT',
70 | 'postalCode' => '06514',
71 | ], $context->getProperty('address'));
72 | }
73 | }
74 |
--------------------------------------------------------------------------------
/tests/ContextTest.php:
--------------------------------------------------------------------------------
1 | 'Foo Bar']);
16 |
17 | $this->assertEquals([
18 | '@context' => 'http://schema.org',
19 | '@type' => 'Event',
20 | 'name' => 'Foo Bar',
21 | ], $context->getProperties());
22 | }
23 |
24 | /**
25 | * @test
26 | */
27 | public function shouldAllowSameAsToBeArray()
28 | {
29 | $context = \JsonLd\Context::create('event', [
30 | 'name' => 'Foo Bar',
31 | 'sameAs' => [
32 | 'https://google.com/facebook',
33 | 'https://google.com/instagram',
34 | 'https://google.com/linkedin'
35 | ],
36 | ]);
37 |
38 | $this->assertEquals([
39 | '@context' => 'http://schema.org',
40 | '@type' => 'Event',
41 | 'name' => 'Foo Bar',
42 | 'sameAs' => [
43 | 'https://google.com/facebook',
44 | 'https://google.com/instagram',
45 | 'https://google.com/linkedin'
46 | ],
47 | ], $context->getProperties());
48 | }
49 |
50 | /**
51 | * @test
52 | */
53 | public function shouldAllowSameAsToBeAString()
54 | {
55 | $context = \JsonLd\Context::create('event', [
56 | 'name' => 'Foo Bar',
57 | 'sameAs' => 'https://google.com/facebook',
58 | ]);
59 |
60 | $this->assertEquals([
61 | '@context' => 'http://schema.org',
62 | '@type' => 'Event',
63 | 'name' => 'Foo Bar',
64 | 'sameAs' => 'https://google.com/facebook',
65 | ], $context->getProperties());
66 | }
67 |
68 | /**
69 | * @test
70 | */
71 | public function shouldGenerateEventScriptTag()
72 | {
73 | $context = \JsonLd\Context::create('event', ['name' => 'Foo Bar']);
74 |
75 | $this->assertEquals('', $context->generate());
76 | }
77 |
78 | public function tearDown()
79 | {
80 | Mockery::close();
81 | }
82 | }
83 |
--------------------------------------------------------------------------------
/src/ContextTypes/MusicAbstractContext.php:
--------------------------------------------------------------------------------
1 | properties['@id'] = $url;
18 |
19 | return null;
20 | }
21 |
22 | /**
23 | * Set genre(s) attribute
24 | *
25 | * @param array|string $items
26 | *
27 | * @return string
28 | */
29 | protected function setGenreAttribute($items)
30 | {
31 | if (is_array($items) === false) {
32 | return $items;
33 | }
34 |
35 | return implode(', ', $items);
36 | }
37 |
38 | /**
39 | * Set artist attribute
40 | *
41 | * @param array|string $items
42 | *
43 | * @return array
44 | */
45 | protected function setByArtistAttribute($items)
46 | {
47 | if (is_array($items) === false) {
48 | return $items;
49 | }
50 |
51 | //Check if not multidimensional array (for backward compatibility)
52 | if ((count($items) == count($items, COUNT_RECURSIVE))) {
53 | return $this->getNestedContext(MusicGroup::class, $items);
54 | }
55 |
56 | //multiple artists
57 | return array_map(function ($item) {
58 | return $this->getNestedContext(MusicGroup::class, $item);
59 | }, $items);
60 | }
61 |
62 | /**
63 | * Set the tracks for a music group
64 | *
65 | * @param array $items
66 | *
67 | * @return array
68 | */
69 | protected function setTrackAttribute($items)
70 | {
71 | if (is_array($items) === false) {
72 | return $items;
73 | }
74 |
75 | return array_map(function ($item) {
76 | return $this->getNestedContext(MusicRecording::class, $item);
77 | }, $items);
78 | }
79 |
80 | /**
81 | * Set image attribute
82 | *
83 | * @param array|string $item
84 | *
85 | * @return array
86 | */
87 | protected function setImageAttribute($item)
88 | {
89 | if (is_array($item) === false) {
90 | return $item;
91 | }
92 |
93 | return $this->getNestedContext(ImageObject::class, $item);
94 | }
95 | }
96 |
--------------------------------------------------------------------------------
/tests/ContextTypes/EventTest.php:
--------------------------------------------------------------------------------
1 | 'Apple Fest',
13 | 'startDate' => '2013-10-04T00:00',
14 | 'url' => 'https://google.com/events/22',
15 | 'offers' => [
16 | [
17 | 'name' => 'Beer',
18 | 'price' => '4.99'
19 | ]
20 | ],
21 | 'location' => [
22 | 'name' => 'Fluff Hut',
23 | 'address' => [
24 | 'streetAddress' => '112 Apple St.',
25 | 'addressLocality' => 'Hamden',
26 | 'addressRegion' => 'CT',
27 | 'postalCode' => '06514',
28 | ],
29 | ],
30 | 'image' => 'https://google.com/some_logo.png',
31 | 'description' => 'A description',
32 | ];
33 |
34 | /**
35 | * @test
36 | */
37 | public function shouldHaveOffersArray()
38 | {
39 | $context = $this->make();
40 |
41 | $this->assertEquals([
42 | [
43 | 'name' => 'Beer',
44 | 'price' => '4.99'
45 | ]
46 | ], $context->getProperty('offers'));
47 | }
48 |
49 |
50 | /**
51 | * @test
52 | */
53 | public function shouldHaveLocationObject()
54 | {
55 | $context = $this->make();
56 |
57 | $this->assertEquals([
58 | '@type' => 'Place',
59 | 'name' => 'Fluff Hut',
60 | 'address' => [
61 | '@type' => 'PostalAddress',
62 | 'streetAddress' => '112 Apple St.',
63 | 'addressLocality' => 'Hamden',
64 | 'addressRegion' => 'CT',
65 | 'postalCode' => '06514',
66 | ],
67 | ], $context->getProperty('location'));
68 | }
69 |
70 | /**
71 | * @test
72 | */
73 | public function shouldHaveImage()
74 | {
75 | $context = $this->make();
76 |
77 | $this->assertEquals(
78 | 'https://google.com/some_logo.png'
79 | , $context->getProperty('image'));
80 | }
81 |
82 | /**
83 | * @test
84 | */
85 | public function shouldHaveDescription()
86 | {
87 | $context = $this->make();
88 |
89 | $this->assertEquals(
90 | 'A description'
91 | , $context->getProperty('description'));
92 | }
93 | }
94 |
--------------------------------------------------------------------------------
/tests/ContextTypes/MusicRecordingTest.php:
--------------------------------------------------------------------------------
1 | 'magni dolores eo',
14 | 'url' => 'https://google.com/1-musicrecording',
15 | 'duration' => 'PT1M33S', // 1 minute 33 seconds
16 | 'genre' => ['Ambient', 'Classical', 'Folk'], // can also be a string
17 | 'byArtist' => [
18 | 'name' => 'exercitation ullamco laboris nisi ut',
19 | 'url' => 'https://google.com/1-musicgroup',
20 | 'description' => 'aliquam quaerat voluptatem.',
21 | ],
22 | 'inAlbum' => [
23 | 'name' => 'sed quia consequuntur',
24 | 'url' => 'https://google.com/1-musicalbum',
25 | 'description' => 'Lorem ipsum dolor sit amet',
26 | ],
27 | ];
28 |
29 | /**
30 | * @test
31 | */
32 | public function shouldHaveName()
33 | {
34 | $context = $this->make();
35 |
36 | $this->assertEquals('magni dolores eo', $context->getProperty('name'));
37 | }
38 |
39 | /**
40 | * @test
41 | */
42 | public function shouldHaveGenre()
43 | {
44 | $context = $this->make();
45 |
46 | $this->assertEquals('Ambient, Classical, Folk', $context->getProperty('genre'));
47 | }
48 |
49 | /**
50 | * @test
51 | */
52 | public function shouldBeAbleToSetGenreAsString()
53 | {
54 | $this->attributes['genre'] = 'Classical';
55 | $context = $this->make();
56 |
57 | $this->assertEquals('Classical', $context->getProperty('genre'));
58 | }
59 |
60 | /**
61 | * @test
62 | */
63 | public function shouldHaveByArtistObject()
64 | {
65 | $context = $this->make();
66 |
67 | $this->assertEquals([
68 | '@type' => 'MusicGroup',
69 | '@id' => 'https://google.com/1-musicgroup',
70 | 'name' => 'exercitation ullamco laboris nisi ut',
71 | 'description' => 'aliquam quaerat voluptatem.',
72 | ], $context->getProperty('byArtist'));
73 | }
74 |
75 | /**
76 | * @test
77 | */
78 | public function shouldHaveInAlbumObject()
79 | {
80 | $context = $this->make();
81 |
82 | $this->assertEquals([
83 | '@type' => 'MusicAlbum',
84 | '@id' => 'https://google.com/1-musicalbum',
85 | 'name' => 'sed quia consequuntur',
86 | 'description' => 'Lorem ipsum dolor sit amet',
87 | ], $context->getProperty('inAlbum'));
88 | }
89 |
90 | }
91 |
--------------------------------------------------------------------------------
/tests/ContextTypes/SculptureTest.php:
--------------------------------------------------------------------------------
1 | 'https://exemple.com/sclpture?id=1234',
13 | 'author' => [
14 | '@type' => 'Person',
15 | 'name' => 'Rodin',
16 | ],
17 | 'text' => 'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco.',
18 | 'commentCount' => 2,
19 | 'comment' => [
20 | ['@type' => 'Comment',
21 | 'author' => ['@type' => 'Person', 'name' => 'Joe Joe'],
22 | 'text' => 'first comment',
23 | 'dateCreated' => '2018-06-14T21:40:00+02:00'],
24 | ['@type' => 'Comment',
25 | 'author' => ['@type' => 'Person', 'name' => 'Joe Bis'],
26 | 'text' => 'second comment',
27 | 'dateCreated' => '2018-06-14T23:23:00+02:00']
28 | ],
29 | 'inLanguage' => 'jp',
30 | 'dateCreated' => '2013-10-04T00:00',
31 | 'name' => 'sculptureNComments'
32 |
33 | ];
34 |
35 |
36 | /**
37 | * @test
38 | */
39 | public function shouldHaveThingName()
40 | {
41 | $context = $this->make();
42 |
43 | $this->assertEquals('sculptureNComments', $context->getProperty('name'));
44 | }
45 |
46 | /**
47 | * @test
48 | */
49 | public function shouldHaveCreativeWorkInLanguage()
50 | {
51 | $context = $this->make();
52 |
53 | $this->assertEquals('jp', $context->getProperty('inLanguage'));
54 | }
55 |
56 | /**
57 | * @test
58 | */
59 | public function shouldHaveCreativeWorkAuthorObject()
60 | {
61 | $context = $this->make();
62 |
63 | $this->assertEquals([
64 | '@type' => 'Person',
65 | 'name' => 'Rodin',
66 | ], $context->getProperty('author'));
67 | }
68 |
69 | /**
70 | * @test
71 | */
72 | public function shouldHave2CommentsArray()
73 | {
74 | $context = $this->make();
75 |
76 | $this->assertEquals([
77 | '@type' => 'Comment',
78 | 'text' => 'first comment',
79 | 'author' => ['@type' => 'Person', 'name' => 'Joe Joe'],
80 | 'dateCreated' => '2018-06-14T21:40:00+02:00',
81 | ], $context->getProperty('comment')[0]);
82 | $this->assertEquals([
83 | '@type' => 'Comment',
84 | 'text' => 'second comment',
85 | 'author' => ['@type' => 'Person', 'name' => 'Joe Bis'],
86 | 'dateCreated' => '2018-06-14T23:23:00+02:00',
87 | ], $context->getProperty('comment')[1]);
88 | }
89 | }
90 |
--------------------------------------------------------------------------------
/tests/ContextTypes/WebSiteTest.php:
--------------------------------------------------------------------------------
1 | 'The subject matter of the content.',
13 | 'headline' => 'Headline of the website.',
14 | 'image' => 'https://og.github.com/mark/github-mark@1200x630.png',
15 | 'name' => 'The name of the item.',
16 | 'url' => 'https://schema.org/WebSite',
17 | 'publisher' => [
18 | 'name' => 'Google',
19 | 'logo' => [
20 | '@type' => 'ImageObject',
21 | 'url' => 'https://google.com/logo.jpg',
22 | 'width' => 600,
23 | 'height' => 60,
24 | ]
25 | ],
26 | 'keywords' => 'about,headline,image,name,url',
27 | 'inLanguage' => 'en',
28 | 'dateCreated' => '2013-10-04T00:00',
29 | 'dateModified' => '2013-10-04T00:00',
30 | 'datePublished' => '2013-10-04T00:00',
31 | 'sameAs' => 'https://schema.org/sameAs',
32 | ];
33 |
34 | /**
35 | * @test
36 | */
37 | public function shouldGetProperties()
38 | {
39 | $context = $this->make();
40 |
41 | $attributesPlus = $this->attributes;
42 | $attributesPlus['@context'] = 'http://schema.org';
43 | $attributesPlus["@type"] = 'WebSite';
44 | $attributesPlus["publisher"]["@type"] = 'Organization';
45 |
46 | $this->assertEquals($context->getProperties(), $attributesPlus);
47 | }
48 |
49 | /**
50 | * @test
51 | */
52 | public function shouldHaveHeadline()
53 | {
54 | $context = $this->make();
55 |
56 | $this->assertEquals('Headline of the website.', $context->getProperty('headline'));
57 | }
58 |
59 | /**
60 | * @test
61 | */
62 | public function shouldHavePublisherObject()
63 | {
64 | $context = $this->make();
65 |
66 | $this->assertEquals([
67 | '@type' => 'Organization',
68 | 'name' => 'Google',
69 | 'logo' => [
70 | '@type' => 'ImageObject',
71 | 'url' => 'https://google.com/logo.jpg',
72 | 'height' => 60,
73 | 'width' => 600,
74 | ],
75 | ], $context->getProperty('publisher'));
76 | }
77 |
78 | /**
79 | * @test
80 | */
81 | public function shouldHaveKeywords()
82 | {
83 | $context = $this->make();
84 |
85 | $this->assertEquals('about,headline,image,name,url', $context->getProperty('keywords'));
86 | }
87 |
88 | /**
89 | * @test
90 | */
91 | public function shouldHaveInLanguage()
92 | {
93 | $context = $this->make();
94 |
95 | $this->assertEquals('en', $context->getProperty('inLanguage'));
96 | }
97 | }
--------------------------------------------------------------------------------
/src/ContextTypes/CreativeWork.php:
--------------------------------------------------------------------------------
1 | Thing::class,
17 | 'aggregateRating' => AggregateRating::class,
18 | 'alternativeHeadline' => null,
19 | 'author' => Person::class,
20 | 'comment' => Comment::class,
21 | 'commentCount' => null,
22 | 'creator' => Person::class,
23 | 'dateCreated' => null,
24 | 'dateModified' => null,
25 | 'datePublished' => null,
26 | 'headline' => null,
27 | 'inLanguage' => null,
28 | 'keywords' => null,
29 | 'learningResourceType' => null,
30 | 'mainEntity' => Thing::class,
31 | 'publisher' => Organization::class,
32 | 'review' => Review::class,
33 | 'text' => null,
34 | 'thumbnailUrl' => null,
35 | 'video' => VideoObject::class,
36 | ];
37 |
38 | /**
39 | * Constructor. Merges extendedStructure up
40 | *
41 | * @param array $attributes
42 | * @param array $extendedStructure
43 | */
44 | public function __construct(array $attributes, array $extendedStructure = [])
45 | {
46 | parent::__construct(
47 | $attributes, array_merge($this->structure, $this->extendedStructure, $extendedStructure)
48 | );
49 | }
50 |
51 | /**
52 | * Set the article body attribute.
53 | *
54 | * @param string $txt
55 | *
56 | * @return array
57 | */
58 | protected function setTextAttribute($txt)
59 | {
60 | return $this->truncate($txt, 260);
61 | }
62 |
63 | /**
64 | * Set the authors
65 | *
66 | * @param array $items
67 | *
68 | * @return array
69 | */
70 | protected function setAuthorAttribute($items)
71 | {
72 | if (is_array($items) === false) {
73 | return $items;
74 | }
75 |
76 | return array_map(function ($item) {
77 | return $this->getNestedContext(Person::class, $item);
78 | }, $items);
79 | }
80 |
81 | /**
82 | * Set the comments
83 | *
84 | * @param array $items
85 | *
86 | * @return array
87 | */
88 | protected function setCommentAttribute($items)
89 | {
90 | if (is_array($items) === false) {
91 | return $items;
92 | }
93 |
94 | return array_map(function ($item) {
95 | return $this->getNestedContext(Comment::class, $item);
96 | }, $items);
97 | }
98 |
99 | /**
100 | * Set the reviews
101 | *
102 | * @param array $items
103 | *
104 | * @return array
105 | */
106 | protected function setReviewAttribute($items)
107 | {
108 | if (is_array($items) === false) {
109 | return $items;
110 | }
111 |
112 | return array_map(function ($item) {
113 | return $this->getNestedContext(Review::class, $item);
114 | }, $items);
115 | }
116 | }
117 |
--------------------------------------------------------------------------------
/src/Context.php:
--------------------------------------------------------------------------------
1 | getContextTypeClass($context);
26 |
27 | $this->context = new $class($data);
28 | }
29 |
30 | /**
31 | * Present given data as a JSON-LD object.
32 | *
33 | * @param string $context
34 | * @param array $data
35 | *
36 | * @return static
37 | */
38 | public static function create($context, array $data = [])
39 | {
40 | return new static($context, $data);
41 | }
42 |
43 | /**
44 | * Return the array of context properties.
45 | *
46 | * @return array
47 | */
48 | public function getProperties()
49 | {
50 | return array_filter($this->context->getProperties());
51 | }
52 |
53 | /**
54 | * Generate the JSON-LD script tag.
55 | *
56 | * @return string
57 | */
58 | public function generate()
59 | {
60 | $properties = $this->getProperties();
61 |
62 | return $properties ? "" : '';
63 | }
64 |
65 | /**
66 | * Return script tag.
67 | *
68 | * @param string $name
69 | *
70 | * @return string|null
71 | * @throws InvalidArgumentException
72 | */
73 | protected function getContextTypeClass($name)
74 | {
75 | // Check for custom context type
76 | if (class_exists($name)) {
77 | return $name;
78 | }
79 |
80 | // Create local context type class
81 | $class = ucwords(str_replace(['-', '_'], ' ', $name));
82 | $class = '\\JsonLd\\ContextTypes\\' . str_replace(' ', '', $class);
83 |
84 | // Check for local context type
85 | if (class_exists($class)) {
86 | return $class;
87 | }
88 |
89 | // Backwards compatible, remove in a future version
90 | switch ($name) {
91 | case 'address':
92 | return ContextTypes\PostalAddress::class;
93 | break;
94 | case 'business':
95 | return ContextTypes\LocalBusiness::class;
96 | break;
97 | case 'breadcrumbs':
98 | return ContextTypes\BreadcrumbList::class;
99 | break;
100 | case 'geo':
101 | return ContextTypes\GeoCoordinates::class;
102 | break;
103 | }
104 |
105 | throw new InvalidArgumentException(sprintf('Undefined context type: "%s"', $name));
106 | }
107 |
108 | /**
109 | * Return script tag.
110 | *
111 | * @return string
112 | */
113 | public function __toString()
114 | {
115 | return $this->generate();
116 | }
117 | }
118 |
--------------------------------------------------------------------------------
/tests/ContextTypes/RecipeTest.php:
--------------------------------------------------------------------------------
1 | 'Fried Sugar Egg',
13 | 'prepTime' => 'PT30M',
14 | 'cookTime' => 'PT1H',
15 | 'totalTime' => 'PT1H30M',
16 | 'image' => 'http://www.google.com/image.jpg',
17 | 'recipeCategory' => [
18 | 'christmas',
19 | 'dinner',
20 | 'soup',
21 | ],
22 | 'description' => 'Lorem ipsum dolor sit amet',
23 | 'recipeIngredient' => [
24 | '1 egg',
25 | '2 gram of sugar',
26 | ],
27 | 'recipeInstructions' => '1. work with eggs, 2. add suger, 3. done',
28 | 'recipeYield' => '30 packages',
29 | 'recipeCuisine' => 'American',
30 | 'author' => [
31 | 'givenName' => 'Peter',
32 | ],
33 | 'nutrition' => [
34 | 'calories' => '428 calories',
35 | 'fatContent' => '23g fat (8g saturated fat)',
36 | 'cholesterolContent' => '53mg cholesterol',
37 | 'sodiumContent' => '1146mg sodium',
38 | 'carbohydrateContent' => '33g carbohydrate (3g sugars',
39 | 'fiberContent' => '2g fiber',
40 | 'proteinContent' => '21g protein.',
41 | ],
42 | 'aggregateRating' => [
43 | 'ratingValue' => 5,
44 | 'reviewCount' => 5,
45 | 'ratingCount' => 3,
46 | ],
47 | 'review' => [
48 | [
49 | 'name' => 'first review',
50 | 'reviewRating' => 3
51 | ],
52 | [
53 | 'name' => 'second review',
54 | 'reviewRating' => 5
55 | ],
56 | ],
57 | 'video' => [
58 | 'url' => 'http://www.google.com',
59 | ]
60 | ];
61 |
62 | /**
63 | * @test
64 | */
65 | public function shouldHaveValidRecipe()
66 | {
67 | $context = $this->make();
68 |
69 | $this->assertEquals('Fried Sugar Egg', $context->getProperty('name'));
70 | $this->assertEquals('PT1H', $context->getProperty('cookTime'));
71 | $this->assertEquals(2, count($context->getProperty('recipeIngredient')));
72 | $this->assertEquals(3, count($context->getProperty('recipeCategory')));
73 |
74 | $nutrition = $context->getProperty('nutrition');
75 | $this->assertEquals('NutritionInformation', $nutrition['@type']);
76 | $this->assertEquals(8, count($nutrition));
77 | $this->assertEquals('2g fiber', $nutrition['fiberContent']);
78 | $this->assertEquals('428 calories', $nutrition['calories']);
79 |
80 | $this->assertEquals([
81 | '@type' => 'AggregateRating',
82 | 'ratingValue' => 5,
83 | 'reviewCount' => 5,
84 | 'ratingCount' => 3,
85 | ], $context->getProperty('aggregateRating'));
86 |
87 | $review = $context->getProperty('review');
88 | $this->assertEquals('Review', $review[1]['@type']);
89 |
90 | $author = $context->getProperty('author');
91 | $this->assertEquals('Person', $author['@type']);
92 |
93 | $video = $context->getProperty('video');
94 | $this->assertEquals('VideoObject', $video['@type']);
95 | }
96 | }
97 |
--------------------------------------------------------------------------------
/tests/ContextTypes/MusicAlbumTest.php:
--------------------------------------------------------------------------------
1 | 'exercitation ullamco laboris nisi ut',
14 | 'url' => 'https://google.com/1-musicalbum',
15 | 'description' => 'Lorem ipsum dolor sit amet',
16 | 'numTracks' => 5,
17 | 'track' => [
18 | [
19 | '@type' => 'MusicRecording',
20 | 'url' => 'https://google.com/1-musicrecording',
21 | 'name' => 'magni dolores eo',
22 | 'duration' => 'PT1M33S', // 1 minute 33 seconds
23 | 'genre' => ['Ambient', 'Classical', 'Folk'],
24 | ],
25 | [
26 | '@type' => 'MusicRecording',
27 | 'url' => 'https://google.com/2-musicrecording',
28 | 'name' => 'totam rem aperiam',
29 | 'duration' => 'PT3M33S', // 3 minute 33 seconds
30 | 'genre' => 'Classical',
31 | ]
32 | ],
33 | 'byArtist' => [
34 | 'url' => 'https://google.com/1-musicgroup',
35 | 'name' => 'exercitation ullamco laboris nisi ut',
36 | 'description' => 'Lorem ipsum dolor sit amet',
37 | ],
38 | ];
39 |
40 | /**
41 | * @test
42 | */
43 | public function shouldHaveName()
44 | {
45 | $context = $this->make();
46 |
47 | $this->assertEquals('exercitation ullamco laboris nisi ut', $context->getProperty('name'));
48 | }
49 |
50 | /**
51 | * @test
52 | */
53 | public function shouldHaveDescription()
54 | {
55 | $context = $this->make();
56 |
57 | $this->assertEquals('Lorem ipsum dolor sit amet', $context->getProperty('description'));
58 | }
59 |
60 | /**
61 | * @test
62 | */
63 | public function shouldHaveTrackObject()
64 | {
65 | $context = $this->make();
66 |
67 | $this->assertEquals([
68 | [
69 | '@type' => 'MusicRecording',
70 | '@id' => 'https://google.com/1-musicrecording',
71 | 'name' => 'magni dolores eo',
72 | 'duration' => 'PT1M33S', // 1 minute 33 seconds
73 | 'genre' => 'Ambient, Classical, Folk',
74 | ],
75 | [
76 | '@type' => 'MusicRecording',
77 | '@id' => 'https://google.com/2-musicrecording',
78 | 'name' => 'totam rem aperiam',
79 | 'duration' => 'PT3M33S', // 3 minute 33 seconds
80 | 'genre' => 'Classical',
81 | ],
82 | ], $context->getProperty('track'));
83 | }
84 |
85 | /**
86 | * @test
87 | */
88 | public function shouldHaveByArtistObject()
89 | {
90 | $context = $this->make();
91 |
92 | $this->assertEquals([
93 | '@type' => 'MusicGroup',
94 | '@id' => 'https://google.com/1-musicgroup',
95 | 'name' => 'exercitation ullamco laboris nisi ut',
96 | 'description' => 'Lorem ipsum dolor sit amet',
97 | ], $context->getProperty('byArtist'));
98 | }
99 |
100 | /**
101 | * @test
102 | */
103 | public function shouldBeAbleToSetByArtistAsString()
104 | {
105 | $this->attributes['byArtist'] = 'voluptas nulla pariatu';
106 | $context = $this->make();
107 |
108 | $this->assertEquals('voluptas nulla pariatu', $context->getProperty('byArtist'));
109 | }
110 |
111 | }
112 |
--------------------------------------------------------------------------------
/tests/ContextTypes/PersonTest.php:
--------------------------------------------------------------------------------
1 | 'Anonymous tester',
13 | 'description' => 'a man in the middle',
14 | 'mainEntityOfPage' => [
15 | 'url' => 'https://example.com/anonymous.html'
16 | ],
17 | 'image' => [
18 | 'url' => 'https://google.com/Doctor.jpg',
19 | 'height' => 800,
20 | 'width' => 800
21 | ],
22 | 'additionalName' => 'phpUnit hacker',
23 | 'address' => [
24 | 'addressLocality' => 'Paris',
25 | 'addressCountry' => 'France'
26 | ],
27 | 'award' => 'phpUnit Excellence',
28 | 'birthDate' => '1943-10-04T00:00',
29 | 'birthPlace' => ['name' => 'Paris'],
30 | 'deathDate' => '2013-10-04T00:00',
31 | 'deathPlace' => ['name' => 'London'],
32 | 'email' => 'toto@yoyo.fr',
33 | 'familyName' => 'Dupondt',
34 | 'faxNumber' => '0000000000',
35 | 'follows' => [ 'name' => 'strange follower' ],
36 | 'givenName' => 'Doctor',
37 | 'homeLocation' => [
38 | 'name' => 'Fluff Hut',
39 | 'address' => [
40 | 'streetAddress' => '112 Apple St.',
41 | 'addressLocality' => 'Hamden',
42 | 'addressRegion' => 'CT',
43 | 'postalCode' => '06514',
44 | ],
45 | ],
46 | 'jobTitle' => 'tester',
47 | 'parent' => [ 'name' => 'daddy' ],
48 | 'telephone' => '+330102030405'
49 |
50 | ];
51 |
52 | public function test_should_have_properties() {
53 |
54 | $this->assertPropertyEquals('name', 'Anonymous tester');
55 |
56 | $this->assertPropertyEquals('description', 'a man in the middle');
57 |
58 | $this->assertPropertyEquals('mainEntityOfPage',
59 | [
60 | '@type' => 'WebPage',
61 | '@id' => 'https://example.com/anonymous.html',
62 | ]);
63 |
64 | $this->assertPropertyEquals('image',
65 | [
66 | '@type' => 'ImageObject',
67 | 'url' => 'https://google.com/Doctor.jpg',
68 | 'height' => 800,
69 | 'width' => 800,
70 | ]);
71 |
72 | $this->assertPropertyEquals('additionalName', 'phpUnit hacker');
73 |
74 | $this->assertPropertyEquals('address',
75 | [
76 | '@type' => 'PostalAddress',
77 | 'addressLocality' => 'Paris',
78 | 'addressCountry' => 'France'
79 | ]);
80 |
81 | $this->assertPropertyEquals('award', 'phpUnit Excellence');
82 |
83 | $this->assertPropertyEquals('birthDate', '1943-10-04T00:00');
84 |
85 | $this->assertPropertyEquals('birthPlace',
86 | [
87 | '@type' => 'Place',
88 | 'name' => 'Paris',
89 | ]);
90 |
91 | $this->assertPropertyEquals('deathDate', '2013-10-04T00:00');
92 |
93 | $this->assertPropertyEquals('deathPlace',
94 | [
95 | '@type' => 'Place',
96 | 'name' => 'London',
97 | ]);
98 |
99 | $this->assertPropertyEquals('email', 'toto@yoyo.fr');
100 |
101 | $this->assertPropertyEquals('familyName', 'Dupondt');
102 |
103 | $this->assertPropertyEquals('faxNumber', '0000000000');
104 |
105 | $this->assertPropertyEquals('follows',
106 | [
107 | '@type' => 'Person',
108 | 'name' => 'strange follower'
109 | ]);
110 |
111 | $this->assertPropertyEquals('givenName', 'Doctor');
112 |
113 | $this->assertPropertyEquals('homeLocation',
114 | [
115 | '@type' => 'Place',
116 | 'name' => 'Fluff Hut',
117 | 'address' => [
118 | '@type' => 'PostalAddress',
119 | 'streetAddress' => '112 Apple St.',
120 | 'addressLocality' => 'Hamden',
121 | 'addressRegion' => 'CT',
122 | 'postalCode' => '06514',
123 | ],
124 | ]
125 | );
126 |
127 | $this->assertPropertyEquals('jobTitle', 'tester');
128 |
129 | $this->assertPropertyEquals('parent',
130 | [
131 | '@type' => 'Person',
132 | 'name' => 'daddy'
133 | ]);
134 |
135 | $this->assertPropertyEquals('telephone', '+330102030405');
136 | }
137 |
138 | }
--------------------------------------------------------------------------------
/tests/ContextTypes/ProductTest.php:
--------------------------------------------------------------------------------
1 | 'Executive Anvil',
13 | 'category' => 'Droppables / Anvils',
14 | 'model' => 'Acme Exec. Anvil',
15 | 'image' => 'http://www.example.com/anvil_executive.jpg',
16 | 'description' => 'Sleeker than ACME\u0027s Classic Anvil, the Executive Anvil is perfect for the business traveler looking for something to drop from a height.',
17 | 'sku' => '925872',
18 | 'brand' => 'Acme Inc.',
19 | 'aggregateRating' => [
20 | 'ratingValue' => '4.4',
21 | 'reviewCount' => '89',
22 | ],
23 | 'offers' => [
24 | 'price' => '119.99',
25 | 'priceCurrency' => 'USD',
26 | 'priceValidUntil' => '2020-11-05',
27 | 'itemCondition' => 'http://schema.org/UsedCondition',
28 | 'availability' => 'http://schema.org/InStock',
29 | ],
30 | 'isSimilarTo' => [
31 | [
32 | '@type' => 'Product',
33 | 'name' => 'Lorem ipsum dolor sit amet.',
34 | 'category' => 'Vestibulum / Duis',
35 | 'model' => 'Quisque at tortor',
36 | 'image' => 'http://www.example.com/lorem_ipsum.jpg',
37 | 'description' => 'Nulla vestibulum augue turpis, a vehicula diam ultrices vitae. Vivamus suscipit id neque ac venenatis. Interdum et malesuada fames ac ante ipsum primis in faucibus.',
38 | 'sku' => '925379',
39 | ],
40 | [
41 | '@type' => 'Product',
42 | 'name' => 'Donec lorem metus, bibendum ut lacus et, bibendum luctus arcu.',
43 | 'category' => 'Maecenas / Maecenas',
44 | 'model' => 'Pellentesque mollis felis vitae porta dapibus.',
45 | 'image' => 'http://www.example.com/porta_ipsum.jpg',
46 | 'description' => 'Duis tempor velit dui, vel tempor velit posuere eu. Suspendisse rhoncus rhoncus nisl, eu bibendum nunc pharetra at. Quisque dictum diam a tellus ultrices, eu blandit mi auctor.',
47 | 'sku' => '185359',
48 | ],
49 | ]
50 | ];
51 |
52 | /**
53 | * @test
54 | */
55 | public function shouldHaveAggregateRatingObject()
56 | {
57 | $context = $this->make();
58 |
59 | $this->assertEquals([
60 | '@type' => 'AggregateRating',
61 | 'ratingValue' => '4.4',
62 | 'reviewCount' => '89',
63 | ], $context->getProperty('aggregateRating'));
64 | }
65 |
66 | /**
67 | * @test
68 | */
69 | public function shouldHaveOfferObject()
70 | {
71 | $context = $this->make();
72 |
73 | $this->assertEquals([
74 | '@type' => 'Offer',
75 | 'priceCurrency' => 'USD',
76 | 'price' => '119.99',
77 | 'priceValidUntil' => '2020-11-05',
78 | 'itemCondition' => 'http://schema.org/UsedCondition',
79 | 'availability' => 'http://schema.org/InStock'
80 | ], $context->getProperty('offers'));
81 | }
82 |
83 | /**
84 | * @test
85 | */
86 | public function shouldHaveIsSimilarToObjects()
87 | {
88 | $context = $this->make();
89 |
90 | $this->assertEquals([
91 | [
92 | '@type' => 'Product',
93 | 'name' => 'Lorem ipsum dolor sit amet.',
94 | 'category' => 'Vestibulum / Duis',
95 | 'model' => 'Quisque at tortor',
96 | 'image' => 'http://www.example.com/lorem_ipsum.jpg',
97 | 'description' => 'Nulla vestibulum augue turpis, a vehicula diam ultrices vitae. Vivamus suscipit id neque ac venenatis. Interdum et malesuada fames ac ante ipsum primis in faucibus.',
98 | 'sku' => '925379',
99 | ],
100 | [
101 | '@type' => 'Product',
102 | 'name' => 'Donec lorem metus, bibendum ut lacus et, bibendum luctus arcu.',
103 | 'category' => 'Maecenas / Maecenas',
104 | 'model' => 'Pellentesque mollis felis vitae porta dapibus.',
105 | 'image' => 'http://www.example.com/porta_ipsum.jpg',
106 | 'description' => 'Duis tempor velit dui, vel tempor velit posuere eu. Suspendisse rhoncus rhoncus nisl, eu bibendum nunc pharetra at. Quisque dictum diam a tellus ultrices, eu blandit mi auctor.',
107 | 'sku' => '185359',
108 | ],
109 | ], $context->getProperty('isSimilarTo'));
110 | }
111 | }
112 |
--------------------------------------------------------------------------------
/tests/ContextTypes/BookTest.php:
--------------------------------------------------------------------------------
1 | 'The Title of Book',
13 | 'isbn' => '00000000',
14 | 'abridged' => false,
15 | 'bookEdition' => 'Library edition',
16 | 'bookFormat' => \JsonLd\ContextTypes\BookFormatType::Paperback,
17 | 'numberOfPages' => '1234',
18 | 'datePublished' => '2013-10-04T00:00',
19 | 'aggregateRating' => [
20 | 'reviewCount' => 5,
21 | 'ratingValue' => 5,
22 | 'bestRating' => 4,
23 | 'worstRating' => 1,
24 | 'ratingCount' => 4,
25 | ],
26 | 'author' => [
27 | ['@type' => 'Person', 'name' => 'Joe Joe'],
28 | ['@type' => 'Person', 'name' => 'Jammy Joe'],
29 | ],
30 | 'image' => [
31 | 'url' => 'https://google.com/thumbnail1.jpg',
32 | 'height' => 800,
33 | 'width' => 800
34 | ],
35 | 'publisher' => [
36 | 'name' => 'Google',
37 | 'logo' => [
38 | '@type' => 'ImageObject',
39 | 'url' => 'https://google.com/logo.jpg',
40 | 'width' => 600,
41 | 'height' => 60,
42 | ]
43 | ],
44 | 'review' => [
45 | ['@type' => 'Review', 'name' => 'first review', 'reviewRating' => 3],
46 | ['@type' => 'Review', 'name' => 'second review', 'reviewRating' => 5],
47 | ],
48 | 'video' => [
49 | 'url' => 'https://google.com/thumbnail1.mov',
50 | 'height' => 800,
51 | 'width' => 800
52 | ],
53 | ];
54 |
55 | /**
56 | * @test
57 | */
58 | public function shouldHaveAggregateRatingObject()
59 | {
60 | $context = $this->make();
61 |
62 | $this->assertEquals([
63 | '@type' => 'AggregateRating',
64 | 'reviewCount' => 5,
65 | 'ratingValue' => 5,
66 | 'bestRating' => 4,
67 | 'worstRating' => 1,
68 | 'ratingCount' => 4,
69 | ], $context->getProperty('aggregateRating'));
70 | }
71 |
72 | /**
73 | * @test
74 | */
75 | public function shouldHave2AuthorsArray()
76 | {
77 | $context = $this->make();
78 |
79 | $this->assertEquals([
80 | '@type' => 'Person',
81 | 'name' => 'Joe Joe',
82 | ], $context->getProperty('author')[0]);
83 | $this->assertEquals([
84 | '@type' => 'Person',
85 | 'name' => 'Jammy Joe',
86 | ], $context->getProperty('author')[1]);
87 | }
88 |
89 | /**
90 | * @test
91 | */
92 | public function shouldHaveImageObject()
93 | {
94 | $context = $this->make();
95 |
96 | $this->assertEquals([
97 | '@type' => 'ImageObject',
98 | 'url' => 'https://google.com/thumbnail1.jpg',
99 | 'height' => 800,
100 | 'width' => 800,
101 | ], $context->getProperty('image'));
102 | }
103 |
104 | /**
105 | * @test
106 | */
107 | public function shouldHavePublisherObject()
108 | {
109 | $context = $this->make();
110 |
111 | $this->assertEquals([
112 | '@type' => 'Organization',
113 | 'name' => 'Google',
114 | 'logo' => [
115 | '@type' => 'ImageObject',
116 | 'url' => 'https://google.com/logo.jpg',
117 | 'height' => 60,
118 | 'width' => 600,
119 | ],
120 | ], $context->getProperty('publisher'));
121 | }
122 |
123 | /**
124 | * @test
125 | */
126 | public function shouldHave2ReviewsArray()
127 | {
128 | $context = $this->make();
129 |
130 | $this->assertEquals([
131 | '@type' => 'Review',
132 | 'name' => 'first review',
133 | 'reviewRating' => 3
134 | ], $context->getProperty('review')[0]);
135 | $this->assertEquals([
136 | '@type' => 'Review',
137 | 'name' => 'second review',
138 | 'reviewRating' => 5
139 | ], $context->getProperty('review')[1]);
140 | }
141 |
142 | /**
143 | * @test
144 | */
145 | public function shouldHaveVideoObject()
146 | {
147 | $context = $this->make();
148 |
149 | $this->assertEquals([
150 | '@type' => 'VideoObject',
151 | 'url' => 'https://google.com/thumbnail1.mov',
152 | 'height' => 800,
153 | 'width' => 800,
154 | ], $context->getProperty('video'));
155 | }
156 | }
157 |
--------------------------------------------------------------------------------
/tests/ContextTypes/CreativeWorkTest.php:
--------------------------------------------------------------------------------
1 | [
13 | 'reviewCount' => 5,
14 | 'ratingValue' => 5,
15 | 'bestRating' => 4,
16 | 'worstRating' => 1,
17 | 'ratingCount' => 4,
18 | ],
19 | 'author' => [
20 | ['@type' => 'Person',
21 | 'name' => 'Joe Joe'],
22 | ['@type' => 'Person',
23 | 'name' => 'Jammy Joe'],
24 | ],
25 | 'creator' => [
26 | 'name' => 'Joe Joe',
27 | ],
28 | 'image' => [
29 | "url" => 'https://google.com/thumbnail1.jpg',
30 | "height" => 800,
31 | "width" => 800
32 | ],
33 | 'publisher' => [
34 | 'name' => 'Google',
35 | 'logo' => [
36 | '@type' => 'ImageObject',
37 | 'url' => 'https://google.com/logo.jpg',
38 | 'width' => 600,
39 | 'height' => 60,
40 | ]
41 | ],
42 | 'review' => [
43 | ['@type' => 'Review', 'name' => 'first review', 'reviewRating' => 3],
44 | ['@type' => 'Review', 'name' => 'second review', 'reviewRating' => 5],
45 | ],
46 | 'video' => [
47 | "url" => 'https://google.com/thumbnail1.mov',
48 | "height" => 800,
49 | "width" => 800
50 | ],
51 | ];
52 |
53 | /**
54 | * @test
55 | */
56 | public function shouldHaveAggregateRatingObject()
57 | {
58 | $context = $this->make();
59 |
60 | $this->assertEquals([
61 | '@type' => 'AggregateRating',
62 | 'reviewCount' => 5,
63 | 'ratingValue' => 5,
64 | 'bestRating' => 4,
65 | 'worstRating' => 1,
66 | 'ratingCount' => 4,
67 | ], $context->getProperty('aggregateRating'));
68 | }
69 |
70 | /**
71 | * @test
72 | */
73 | public function shouldHave2AuthorsArray()
74 | {
75 | $context = $this->make();
76 |
77 | $this->assertEquals([
78 | '@type' => 'Person',
79 | 'name' => 'Joe Joe',
80 | ], $context->getProperty('author')[0]);
81 | $this->assertEquals([
82 | '@type' => 'Person',
83 | 'name' => 'Jammy Joe',
84 | ], $context->getProperty('author')[1]);
85 | }
86 |
87 |
88 | /**
89 | * @test
90 | */
91 | public function shouldHaveCreatorObject()
92 | {
93 | $context = $this->make();
94 |
95 | $this->assertEquals([
96 | '@type' => 'Person',
97 | 'name' => 'Joe Joe',
98 | ], $context->getProperty('creator'));
99 | }
100 |
101 | /**
102 | * @test
103 | */
104 | public function shouldHaveImageObject()
105 | {
106 | $context = $this->make();
107 |
108 | $this->assertEquals([
109 | '@type' => 'ImageObject',
110 | 'url' => 'https://google.com/thumbnail1.jpg',
111 | 'height' => 800,
112 | 'width' => 800,
113 | ], $context->getProperty('image'));
114 | }
115 |
116 | /**
117 | * @test
118 | */
119 | public function shouldHavePublisherObject()
120 | {
121 | $context = $this->make();
122 |
123 | $this->assertEquals([
124 | '@type' => 'Organization',
125 | 'name' => 'Google',
126 | 'logo' => [
127 | '@type' => 'ImageObject',
128 | 'url' => 'https://google.com/logo.jpg',
129 | 'height' => 60,
130 | 'width' => 600,
131 | ],
132 | ], $context->getProperty('publisher'));
133 | }
134 |
135 | /**
136 | * @test
137 | */
138 | public function shouldHave2ReviewsArray()
139 | {
140 | $context = $this->make();
141 |
142 | $this->assertEquals([
143 | '@type' => 'Review',
144 | 'name' => 'first review',
145 | 'reviewRating' => 3
146 | ], $context->getProperty('review')[0]);
147 | $this->assertEquals([
148 | '@type' => 'Review',
149 | 'name' => 'second review',
150 | 'reviewRating' => 5
151 | ], $context->getProperty('review')[1]);
152 | }
153 |
154 | /**
155 | * @test
156 | */
157 | public function shouldHaveVideoObject()
158 | {
159 | $context = $this->make();
160 |
161 | $this->assertEquals([
162 | '@type' => 'VideoObject',
163 | 'url' => 'https://google.com/thumbnail1.mov',
164 | 'height' => 800,
165 | 'width' => 800,
166 | ], $context->getProperty('video'));
167 | }
168 | }
169 |
--------------------------------------------------------------------------------
/tests/ContextTypes/AudioBookTest.php:
--------------------------------------------------------------------------------
1 | 'The Title of Book',
13 | 'isbn' => '00000000',
14 | 'abridged' => false,
15 | 'bookEdition' => 'Library edition',
16 | 'bookFormat' => \JsonLd\ContextTypes\BookFormatType::Paperback,
17 | 'transcript' => 'Some text and other things',
18 | 'numberOfPages' => '1234',
19 | 'datePublished' => '2013-10-04T00:00',
20 | 'aggregateRating' => [
21 | 'reviewCount' => 5,
22 | 'ratingValue' => 5,
23 | 'bestRating' => 4,
24 | 'worstRating' => 1,
25 | 'ratingCount' => 4,
26 | ],
27 | 'author' => [
28 | '@type' => 'Person',
29 | 'name' => 'Joe Joe',
30 | ],
31 | 'readBy' => [
32 | '@type' => 'Person',
33 | 'name' => 'Jammy Jane'
34 | ],
35 | 'image' => [
36 | 'url' => 'https://google.com/thumbnail1.jpg',
37 | 'height' => 800,
38 | 'width' => 800
39 | ],
40 | 'publisher' => [
41 | 'name' => 'Google',
42 | 'logo' => [
43 | '@type' => 'ImageObject',
44 | 'url' => 'https://google.com/logo.jpg',
45 | 'width' => 600,
46 | 'height' => 60,
47 | ]
48 | ],
49 | 'review' => [
50 | ['@type' => 'Review', 'name' => 'first review', 'reviewRating' => 3],
51 | ['@type' => 'Review', 'name' => 'second review', 'reviewRating' => 5],
52 | ],
53 | 'caption' => [
54 | 'contentUrl' => 'https://google.com/thumbnail1.mov',
55 | 'bitrate' => '320kbps',
56 | ],
57 | ];
58 |
59 | /**
60 | * @test
61 | */
62 | public function shouldHaveAggregateRatingObject()
63 | {
64 | $context = $this->make();
65 |
66 | $this->assertEquals([
67 | '@type' => 'AggregateRating',
68 | 'reviewCount' => 5,
69 | 'ratingValue' => 5,
70 | 'bestRating' => 4,
71 | 'worstRating' => 1,
72 | 'ratingCount' => 4,
73 | ], $context->getProperty('aggregateRating'));
74 | }
75 |
76 | /**
77 | * @test
78 | */
79 | public function shouldHaveAuthorArray()
80 | {
81 | $context = $this->make();
82 |
83 | $this->assertEquals([
84 | '@type' => 'Person',
85 | 'name' => 'Joe Joe',
86 | ], $context->getProperty('author'));
87 | }
88 |
89 | /**
90 | * @test
91 | */
92 | public function shouldHaveReadByArray()
93 | {
94 | $context = $this->make();
95 |
96 | $this->assertEquals([
97 | '@type' => 'Person',
98 | 'name' => 'Jammy Jane',
99 | ], $context->getProperty('readBy'));
100 | }
101 |
102 | /**
103 | * @test
104 | */
105 | public function shouldHaveImageObject()
106 | {
107 | $context = $this->make();
108 |
109 | $this->assertEquals([
110 | '@type' => 'ImageObject',
111 | 'url' => 'https://google.com/thumbnail1.jpg',
112 | 'height' => 800,
113 | 'width' => 800,
114 | ], $context->getProperty('image'));
115 | }
116 |
117 | /**
118 | * @test
119 | */
120 | public function shouldHavePublisherObject()
121 | {
122 | $context = $this->make();
123 |
124 | $this->assertEquals([
125 | '@type' => 'Organization',
126 | 'name' => 'Google',
127 | 'logo' => [
128 | '@type' => 'ImageObject',
129 | 'url' => 'https://google.com/logo.jpg',
130 | 'height' => 60,
131 | 'width' => 600,
132 | ],
133 | ], $context->getProperty('publisher'));
134 | }
135 |
136 | /**
137 | * @test
138 | */
139 | public function shouldHave2ReviewsArray()
140 | {
141 | $context = $this->make();
142 |
143 | $this->assertEquals([
144 | '@type' => 'Review',
145 | 'name' => 'first review',
146 | 'reviewRating' => 3
147 | ], $context->getProperty('review')[0]);
148 | $this->assertEquals([
149 | '@type' => 'Review',
150 | 'name' => 'second review',
151 | 'reviewRating' => 5
152 | ], $context->getProperty('review')[1]);
153 | }
154 |
155 | /**
156 | * @test
157 | */
158 | public function shouldHaveCaptionObject()
159 | {
160 | $context = $this->make();
161 |
162 | $this->assertEquals([
163 | '@type' => 'MediaObject',
164 | 'contentUrl' => 'https://google.com/thumbnail1.mov',
165 | 'bitrate' => '320kbps',
166 | ], $context->getProperty('caption'));
167 | }
168 | }
169 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # JSON-LD Generator
2 |
3 | [](https://travis-ci.org/Torann/json-ld)
4 | [](https://packagist.org/packages/torann/json-ld)
5 | [](https://packagist.org/packages/torann/json-ld)
6 | [](https://www.patreon.com/torann)
7 | [](https://liberapay.com/Torann/)
8 | [](https://flattr.com/profile/torann)
9 | [](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=4CJA2A97NPYVU)
10 |
11 | Extremely simple JSON-LD generator.
12 |
13 | ## Installation
14 |
15 | - [JSON-LD Generator on Packagist](https://packagist.org/packages/torann/json-ld)
16 | - [JSON-LD Generator on GitHub](https://github.com/Torann/json-ld)
17 |
18 | From the command line run
19 |
20 | ```
21 | $ composer require torann/json-ld
22 | ```
23 |
24 | ## Methods
25 |
26 | **/JsonLd/Context.php**
27 |
28 | - `create($context, array $data = [])`
29 | - `getProperties()`
30 | - `generate()`
31 |
32 | ## Context Types
33 |
34 | - article
35 | - audiobook
36 | - beach
37 | - blog_posting
38 | - book
39 | - breadcrumb_list
40 | - contact_point
41 | - corporation
42 | - creative_work
43 | - duration
44 | - event
45 | - geo_coordinates
46 | - image_object
47 | - invoice
48 | - list_item
49 | - local_business
50 | - media_object
51 | - music_album
52 | - music_group
53 | - music_playlist
54 | - music_recording
55 | - news_article
56 | - offer
57 | - order
58 | - organization
59 | - person
60 | - place
61 | - postal_address
62 | - price_specification
63 | - product
64 | - rating
65 | - recipe
66 | - review
67 | - sculpture
68 | - search_box
69 | - thing
70 | - video_object
71 | - web_page
72 | - web_site
73 |
74 | ## Examples
75 |
76 | ### Quick Example
77 |
78 | #### Business
79 |
80 | ```php
81 | $context = \JsonLd\Context::create('local_business', [
82 | 'name' => 'Consectetur Adipiscing',
83 | 'description' => 'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor',
84 | 'telephone' => '555-555-5555',
85 | 'openingHours' => 'mon,tue,fri',
86 | 'address' => [
87 | 'streetAddress' => '112 Apple St.',
88 | 'addressLocality' => 'Hamden',
89 | 'addressRegion' => 'CT',
90 | 'postalCode' => '06514',
91 | ],
92 | 'geo' => [
93 | 'latitude' => '41.3958333',
94 | 'longitude' => '-72.8972222',
95 | ],
96 | ]);
97 |
98 | echo $context; // Will output the script tag
99 | ```
100 |
101 | ### News Article
102 |
103 | ```php
104 | $context = \JsonLd\Context::create('news_article', [
105 | 'headline' => 'Article headline',
106 | 'description' => 'A most wonderful article',
107 | 'mainEntityOfPage' => [
108 | 'url' => 'https://google.com/article',
109 | ],
110 | 'image' => [
111 | 'url' => 'https://google.com/thumbnail1.jpg',
112 | 'height' => 800,
113 | 'width' => 800,
114 | ],
115 | 'datePublished' => '2015-02-05T08:00:00+08:00',
116 | 'dateModified' => '2015-02-05T09:20:00+08:00',
117 | 'author' => [
118 | 'name' => 'John Doe',
119 | ],
120 | 'publisher' => [
121 | 'name' => 'Google',
122 | 'logo' => [
123 | 'url' => 'https://google.com/logo.jpg',
124 | 'width' => 600,
125 | 'height' => 60,
126 | ]
127 | ],
128 | ]);
129 |
130 | echo $context; // Will output the script tag
131 | ```
132 |
133 | ### Using the JSON-LD in a Laracasts Presenter
134 |
135 | Even though this example shows using the JSON-LD inside of a `Laracasts\Presenter` presenter, Laravel is not required for this package.
136 |
137 | #### /App/Presenters/BusinessPresenter.php
138 |
139 | ```php
140 | $this->entity->name,
158 | 'description' => $this->entity->description,
159 | 'telephone' => $this->entity->telephone,
160 | 'openingHours' => 'mon,tue,fri',
161 | 'address' => [
162 | 'streetAddress' => $this->entity->address,
163 | 'addressLocality' => $this->entity->city,
164 | 'addressRegion' => $this->entity->state,
165 | 'postalCode' => $this->entity->postalCode,
166 | ],
167 | 'geo' => [
168 | 'latitude' => $this->entity->location->lat,
169 | 'longitude' => $this->entity->location->lng,
170 | ],
171 | ]);
172 | }
173 | }
174 | ```
175 |
176 | #### Generate the Tags
177 |
178 | The generator comes with a `__toString` method that will automatically generate the correct script tags when displayed as a string.
179 |
180 | **Inside of a Laravel View**
181 |
182 | ```php
183 | echo $business->present()->jsonLd();
184 | ```
185 |
186 | **Inside of a Laravel View**
187 |
188 | ```
189 | {!! $business->present()->jsonLd() !!}
190 | ```
191 |
192 | ## Custom Context Type
193 |
194 | The first argument for the `create($context, array $data = [])` method also accepts class names. This is helpful for custom context types.
195 |
196 | ```php
197 | null,
212 | 'description' => null,
213 | 'image' => null,
214 | 'url' => null,
215 | ];
216 | }
217 | ```
218 |
219 | ```php
220 | $context = \JsonLd\Context::create(\App\JsonLd\FooBar::class, [
221 | 'name' => 'Foo Foo headline',
222 | 'description' => 'Bar bar article description',
223 | 'url' => 'http://google.com',
224 | ]);
225 |
226 | echo $context; // Will output the script tag
227 | ```
--------------------------------------------------------------------------------
/tests/ContextTypes/ArticleTest.php:
--------------------------------------------------------------------------------
1 | 'articleNComments',
13 | 'url' => 'https://google.com/1-article',
14 | 'description' => 'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur.',
15 | 'image' => [
16 | 'url' => 'https://google.com/thumbnail1.jpg',
17 | 'height' => 800,
18 | 'width' => 800
19 | ],
20 | 'video' => [
21 | 'url' => 'https://google.com/thumbnail1.mov',
22 | 'height' => 800,
23 | 'width' => 800
24 | ],
25 | 'thumbnailUrl' => 'https://google.com/thumbnail1.jpg',
26 | 'text' => 'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco.',
27 | 'review' => [
28 | '@type' => 'Review',
29 | 'reviewRating' => 5,
30 | ],
31 | 'publisher' => [
32 | 'name' => 'Google',
33 | 'logo' => [
34 | '@type' => 'ImageObject',
35 | 'url' => 'https://google.com/logo.jpg',
36 | 'width' => 600,
37 | 'height' => 60,
38 | ]
39 | ],
40 | 'keywords' => 'Lorem,ipsum,dolor',
41 | 'inLanguage' => 'en',
42 | 'commentCount' => 2,
43 | 'comment' => [
44 | ['@type' => 'Comment',
45 | 'author' => ['@type' => 'Person', 'name' => 'Joe Joe'],
46 | 'text' => 'first comment',
47 | 'dateCreated' => '2018-06-14T21:40:00+02:00'],
48 | ['@type' => 'Comment',
49 | 'author' => ['@type' => 'Person', 'name' => 'Joe Bis'],
50 | 'text' => 'second comment',
51 | 'dateCreated' => '2018-06-14T23:23:00+02:00']
52 | ],
53 | 'dateCreated' => '2013-10-04T00:00',
54 | 'dateModified' => '2013-10-04T00:00',
55 | 'datePublished' => '2013-10-04T00:00',
56 | 'author' => [
57 | '@type' => 'Person',
58 | 'name' => 'Joe Joe',
59 | ],
60 | 'mainEntityOfPage' => [
61 | 'url' => 'https://blogspot.com/100-article'
62 | ],
63 | 'headline' => 'eiusmod tempor incididunt ut labore et dolore magna aliqua.',
64 | ];
65 |
66 | /**
67 | * @test
68 | */
69 | public function shouldHaveThingName()
70 | {
71 | $context = $this->make();
72 |
73 | $this->assertEquals('articleNComments', $context->getProperty('name'));
74 | }
75 |
76 | /**
77 | * @test
78 | */
79 | public function shouldHaveThingMainEntityOfPageObject()
80 | {
81 | $context = $this->make();
82 |
83 | $this->assertEquals([
84 | '@type' => 'WebPage',
85 | '@id' => 'https://blogspot.com/100-article',
86 | ], $context->getProperty('mainEntityOfPage'));
87 | }
88 |
89 | /**
90 | * @test
91 | */
92 | public function shouldHaveCreativeWorkInLanguage()
93 | {
94 | $context = $this->make();
95 |
96 | $this->assertEquals('en', $context->getProperty('inLanguage'));
97 | }
98 |
99 | /**
100 | * @test
101 | */
102 | public function shouldHaveCreativeWorkAuthorObject()
103 | {
104 | $context = $this->make();
105 |
106 | $this->assertEquals([
107 | '@type' => 'Person',
108 | 'name' => 'Joe Joe',
109 | ], $context->getProperty('author'));
110 | }
111 |
112 | /**
113 | * @test
114 | */
115 | public function shouldTruncateDescription()
116 | {
117 | $context = $this->make();
118 |
119 | $this->assertEquals('Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit...', $context->getProperty('description'));
120 | }
121 |
122 | /**
123 | * @test
124 | */
125 | public function shouldHaveHeadline()
126 | {
127 | $context = $this->make();
128 |
129 | $this->assertEquals('eiusmod tempor incididunt ut labore et dolore magna aliqua.', $context->getProperty('headline'));
130 | }
131 |
132 | /**
133 | * @test
134 | */
135 | public function shouldHaveImageObject()
136 | {
137 | $context = $this->make();
138 |
139 | $this->assertEquals([
140 | '@type' => 'ImageObject',
141 | 'url' => 'https://google.com/thumbnail1.jpg',
142 | 'height' => 800,
143 | 'width' => 800,
144 | ], $context->getProperty('image'));
145 | }
146 |
147 | /**
148 | * @test
149 | */
150 | public function shouldHaveVideoObject()
151 | {
152 | $context = $this->make();
153 |
154 | $this->assertEquals([
155 | '@type' => 'VideoObject',
156 | 'url' => 'https://google.com/thumbnail1.mov',
157 | 'height' => 800,
158 | 'width' => 800,
159 | ], $context->getProperty('video'));
160 | }
161 |
162 | /**
163 | * @test
164 | */
165 | public function shouldHaveReviewObject()
166 | {
167 | $context = $this->make();
168 |
169 | $this->assertEquals([
170 | '@type' => 'Review',
171 | 'reviewRating' => 5,
172 | ], $context->getProperty('review'));
173 | }
174 |
175 | /**
176 | * @test
177 | */
178 | public function shouldHavePublisherObject()
179 | {
180 | $context = $this->make();
181 |
182 | $this->assertEquals([
183 | '@type' => 'Organization',
184 | 'name' => 'Google',
185 | 'logo' => [
186 | '@type' => 'ImageObject',
187 | 'url' => 'https://google.com/logo.jpg',
188 | 'height' => 60,
189 | 'width' => 600,
190 | ],
191 | ], $context->getProperty('publisher'));
192 | }
193 |
194 | /**
195 | * @test
196 | */
197 | public function shouldHaveKeywords()
198 | {
199 | $context = $this->make();
200 |
201 | $this->assertEquals('Lorem,ipsum,dolor', $context->getProperty('keywords'));
202 | }
203 |
204 | /**
205 | * @test
206 | */
207 | public function shouldHave2CommentsArray()
208 | {
209 | $context = $this->make();
210 |
211 | $this->assertEquals([
212 | '@type' => 'Comment',
213 | 'text' => 'first comment',
214 | 'author' => ['@type' => 'Person', 'name' => 'Joe Joe'],
215 | 'dateCreated' => '2018-06-14T21:40:00+02:00',
216 | ], $context->getProperty('comment')[0]);
217 | $this->assertEquals([
218 | '@type' => 'Comment',
219 | 'text' => 'second comment',
220 | 'author' => ['@type' => 'Person', 'name' => 'Joe Bis'],
221 | 'dateCreated' => '2018-06-14T23:23:00+02:00',
222 | ], $context->getProperty('comment')[1]);
223 | }
224 |
225 | }
226 |
--------------------------------------------------------------------------------
/src/ContextTypes/AbstractContext.php:
--------------------------------------------------------------------------------
1 | '',
32 | ];
33 |
34 | /**
35 | * Property structure
36 | *
37 | * @var array
38 | */
39 | protected $extendStructure = [];
40 |
41 | /**
42 | * Property structure, will be merged up for objects extending Thing
43 | *
44 | * @var array
45 | */
46 | private $extendedStructure = [];
47 |
48 | /**
49 | * Create a new context type instance
50 | *
51 | * @param array $attributes
52 | */
53 | public function __construct(array $attributes = [])
54 | {
55 | // Set type
56 | $path = explode('\\', get_class($this));
57 | $this->type = end($path);
58 |
59 | // Set attributes
60 | $this->fill($attributes);
61 | }
62 |
63 | /**
64 | * After fill event.
65 | *
66 | * @param array $attributes
67 | */
68 | public function afterFill($attributes)
69 | {
70 | //
71 | }
72 |
73 | /**
74 | * Creates an array of schema.org attribute from attributes.
75 | *
76 | * @param array $attributes
77 | */
78 | public function fill($attributes)
79 | {
80 | // Some context types have varying types
81 | if ($this->hasGetMutator('type')) {
82 | $this->type = $this->mutateAttribute('type', $this->getArrValue($attributes, 'type', $this->type));
83 | }
84 |
85 | // Set properties
86 | $properties = array_merge([
87 | '@context' => 'http://schema.org',
88 | '@type' => $this->type,
89 | 'sameAs' => null
90 | ], $this->structure, $this->extendStructure);
91 |
92 | // Set properties from attributes
93 | foreach ($properties as $key => $property) {
94 | $this->setProperty($key, $property, $this->getArrValue($attributes, $key, ''));
95 | }
96 |
97 | // After fill event
98 | $this->afterFill($attributes);
99 | }
100 |
101 | /**
102 | * Set sameAs Attribute
103 | *
104 | * @param mixed $value
105 | *
106 | * @return mixed
107 | */
108 | protected function setSameAsAttribute($value)
109 | {
110 | return $value;
111 | }
112 |
113 | /**
114 | * Creates context properties.
115 | *
116 | * @return array
117 | */
118 | public function getProperties()
119 | {
120 | return $this->properties;
121 | }
122 |
123 | /**
124 | * Get an item from properties.
125 | *
126 | * @param string $key
127 | * @param mixed $default
128 | *
129 | * @return mixed
130 | */
131 | public function getProperty($key, $default = null)
132 | {
133 | return $this->getArrValue($this->getProperties(), $key, $default);
134 | }
135 |
136 | /**
137 | * Set property value in attributes.
138 | *
139 | * @param string $key
140 | * @param mixed $property
141 | * @param mixed $value
142 | *
143 | * @return mixed
144 | */
145 | protected function setProperty($key, $property, $value = null)
146 | {
147 | // Can't be changed
148 | if ($key[0] === '@') {
149 | return $this->properties[$key] = $property;
150 | }
151 |
152 | // If the attribute has a get mutator, we will call that
153 | // then return what it returns as the value.
154 | if ($this->hasGetMutator($key)) {
155 | return $this->properties[$key] = $this->mutateAttribute($key, $value);
156 | }
157 |
158 | // Format date and time to UTC
159 | if ($value instanceof DateTime) {
160 | return $this->properties[$key] = $value->format('Y-m-d\TH:i:s');
161 | }
162 |
163 | // Set nested context
164 | if ($value instanceof Context) {
165 | return $this->properties[$key] = $this->filterNestedContext($value->getProperties());
166 | }
167 |
168 | // Set nested context from class
169 | if ($property && class_exists($property)) {
170 | return $this->properties[$key] = $this->getNestedContext($property, $value);
171 | }
172 |
173 | // Map properties to object
174 | if ($property !== null && is_array($property) && is_array($value)) {
175 | return $this->properties[$key] = $this->mapProperty($property, $value);
176 | }
177 |
178 | // Set value
179 | return $this->properties[$key] = $value;
180 | }
181 |
182 | /**
183 | * Set context type.
184 | *
185 | * @param string $type
186 | *
187 | * @return void
188 | */
189 | protected function setType($type)
190 | {
191 | $this->properties['@type'] = $type;
192 | }
193 |
194 | /**
195 | * Get nested context array.
196 | *
197 | * @param string $class
198 | * @param array $attributes
199 | *
200 | * @return array
201 | */
202 | protected function getNestedContext($class, $attributes = null)
203 | {
204 | // Must be an array
205 | if (is_array($attributes) === false) return $attributes;
206 |
207 | // Create nested context
208 | $context = new $class($attributes);
209 |
210 | // Return context attributes
211 | return $this->filterNestedContext($context->getProperties());
212 | }
213 |
214 | /**
215 | * Filter nested context array.
216 | *
217 | * @param array $properties
218 | *
219 | * @return array
220 | */
221 | protected function filterNestedContext(array $properties = [])
222 | {
223 | $func = function ($value, $key) {
224 | return ($value && $key !== '@context');
225 | };
226 |
227 | if (defined('ARRAY_FILTER_USE_BOTH') === false) {
228 | $return = [];
229 | foreach ($properties as $k => $v) {
230 | if (call_user_func($func, $v, $k)) {
231 | $return[$k] = $v;
232 | }
233 | }
234 |
235 | return $return;
236 | }
237 |
238 | return array_filter($properties, $func, ARRAY_FILTER_USE_BOTH);
239 | }
240 |
241 | /**
242 | * Determine if a get mutator exists for an attribute.
243 | *
244 | * @param string $key
245 | *
246 | * @return bool
247 | */
248 | protected function hasGetMutator($key)
249 | {
250 | return method_exists($this, 'set' . $this->getMutatorName($key) . 'Attribute');
251 | }
252 |
253 | /**
254 | * Get the value of an attribute using its mutator.
255 | *
256 | * @param string $key
257 | * @param mixed $args
258 | *
259 | * @return mixed
260 | */
261 | protected function mutateAttribute($key, $args)
262 | {
263 | return $this->{'set' . $this->getMutatorName($key) . 'Attribute'}($args);
264 | }
265 |
266 | /**
267 | * Get mutator name from string.
268 | *
269 | * @param string $value
270 | *
271 | * @return string
272 | */
273 | protected function getMutatorName($value)
274 | {
275 | $value = ucwords(str_replace(['-', '_'], ' ', $value));
276 |
277 | return lcfirst(str_replace(' ', '', $value));
278 | }
279 |
280 | /**
281 | * Get property value from attributes.
282 | *
283 | * @param array $template
284 | * @param array $props
285 | *
286 | * @return mixed
287 | */
288 | protected function mapProperty(array $template = [], $props = [])
289 | {
290 | // No values set
291 | if (is_array($props) === false) {
292 | return null;
293 | }
294 |
295 | foreach ($template as $key => $value) {
296 | if ($key[0] !== '@') {
297 | $template[$key] = $this->getArrValue($props, $key, '');
298 | }
299 | }
300 |
301 | return array_filter($template);
302 | }
303 |
304 | /**
305 | * Trim a string to a given number of words
306 | *
307 | * @param string $string
308 | * @param int $limit
309 | * @param string $pad
310 | * @param string $break
311 | *
312 | * @return string
313 | */
314 | protected function truncate($string, $limit, $pad = '...', $break = ' ')
315 | {
316 | // return with no change if string is shorter than $limit
317 | if (strlen($string) <= $limit) return $string;
318 |
319 | // is $break present between $limit and the end of the string?
320 | if (false !== ($breakpoint = strpos($string, $break, $limit))) {
321 | if ($breakpoint < strlen($string) - 1) {
322 | $string = substr($string, 0, $breakpoint) . $pad;
323 | }
324 | }
325 |
326 | return $string;
327 | }
328 |
329 | /**
330 | * Get an item from an array.
331 | *
332 | * @param array $array
333 | * @param string $key
334 | * @param mixed $default
335 | *
336 | * @return mixed
337 | */
338 | protected function getArrValue(array $array, $key, $default = null)
339 | {
340 | if (is_null($key)) {
341 | return $default;
342 | }
343 |
344 | if (isset($array[$key])) {
345 | return $array[$key];
346 | }
347 |
348 | return $default;
349 | }
350 | }
351 |
--------------------------------------------------------------------------------