src/SEOTools/OpenGraph.php
<?php
namespace Artesaos\SEOTools;
use Illuminate\Support\Arr;
use Artesaos\SEOTools\Contracts\OpenGraph as OpenGraphContract;
/**
* OpenGraph provides implementation for `OpenGraph` contract.
*
* @see \Artesaos\SEOTools\Contracts\OpenGraph
*/
class OpenGraph implements OpenGraphContract
{
/**
* OpenGraph Prefix.
*
* @var string
*/
protected $og_prefix = 'og:';
/**
* Config.
*
* @var array
*/
protected $config;
/**
* Url property
*
* @var string
*/
protected $url = '';
/**
* Array of Properties.
*
* @var array
*/
protected $properties = [];
/**
* Array of Article Properties.
*
* @var array
*/
protected $articleProperties = [];
/**
* Array of Profile Properties.
*
* @var array
*/
protected $profileProperties = [];
/**
* Array of Music Song Properties.
*
* @var array
*/
protected $musicSongProperties = [];
/**
* Array of Music Album Properties.
*
* @var array
*/
protected $musicAlbumProperties = [];
/**
* Array of Music Playlist Properties.
*
* @var array
*/
protected $musicPlaylistProperties = [];
/**
* Array of Music Radio Properties.
*
* @var array
*/
protected $musicRadioStationProperties = [];
/**
* Array of Video Movie Properties.
*
* @var array
*/
protected $videoMovieProperties = [];
/**
* Array of Video Episode Properties.
*
* @var array
*/
protected $videoEpisodeProperties = [];
/**
* Array of Video TV Show Properties.
*
* @var array
*/
protected $videoTVShowProperties = [];
/**
* Array of Video Other Properties.
*
* @var array
*/
protected $videoOtherProperties = [];
/**
* Array of Book Properties.
*
* @var array
*/
protected $bookProperties = [];
/**
* Array of Video Properties.
*
* @var array
*/
protected $videoProperties = [];
/**
* Array of Audio Properties.
*
* @var array
*/
protected $audioProperties = [];
/**
* Array of Place Properties.
*
* @var array
*/
protected $placeProperties = [];
/**
* Array of Product Properties.
*
* @var array
*/
protected $productProperties = [];
/**
* Array of Image Properties.
*
* @var array
*/
protected $images = [];
/**
* Create a new OpenGraph instance.
*
* @param array $config config
*
* @return void
*/
public function __construct(array $config = [])
{
$this->config = $config;
}
/**
* {@inheritdoc}
*/
public function generate($minify = false)
{
$this->setupDefaults();
$output = $this->eachProperties($this->properties);
$props = [
'images' => ['image', true],
'articleProperties' => ['article', false],
'profileProperties' => ['profile', false],
'bookProperties' => ['book', false],
'musicSongProperties' => ['music', false],
'musicAlbumProperties' => ['music', false],
'musicPlaylistProperties' => ['music', false],
'musicRadioStationProperties' => ['music', false],
'videoMovieProperties' => ['video', false],
'videoEpisodeProperties' => ['video', false],
'videoTVShowProperties' => ['video', false],
'videoOtherProperties' => ['video', false],
'videoProperties' => ['video', true],
'audioProperties' => ['audio', true],
'placeProperties' => ['place', false],
'productProperties' => ['product', false],
];
foreach ($props as $prop => $options) {
$output .= $this->eachProperties(
$this->{$prop},
$options[0],
$options[1]
);
}
return ($minify) ? str_replace(PHP_EOL, '', $output) : $output;
}
/**
* Make list of open graph tags.
*
* @param array $properties array of properties
* @param null|string $prefix prefix of property
* @param bool $ogPrefix opengraph prefix
*
* @return string
*/
protected function eachProperties(
array $properties,
$prefix = null,
$ogPrefix = true
) {
$html = [];
foreach ($properties as $property => $value) {
// multiple properties
if (is_array($value)) {
if (is_string($property)){
$subListPrefix = $prefix.":".$property;
$subList = $this->eachProperties($value, $subListPrefix, false);
} else {
$subListPrefix = (is_string($property)) ? $property : $prefix;
$subList = $this->eachProperties($value, $subListPrefix);
}
$html[] = $subList;
} else {
if (is_string($prefix)) {
$key = (is_string($property)) ?
$prefix.':'.$property :
$prefix;
} else {
$key = $property;
}
// if empty jump to next
if (empty($value)) {
continue;
}
$html[] = $this->makeTag($key, $value, $ogPrefix);
}
}
return implode($html);
}
/**
* Make a og tag.
*
* @param string $key meta property key
* @param string $value meta property value
* @param bool $ogPrefix opengraph prefix
*
* @return string
*/
protected function makeTag($key = null, $value = null, $ogPrefix = false)
{
return sprintf(
'<meta property="%s%s" content="%s">%s',
$ogPrefix ? $this->og_prefix : '',
strip_tags($key),
$this->cleanTagValue($value),
PHP_EOL
);
}
/**
* Clean og tag value
*
* @param string $value meta property value
*
* @return string
*/
protected function cleanTagValue($value)
{
// Safety
$value = str_replace(['http-equiv=', 'url='], '', $value);
// Escape double quotes
$value = htmlspecialchars($value, ENT_QUOTES, null, false);
// Clean
$value = strip_tags($value);
return $value;
}
/**
* Add or update property.
*
* @return void
*/
protected function setupDefaults()
{
$defaults = (isset($this->config['defaults'])) ?
$this->config['defaults'] :
[];
foreach ($defaults as $key => $value) {
if ($key === 'images') {
if (empty($this->images)) {
$this->images = $value;
}
} elseif ($key === 'url' && empty($value)) {
if ($value === null) {
$this->addProperty('url', $this->url ?: app('url')->current());
} elseif ($this->url) {
$this->addProperty('url', $this->url);
}
} elseif (! empty($value) && ! array_key_exists($key, $this->properties)) {
$this->addProperty($key, $value);
}
}
}
/**
* {@inheritdoc}
*/
public function addProperty($key, $value)
{
$this->properties[$key] = $value;
return $this;
}
/**
* {@inheritdoc}
*/
public function setArticle($attributes = [])
{
$validkeys = [
'published_time',
'modified_time',
'expiration_time',
'author',
'section',
'tag',
];
$this->setProperties(
'article',
'articleProperties',
$attributes,
$validkeys
);
return $this;
}
/**
* {@inheritdoc}
*/
public function setProfile($attributes = [])
{
$validkeys = [
'first_name',
'last_name',
'username',
'gender',
];
$this->setProperties(
'profile',
'profileProperties',
$attributes,
$validkeys
);
return $this;
}
/**
* {@inheritdoc}
*/
public function setBook($attributes = [])
{
$validkeys = [
'author',
'isbn',
'release_date',
'tag',
];
$this->setProperties('book', 'bookProperties', $attributes, $validkeys);
return $this;
}
/**
* {@inheritdoc}
*/
public function setMusicSong($attributes = [])
{
$validkeys = [
'duration',
'album',
'album:disc',
'album:track',
'musician',
];
$this->setProperties(
'music.song',
'musicSongProperties',
$attributes,
$validkeys
);
return $this;
}
/**
* {@inheritdoc}
*/
public function setMusicAlbum($attributes = [])
{
$validkeys = [
'song',
'song:disc',
'song:track',
'musician',
'release_date',
];
$this->setProperties(
'music.album',
'musicAlbumProperties',
$attributes,
$validkeys
);
return $this;
}
/**
* {@inheritdoc}
*/
public function setMusicPlaylist($attributes = [])
{
$validkeys = [
'song',
'song:disc',
'song:track',
'creator',
];
$this->setProperties(
'music.playlist',
'musicPlaylistProperties',
$attributes,
$validkeys
);
return $this;
}
/**
* {@inheritdoc}
*/
public function setMusicRadioStation($attributes = [])
{
$validkeys = [
'creator',
];
$this->setProperties(
'music.radio_station',
'musicRadioStationProperties',
$attributes,
$validkeys
);
return $this;
}
/**
* {@inheritdoc}
*/
public function setVideoMovie($attributes = [])
{
$validkeys = [
'actor',
'actor:role',
'director',
'writer',
'duration',
'release_date',
'tag',
];
$this->setProperties(
'video.movie',
'videoMovieProperties',
$attributes,
$validkeys
);
return $this;
}
/**
* {@inheritdoc}
*/
public function setVideoEpisode($attributes = [])
{
$validkeys = [
'actor',
'actor:role',
'director',
'writer',
'duration',
'release_date',
'tag',
'series',
];
$this->setProperties(
'video.episode',
'videoEpisodeProperties',
$attributes,
$validkeys
);
return $this;
}
/**
* {@inheritdoc}
*/
public function setVideoOther($attributes = [])
{
$validkeys = [
'actor',
'actor:role',
'director',
'writer',
'duration',
'release_date',
'tag',
];
$this->setProperties(
'video.other',
'videoOtherProperties',
$attributes,
$validkeys
);
return $this;
}
/**
* {@inheritdoc}
*/
public function setVideoTVShow($attributes = [])
{
$validkeys = [
'actor',
'actor:role',
'director',
'writer',
'duration',
'release_date',
'tag',
];
$this->setProperties(
'video.tv_show',
'videoTVShowProperties',
$attributes,
$validkeys
);
return $this;
}
/**
* {@inheritdoc}
*/
public function addVideo($source = null, $attributes = [])
{
$validKeys = [
'url',
'secure_url',
'type',
'width',
'height',
];
$this->videoProperties[] = [
$source,
$this->cleanProperties($attributes, $validKeys),
];
return $this;
}
/**
* {@inheritdoc}
*/
public function addAudio($source = null, $attributes = [])
{
$validKeys = [
'url',
'secure_url',
'type',
];
$this->audioProperties[] = [
$source,
$this->cleanProperties($attributes, $validKeys),
];
return $this;
}
/**
* Set place properties.
*
* @param array $attributes opengraph place attributes
*
* @return OpenGraphContract
*/
public function setPlace($attributes = [])
{
$validkeys = [
'location:latitude',
'location:longitude',
];
$this->setProperties('place', 'placeProperties', $attributes, $validkeys);
return $this;
}
/**
* Set product properties.
* Reference: https://developers.facebook.com/docs/marketing-api/catalog/reference/#example-feeds
*
* @param array $attributes opengraph product attributes
*
* @return OpenGraphContract
*/
public function setProduct($attributes = [])
{
$validkeys = [
// Required
'brand',
'availability',
'condition',
// Conditionally required
'locale',
'plural_title',
// Conditionally required: https://developers.facebook.com/docs/payments/product/
'price:amount', // Required if Static Pricing & not Dynamic Pricing
'price:currency', // Required if Static Pricing & not Dynamic Pricing
// Optional
'catalog_id',
'item_group_id',
'category',
'gender',
'gtin',
'isbn',
'mfr_part_no',
'retailer_item_id',
'sale_price:amount',
'sale_price:currency',
'sale_price_dates:start',
'sale_price_dates:end',
// Optional - extra
'custom_label_0',
'custom_label_1',
'custom_label_2',
'custom_label_3',
'custom_label_4',
// Deprecated
'original_price:amount',
'original_price:currency',
'pretax_price:amount',
'pretax_price:currency',
'shipping_cost:amount',
'shipping_cost:currency',
'weight:value',
'weight:units',
'shipping_weight:value',
'shipping_weight:units',
];
$this->setProperties('product', 'productProperties', $attributes, $validkeys);
return $this;
}
/**
* Clean invalid properties.
*
* @param array $attributes attributes input
* @param string[] $validKeys keys that are allowed
*
* @return array
*/
protected function cleanProperties($attributes = [], $validKeys = [])
{
$array = [];
foreach ($attributes as $attribute => $value) {
if (in_array($attribute, $validKeys)) {
$array[$attribute] = $value;
}
}
return $array;
}
/**
* Set properties.
*
* @param string $type type of og:type
* @param string $key variable key
* @param array $attributes inputted opengraph attributes
* @param string[] $validKeys valid opengraph attributes
*
* @return void
*/
protected function setProperties(
$type = null,
$key = null,
$attributes = [],
$validKeys = []
) {
if (isset($this->properties['type']) && $this->properties['type'] == $type) {
foreach ($attributes as $attribute => $value) {
if (in_array($attribute, $validKeys)) {
$this->{$key}[$attribute] = $value;
}
}
}
}
/**
* {@inheritdoc}
*/
public function removeProperty($key)
{
Arr::forget($this->properties, $key);
return $this;
}
/**
* {@inheritdoc}
*/
public function addImage($source = null, $attributes = [])
{
$validKeys = [
'url',
'secure_url',
'type',
'width',
'height',
'alt',
];
if (is_array($source)) {
$this->images[] = $this->cleanProperties($source, $validKeys);
} else {
$this->images[] = [
$source,
$this->cleanProperties($attributes, $validKeys),
];
}
return $this;
}
/**
* {@inheritdoc}
*/
public function addImages(array $urls)
{
array_push($this->images, $urls);
return $this;
}
/**
* {@inheritdoc}
*/
public function setType($type = null)
{
return $this->addProperty('type', $type);
}
/**
* {@inheritdoc}
*/
public function setTitle($title = null)
{
return $this->addProperty('title', $title);
}
/**
* {@inheritdoc}
*/
public function setDescription($description = null)
{
return $this->addProperty('description', htmlspecialchars($description, ENT_QUOTES, 'UTF-8', false));
}
/**
* {@inheritdoc}
*/
public function setUrl($url)
{
$this->url = $url;
return $this;
}
/**
* {@inheritdoc}
*/
public function setSiteName($name)
{
return $this->addProperty('site_name', $name);
}
}