├── .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 | [![Build Status](https://travis-ci.org/Torann/json-ld.svg)](https://travis-ci.org/Torann/json-ld) 4 | [![Latest Stable Version](https://poser.pugx.org/torann/json-ld/v/stable.png)](https://packagist.org/packages/torann/json-ld) 5 | [![Total Downloads](https://poser.pugx.org/torann/json-ld/downloads.png)](https://packagist.org/packages/torann/json-ld) 6 | [![Patreon donate button](https://img.shields.io/badge/patreon-donate-yellow.svg)](https://www.patreon.com/torann) 7 | [![Donate weekly to this project using Gratipay](https://img.shields.io/badge/gratipay-donate-yellow.svg)](https://liberapay.com/Torann/) 8 | [![Donate to this project using Flattr](https://img.shields.io/badge/flattr-donate-yellow.svg)](https://flattr.com/profile/torann) 9 | [![Donate to this project using Paypal](https://img.shields.io/badge/Donate-PayPal-green.svg)](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 | --------------------------------------------------------------------------------