migrate therinaldos.com data
Build & Deploy to DigitalOcean Space / build (push) Failing after 2m38s

This commit is contained in:
2024-05-05 15:50:45 -04:00
commit ef1ff240d4
23182 changed files with 3801898 additions and 0 deletions
+20
View File
@@ -0,0 +1,20 @@
Copyright (c) 2014 Fusonic GmbH (http://www.fusonic.net)
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,35 @@
<?php
/*
* This file is part of Fusonic-linq.
*
* (c) Fusonic GmbH
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Fusonic\Linq;
use Fusonic\Linq\Linq;
/**
* Class GroupedLinq
* Represents a Linq object that groups together other elements with a group key().
* @package Fusonic\Linq
*/
class GroupedLinq extends Linq
{
private $groupKey;
public function __construct($groupKey, $dataSource)
{
parent::__construct($dataSource);
$this->groupKey = $groupKey;
}
public function key()
{
return $this->groupKey;
}
}
@@ -0,0 +1,67 @@
<?php
/*
* This file is part of Fusonic-linq.
* https://github.com/fusonic/fusonic-linq
*
* (c) Fusonic GmbH
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Fusonic\Linq\Helper;
use ArrayIterator;
use InvalidArgumentException;
use UnexpectedValueException;
class LinqHelper
{
const MODE_ASSERT = 'MODE_ASSERT';
const MODE_NULL = 'MODE_NULL';
const LINQ_ORDER_ASC = 'asc';
const LINQ_ORDER_DESC = 'desc';
const LINQ_ORDER_TYPE_NUMERIC = 1;
const LINQ_ORDER_TYPE_ALPHANUMERIC = 2;
const LINQ_ORDER_TYPE_DATETIME = 3;
public static function getBoolOrThrowException($returned)
{
if (!is_bool($returned)) {
throw new UnexpectedValueException("Return type of filter func must be boolean.");
}
return $returned;
}
public static function assertArgumentIsIterable($param, $argumentName)
{
if (!self::isIterable($param)) {
throw new InvalidArgumentException("Argument must be an array, or implement either the \IteratorAggregate or \Iterator interface. ArgumentName = " . $argumentName);
}
}
public static function getIteratorOrThrow($value)
{
if (is_array($value)) {
return new ArrayIterator($value);
}
else if($value instanceof \IteratorAggregate) {
return $value->getIterator();
}
else if($value instanceof \Iterator) {
return $value;
}
throw new \UnexpectedValueException("Value must be an array, or implement either the \IteratorAggregate or \Iterator interface");
}
public static function isIterable($param)
{
return is_array($param)
|| $param instanceof \IteratorAggregate
|| $param instanceof \Iterator;
}
}
@@ -0,0 +1,62 @@
<?php
/*
* This file is part of Fusonic-linq.
* https://github.com/fusonic/fusonic-linq
*
* (c) Fusonic GmbH
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Fusonic\Linq\Iterator;
use Iterator;
class DistinctIterator extends \IteratorIterator
{
private $iterator;
private $distinct;
public function __construct(Iterator $iterator)
{
$this->iterator = $iterator;
}
public function current()
{
return $this->distinct->current();
}
public function next()
{
$this->distinct->next();
}
public function key()
{
return $this->distinct->key();
}
public function valid()
{
return $this->distinct->valid();
}
public function rewind()
{
if ($this->distinct === null) {
$this->getDistincts();
}
$this->distinct->rewind();
}
private function getDistincts()
{
$data = iterator_to_array($this->iterator);
$distinct = array_unique($data);
$this->distinct = new \ArrayIterator($distinct);
}
}
@@ -0,0 +1,65 @@
<?php
/*
* This file is part of Fusonic-linq.
* https://github.com/fusonic/fusonic-linq
*
* (c) Fusonic GmbH
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Fusonic\Linq\Iterator;
use ArrayIterator;
use Iterator;
class ExceptIterator implements Iterator
{
private $first;
private $second;
private $result;
public function __construct(Iterator $first, Iterator $second)
{
$this->first = $first;
$this->second = $second;
}
public function current()
{
return $this->result->current();
}
public function next()
{
$this->result->next();
}
public function key()
{
return $this->result->key();
}
public function valid()
{
return $this->result->valid();
}
public function rewind()
{
if ($this->result === null) {
$this->getResult();
}
$this->result->rewind();
}
private function getResult()
{
$firstArray = iterator_to_array($this->first);
$secondArray = iterator_to_array($this->second);
$this->result = new ArrayIterator(array_diff($firstArray, $secondArray));
}
}
@@ -0,0 +1,74 @@
<?php
/*
* This file is part of Fusonic-linq.
* https://github.com/fusonic/fusonic-linq
*
* (c) Fusonic GmbH
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Fusonic\Linq\Iterator;
use Iterator;
use ArrayIterator;
use Fusonic\Linq\GroupedLinq;
class GroupIterator implements Iterator
{
private $iterator;
private $grouped;
private $keySelector;
public function __construct($iterator, $keySelector)
{
$this->iterator = $iterator;
$this->keySelector = $keySelector;
}
public function current()
{
$current = $this->grouped->current();
return new GroupedLinq($current['key'], new \ArrayIterator($current['values']));
}
public function next()
{
$this->grouped->next();
}
public function key()
{
return $this->grouped->key();
}
public function valid()
{
return $this->grouped->valid();
}
public function rewind()
{
if ($this->grouped === null) {
$this->doGroup();
}
$this->grouped->rewind();
}
private function doGroup()
{
$keySelector = $this->keySelector;
$this->grouped = new \ArrayIterator(array());
foreach ($this->iterator as $value) {
$key = $keySelector($value);
if (!isset($this->grouped[$key])) {
$this->grouped[$key] = array('key' => $key, 'values'=> array());
}
$this->grouped[$key]['values'][] = $value;
}
}
}
@@ -0,0 +1,65 @@
<?php
/*
* This file is part of Fusonic-linq.
* https://github.com/fusonic/fusonic-linq
*
* (c) Fusonic GmbH
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Fusonic\Linq\Iterator;
use ArrayIterator;
use Iterator;
class IntersectIterator implements Iterator
{
private $first;
private $second;
private $intersections;
public function __construct(Iterator $first, Iterator $second)
{
$this->first = $first;
$this->second = $second;
}
public function current()
{
return $this->intersections->current();
}
public function next()
{
$this->intersections->next();
}
public function key()
{
return $this->intersections->key();
}
public function valid()
{
return $this->intersections->valid();
}
public function rewind()
{
if ($this->intersections === null) {
$this->calcIntersections();
}
$this->intersections->rewind();
}
private function calcIntersections()
{
$firstArray = iterator_to_array($this->first);
$secondArray = iterator_to_array($this->second);
$this->intersections = new ArrayIterator(array_intersect($firstArray, $secondArray));
}
}
@@ -0,0 +1,94 @@
<?php
/*
* This file is part of Fusonic-linq.
* https://github.com/fusonic/fusonic-linq
*
* (c) Burgy Benjamin <benjamin.burgy@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Fusonic\Linq\Iterator;
use FilterIterator;
use Fusonic\Linq\Helper;
use Iterator;
/**
* Iterator for filtering the Linq query with a specified <b>type</b>.
* @package Fusonic\Linq\Iterator
*/
final class OfTypeIterator
extends
\FilterIterator
{
/**
* @var callable $acceptCallback
*/
private $acceptCallback;
/**
* Initializes an instance of <b>OfTypeIterator</b>.
*
* @param Iterator $iterator
* @param string $type
*/
public function __construct(Iterator $iterator, $type)
{
parent::__construct($iterator);
switch (strtolower($type))
{
case 'int':
case 'integer':
$this->acceptCallback = function ($current)
{
return is_int($current);
};
break;
case 'float':
case 'double':
$this->acceptCallback = function ($current)
{
return is_float($current);
};
break;
case 'string':
$this->acceptCallback = function ($current)
{
return is_string($current);
};
break;
case 'bool':
case 'boolean':
$this->acceptCallback = function ($current)
{
return is_bool($current);
};
break;
default:
$this->acceptCallback = function ($current) use ($type)
{
return $current instanceof $type;
};
}
}
/**
* (PHP 5 &gt;= 5.1.0)<br/>
* Check whether the current element of the iterator is acceptable
* @link http://php.net/manual/en/filteriterator.accept.php
* @return bool true if the current element is acceptable, otherwise false.
*/
public function accept()
{
/** @var mixed $current */
$current = $this->current();
/** @var callable $func */
$func = $this->acceptCallback;
return $func($current);
}
}
@@ -0,0 +1,113 @@
<?php
/*
* This file is part of Fusonic-linq.
* https://github.com/fusonic/fusonic-linq
*
* (c) Fusonic GmbH
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Fusonic\Linq\Iterator;
use Iterator;
use ArrayIterator;
use Fusonic\Linq;
use Fusonic\Linq\Helper;
class OrderIterator implements Iterator
{
private $iterator;
private $direction;
private $orderedIterator;
private $orderKeyFunc;
public function __construct(Iterator $items, $orderKeyFunc, $direction)
{
$this->iterator = $items;
$this->direction = $direction;
$this->orderKeyFunc = $orderKeyFunc;
}
public function current()
{
return $this->orderedIterator->current();
}
public function next()
{
$this->orderedIterator->next();
}
public function key()
{
return $this->orderedIterator->key();
}
public function valid()
{
return $this->orderedIterator->valid();
}
public function rewind()
{
if ($this->orderedIterator == null) {
$this->orderItems();
}
$this->orderedIterator->rewind();
}
public function orderItems()
{
$orderKeyFunc = $this->orderKeyFunc;
$direction = $this->direction;
$itemIterator = $this->iterator;
$itemIterator->rewind();
if (!$itemIterator->valid()) {
$this->orderedIterator = new ArrayIterator();
return;
}
$firstOrderKey = $orderKeyFunc($itemIterator->current());
$sortType = Helper\LinqHelper::LINQ_ORDER_TYPE_NUMERIC;
if ($firstOrderKey instanceof \DateTime) {
$sortType = Helper\LinqHelper::LINQ_ORDER_TYPE_DATETIME;
} elseif (!is_numeric($firstOrderKey)) {
$sortType = Helper\LinqHelper::LINQ_ORDER_TYPE_ALPHANUMERIC;
}
$keyMap = array();
$valueMap = array();
foreach ($itemIterator as $value) {
$orderKey = $orderKeyFunc != null ? $orderKeyFunc($value) : $value;
if ($sortType == Helper\LinqHelper::LINQ_ORDER_TYPE_DATETIME) {
$orderKey = $orderKey->getTimeStamp();
}
$keyMap[] = $orderKey;
$valueMap[] = $value;
}
if ($sortType == Helper\LinqHelper::LINQ_ORDER_TYPE_DATETIME) {
$sortType = Helper\LinqHelper::LINQ_ORDER_TYPE_NUMERIC;
}
if ($direction == Helper\LinqHelper::LINQ_ORDER_ASC) {
asort($keyMap, $sortType == Helper\LinqHelper::LINQ_ORDER_TYPE_NUMERIC ? SORT_NUMERIC : SORT_LOCALE_STRING);
} else {
arsort($keyMap, $sortType == Helper\LinqHelper::LINQ_ORDER_TYPE_NUMERIC ? SORT_NUMERIC : SORT_LOCALE_STRING);
}
$sorted = new ArrayIterator(array());
foreach ($keyMap as $key => $value) {
$sorted[] = $valueMap[$key];
}
$this->orderedIterator = $sorted;
}
}
@@ -0,0 +1,37 @@
<?php
/*
* This file is part of Fusonic-linq.
* https://github.com/fusonic/fusonic-linq
*
* (c) Fusonic GmbH
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Fusonic\Linq\Iterator;
use InvalidArgumentException;
use Iterator;
class SelectIterator extends \IteratorIterator
{
private $selector;
public function __construct(Iterator $iterator, $selector)
{
parent::__construct($iterator);
if ($selector === null) {
throw new InvalidArgumentException("Selector must not be null.");
}
$this->selector = $selector;
}
public function current()
{
$selector = $this->selector;
return $selector(parent::current());
}
}
@@ -0,0 +1,84 @@
<?php
/*
* This file is part of Fusonic-linq.
* https://github.com/fusonic/fusonic-linq
*
* (c) Fusonic GmbH
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Fusonic\Linq\Iterator;
use Iterator;
use Fusonic\Linq\Helper;
class SelectManyIterator implements Iterator
{
private $iterator;
private $currentIterator;
private $key = 0;
public function __construct(Iterator $iterator)
{
$this->iterator = $iterator;
}
public function current()
{
if ($this->currentIterator != null) {
return $this->currentIterator->current();
}
return null;
}
public function next()
{
if ($this->currentIterator != null) {
$this->currentIterator->next();
if (!$this->currentIterator->valid()) {
$this->iterator->next();
if ($this->iterator->valid()) {
$this->currentIterator = Helper\LinqHelper::getIteratorOrThrow($this->iterator->current());
if ($this->currentIterator != null) {
$this->currentIterator->rewind();
$this->key++;
}
}
} else {
$this->key++;
}
}
}
public function key()
{
return $this->key;
}
public function valid()
{
$current = $this->currentIterator;
return $current != null && $current->valid();
}
public function rewind()
{
$this->iterator->rewind();
if ($this->iterator->valid()) {
$current = $this->iterator->current();
$this->currentIterator = Helper\LinqHelper::getIteratorOrThrow($current);
if ($this->currentIterator != null) {
$this->currentIterator->rewind();
}
} else {
$this->currentIterator = null;
}
$this->key = 0;
}
}
@@ -0,0 +1,34 @@
<?php
/*
* This file is part of Fusonic-linq.
* https://github.com/fusonic/fusonic-linq
*
* (c) Fusonic GmbH
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Fusonic\Linq\Iterator;
use Iterator;
use Fusonic\Linq\Helper;
class WhereIterator extends \FilterIterator
{
private $func;
public function __construct(Iterator $iterator, $func)
{
parent::__construct($iterator);
$this->func = $func;
}
public function accept()
{
$func = $this->func;
$current = $this->current();
return Helper\LinqHelper::getBoolOrThrowException($func($current));
}
}
@@ -0,0 +1,732 @@
<?php
/*
* This file is part of Fusonic-linq.
*
* (c) Fusonic GmbH
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Fusonic\Linq;
use Countable;
use Fusonic\Linq\Iterator\ExceptIterator;
use Fusonic\Linq\Iterator\DistinctIterator;
use Fusonic\Linq\Iterator\GroupIterator;
use Fusonic\Linq\Iterator\IntersectIterator;
use Fusonic\Linq\Iterator\OfTypeIterator;
use Fusonic\Linq\Iterator\OrderIterator;
use Fusonic\Linq\Iterator\SelectIterator;
use Fusonic\Linq\Iterator\SelectManyIterator;
use Fusonic\Linq\Iterator\WhereIterator;
use Fusonic\Linq\Helper\LinqHelper;
use IteratorAggregate;
use Traversable;
use UnexpectedValueException;
use InvalidArgumentException;
use OutOfRangeException;
/**
* Linq is a simple, powerful and consistent library for querying, projecting and aggregating data in php.
*
* @author David Roth <david.roth@fusonic.net>.
*/
class Linq implements IteratorAggregate, Countable
{
private $iterator;
/**
* Creates a new Linq object using the provided dataSource.
*
* @param array|\Iterator|IteratorAggregate $dataSource A Traversable sequence as data source.
*/
public function __construct($dataSource)
{
LinqHelper::assertArgumentIsIterable($dataSource, "dataSource");
$dataSource = LinqHelper::getIteratorOrThrow($dataSource);
$this->iterator = $dataSource;
}
/**
* Creates a new Linq object using the provided dataDataSource.
* This is the recommended way for getting a new Linq instance.
*
* @param array|\Iterator|IteratorAggregate $dataSource A Traversable sequence as data source.
* @return Linq
*/
public static function from($dataSource)
{
return new Linq($dataSource);
}
/**
* Generates a sequence of integral numbers within a specified range.
*
* @param $start The value of the first integer in the sequence.
* @param $count The number of sequential integers to generate.
* @return Linq An sequence that contains a range of sequential int numbers.
* @throws \OutOfRangeException
*/
public static function range($start, $count)
{
if ($count < 0) {
throw new OutOfRangeException('$count must be not be negative.');
}
return new Linq(range($start, $start + $count - 1));
}
/**
* Filters the Linq object according to func return result.
*
* @param callback $func A func that returns boolean
* @return Linq Filtered results according to $func
*/
public function where($func)
{
return new Linq(new WhereIterator($this->iterator, $func));
}
/**
* Filters the Linq object according to type.
*
* @param string $type
*
* @return Linq Filtered results according to $func
*/
public function ofType($type)
{
return new Linq(new OfTypeIterator($this->iterator, $type));
}
/**
* Bypasses a specified number of elements and then returns the remaining elements.
*
* @param int $count The number of elements to skip before returning the remaining elements.
* @return Linq A Linq object that contains the elements that occur after the specified index.
*/
public function skip($count)
{
// If its an array iterator we must check the arrays bounds are greater than the skip count.
// This is because the LimitIterator will use the seek() method which will throw an exception if $count > array.bounds.
$innerIterator = $this->iterator;
if ($innerIterator instanceof \ArrayIterator) {
if ($count >= $innerIterator->count()) {
return new Linq(array());
}
}
return new Linq(new \LimitIterator($innerIterator, $count, -1));
}
/**
* Returns a specified number of contiguous elements from the start of a sequence
*
* @param int $count The number of elements to return.
* @return Linq A Linq object that contains the specified number of elements from the start.
*/
public function take($count)
{
if ($count == 0) {
return new Linq(array());
}
return new Linq(new \LimitIterator($this->iterator, 0, $count));
}
/**
* Applies an accumulator function over a sequence.
* The aggregate method makes it simple to perform a calculation over a sequence of values.
* This method works by calling $func one time for each element.
* The first element of source is used as the initial aggregate value if $seed parameter is not specified.
* If $seed is specified, this value will be used as the first value.
*
* @param callback $func An accumulator function to be invoked on each element.
* @param mixed $seed
* @throws \RuntimeException if the input sequence contains no elements.
* @return mixed Returns the final result of $func.
*/
public function aggregate($func, $seed = null)
{
$result = null;
$first = true;
if ($seed !== null) {
$result = $seed;
$first = false;
}
foreach ($this->iterator as $current) {
if (!$first) {
$result = $func($result, $current);
} else {
$result = $current;
$first = false;
}
}
if ($first) {
throw new \RuntimeException("The input sequence contains no elements.");
}
return $result;
}
/**
* Splits the sequence in chunks according to $chunksize.
*
* @param $chunksize Specifies how many elements are grouped together per chunk.
* @throws \InvalidArgumentException
* @return Linq
*/
public function chunk($chunksize)
{
if ($chunksize < 1) {
throw new \InvalidArgumentException("chunksize", $chunksize);
}
$i = -1;
return $this->select(
function ($x) use (&$i) {
$i++;
return array("index" => $i, "value" => $x);
}
)
->groupBy(
function ($pair) use ($chunksize) {
return $pair["index"] / $chunksize;
}
)
->select(
function (GroupedLinq $group) {
return $group->select(
function ($v) {
return $v["value"];
}
);
}
);
}
/**
* Determines whether all elements satisfy a condition.
*
* @param callback $func A function to test each element for a condition.
* @return bool True if every element passes the test in the specified func, or if the sequence is empty; otherwise, false.
*/
public function all($func)
{
foreach ($this->iterator as $current) {
$match = LinqHelper::getBoolOrThrowException($func($current));
if (!$match) {
return false;
}
}
return true;
}
/**
* Determines whether any element exists or satisfies a condition by invoking $func.
*
* @param callback $func A function to test each element for a condition or NULL to determine if any element exists.
* @return bool True if no $func given and the source sequence contains any elements or True if any elements passed the test in the specified func; otherwise, false.
*/
public function any($func = null)
{
foreach ($this->iterator as $current) {
if ($func === null) {
return true;
}
$match = LinqHelper::getBoolOrThrowException($func($current));
if ($match) {
return true;
}
}
return false;
}
/**
* Counts the elements of this Linq sequence.
* @return int
*/
public function count()
{
if ($this->iterator instanceof Countable) {
return $this->iterator->count();
}
return iterator_count($this->iterator);
}
/**
* Computes the average of all numeric values. Uses $func to obtain the value on each element.
*
* @param callback $func A func that returns any numeric type (int, float etc.)
* @throws \UnexpectedValueException if an item of the sequence is not a numeric value.
* @return double Average of items
*/
public function average($func = null)
{
$resultTotal = 0;
$itemCount = 0;
$source = $this->getSelectIteratorOrInnerIterator($func);
foreach ($source as $item) {
if (!is_numeric($item)) {
throw new UnexpectedValueException("Cannot calculate an average on a none numeric value");
}
$resultTotal += $item;
$itemCount++;
}
return $itemCount == 0 ? 0 : ($resultTotal / $itemCount);
}
/**
* Sorts the elements in ascending order according to a key provided by $func.
*
* @param callback $func A function to extract a key from an element.
* @return Linq A new Linq instance whose elements are sorted ascending according to a key.
*/
public function orderBy($func)
{
return $this->order($func, LinqHelper::LINQ_ORDER_ASC);
}
/**
* Sorts the elements in descending order according to a key provided by $func.
*
* @param callback $func A function to extract a key from an element.
* @return Linq A new Linq instance whose elements are sorted descending according to a key.
*/
public function orderByDescending($func)
{
return $this->order($func, LinqHelper::LINQ_ORDER_DESC);
}
private function order($func, $direction = LinqHelper::LINQ_ORDER_ASC)
{
return new Linq(new OrderIterator($this->iterator, $func, $direction));
}
/**
* Gets the sum of all items or by invoking a transform function on each item to get a numeric value.
*
* @param callback $func A func that returns any numeric type (int, float etc.) from the given element, or NULL to use the element itself.
* @throws \UnexpectedValueException if any element is not a numeric value.
* @return double The sum of all items.
*/
public function sum($func = null)
{
$sum = 0;
$iterator = $this->getSelectIteratorOrInnerIterator($func);
foreach ($iterator as $value) {
if (!is_numeric($value)) {
throw new UnexpectedValueException("sum() only works on numeric values.");
}
$sum += $value;
}
return $sum;
}
/**
* Gets the minimum item value of all items or by invoking a transform function on each item to get a numeric value.
*
* @param callback $func A func that returns any numeric type (int, float etc.) from the given element, or NULL to use the element itself.
* @throws \RuntimeException if the sequence contains no elements
* @throws \UnexpectedValueException
* @return double Minimum item value
*/
public function min($func = null)
{
$min = null;
$iterator = $this->getSelectIteratorOrInnerIterator($func);
foreach ($iterator as $value) {
if (!is_numeric($value) && !is_string($value) && !($value instanceof \DateTime)) {
throw new UnexpectedValueException("min() only works on numeric values, strings and DateTime objects.");
}
if (is_null($min)) {
$min = $value;
} elseif ($min > $value) {
$min = $value;
}
}
if ($min === null) {
throw new \RuntimeException("Cannot calculate min() as the Linq sequence contains no elements.");
}
return $min;
}
/**
* Returns the maximum item value according to $func
*
* @param callback $func A func that returns any numeric type (int, float etc.)
* @throws \RuntimeException if the sequence contains no elements
* @throws \UnexpectedValueException if any element is not a numeric value or a string.
* @return double Maximum item value
*/
public function max($func = null)
{
$max = null;
$iterator = $this->getSelectIteratorOrInnerIterator($func);
foreach ($iterator as $value) {
if (!is_numeric($value) && !is_string($value) && !($value instanceof \DateTime)) {
throw new UnexpectedValueException("max() only works on numeric values, strings and DateTime objects.");
}
if (is_null($max)) {
$max = $value;
} elseif ($max < $value) {
$max = $value;
}
}
if ($max === null) {
throw new \RuntimeException("Cannot calculate max() as the Linq sequence contains no elements.");
}
return $max;
}
/**
* Projects each element into a new form by invoking the selector function.
*
* @param callback $func A transform function to apply to each element.
* @return Linq A new Linq object whose elements are the result of invoking the transform function on each element of the original Linq object.
*/
public function select($func)
{
return new Linq(new SelectIterator($this->iterator, $func));
}
/**
* Projects each element of a sequence to a new Linq and flattens the resulting sequences into one sequence.
*
* @param callback $func A func that returns a sequence (array, Linq, Iterator).
* @throws \UnexpectedValueException if an element is not a traversable sequence.
* @return Linq A new Linq object whose elements are the result of invoking the one-to-many transform function on each element of the input sequence.
*/
public function selectMany($func)
{
return new Linq(new SelectManyIterator(new SelectIterator($this->iterator, $func)));
}
/**
* Performs the specified action on each element of the Linq sequence and returns the Linq sequence.
* @param callback $func A func that will be evaluated for each item in the linq sequence.
* @return Linq The original Linq sequence that was used to perform the foreach.
*/
public function each($func)
{
foreach ($this->iterator as $item) {
$func($item);
}
return $this;
}
/**
* Determines whether a sequence contains a specified element.
* This function will use php strict comparison (===). If you need custom comparison use the Linq::any($func) method.
*
* @param mixed $value The value to locate in the sequence.
* @return bool True if $value is found within the sequence; otherwise false.
*/
public function contains($value)
{
return $this->any(
function ($x) use ($value) {
return $x === $value;
}
);
}
/**
* Concatenates this Linq object with the given sequence.
*
* @param array|\Iterator $second A sequence which will be concatenated with this Linq object.
* @throws InvalidArgumentException if the given sequence is not traversable.
* @return Linq A new Linq object that contains the concatenated elements of the input sequence and the original Linq sequence.
*/
public function concat($second)
{
LinqHelper::assertArgumentIsIterable($second, "second");
$allItems = new \ArrayIterator(array($this->iterator, $second));
return new Linq(new SelectManyIterator($allItems));
}
/**
* Returns distinct item values of this
*
* @param callback $func
* @return Linq Distinct item values of this
*/
public function distinct($func = null)
{
return new Linq(new DistinctIterator($this->getSelectIteratorOrInnerIterator($func)));
}
/**
* Intersects the Linq sequence with second Iterable sequence.
*
* @param \Iterator|array An iterator to intersect with:
* @return Linq intersected items
*/
public function intersect($second)
{
LinqHelper::assertArgumentIsIterable($second, "second");
return new Linq(new IntersectIterator($this->iterator, LinqHelper::getIteratorOrThrow($second)));
}
/**
* Returns all elements except the ones of the given sequence.
*
* @param array|\Iterator $second
* @return Linq Returns all items of this not occuring in $second
*/
public function except($second)
{
LinqHelper::assertArgumentIsIterable($second, "second");
return new Linq(new ExceptIterator($this->iterator, LinqHelper::getIteratorOrThrow($second)));
}
/**
* Returns the element at a specified index.
* This method throws an exception if index is out of range.
* To instead return NULL when the specified index is out of range, use the elementAtOrNull method.
*
* @throws \OutOfRangeException if index is less than 0 or greater than or equal to the number of elements in the sequence.
* @param int $index
* @return mixed Item at $index
*/
public function elementAt($index)
{
return $this->getValueAt($index, true);
}
/**
* Returns the element at a specified index or NULL if the index is out of range.
*
* @param $index
* @return mixed Item at $index
*/
public function elementAtOrNull($index)
{
return $this->getValueAt($index, false);
}
private function getValueAt($index, $throwEx)
{
$i = 0;
foreach ($this->iterator as $value) {
if ($i == $index) {
return $value;
}
$i++;
}
if ($throwEx) {
throw new OutOfRangeException("Index is less than 0 or greater than or equal to the number of elements in the sequence.");
}
return null;
}
/**
* Groups the object according to the $func generated key
*
* @param callback $keySelector a func that returns an item as key, item can be any type.
* @return GroupedLinq
*/
public function groupBy($keySelector)
{
return new Linq(new GroupIterator($this->iterator, $keySelector));
}
/**
* Returns the last element that satisfies a specified condition.
* @throws \RuntimeException if no element satisfies the condition in predicate or the source sequence is empty.
*
* @param callback $func a func that returns boolean.
* @return Object Last item in this
*/
public function last($func = null)
{
return $this->getLast($func, true);
}
/**
* Returns the last element that satisfies a condition or NULL if no such element is found.
*
* @param callback $func a func that returns boolean.
* @return mixed
*/
public function lastOrNull($func = null)
{
return $this->getLast($func, false);
}
/**
* Returns the first element that satisfies a specified condition
* @throws \RuntimeException if no element satisfies the condition in predicate -or- the source sequence is empty / does not match any elements.
*
* @param callback $func a func that returns boolean.
* @return mixed
*/
public function first($func = null)
{
return $this->getFirst($func, true);
}
/**
* Returns the first element, or NULL if the sequence contains no elements.
*
* @param callback $func a func that returns boolean.
* @return mixed
*/
public function firstOrNull($func = null)
{
return $this->getFirst($func, false);
}
/**
* Returns the only element that satisfies a specified condition.
*
* @throws \RuntimeException if no element exists or if more than one element exists.
* @param callback $func a func that returns boolean.
* @return mixed
*/
public function single($func = null)
{
return $this->getSingle($func, true);
}
/**
* Returns the only element that satisfies a specified condition or NULL if no such element exists.
*
* @throws \RuntimeException if more than one element satisfies the condition.
* @param callback $func a func that returns boolean.
* @return mixed
*/
public function singleOrNull($func = null)
{
return $this->getSingle($func, false);
}
private function getWhereIteratorOrInnerIterator($func)
{
return $func === null ? $this->iterator : new WhereIterator($this->iterator, $func);
}
private function getSelectIteratorOrInnerIterator($func)
{
return $func === null ? $this->iterator : new SelectIterator($this->iterator, $func);
}
private function getSingle($func, $throw)
{
$source = $this->getWhereIteratorOrInnerIterator($func);
$count = 0;
$single = null;
foreach ($source as $stored) {
$count++;
if ($count > 1) {
throw new \RuntimeException("The input sequence contains more than 1 elements.");
}
$single = $stored;
}
if ($count == 0 && $throw) {
throw new \RuntimeException("The input sequence contains no matching element.");
}
return $single;
}
private function getFirst($func, $throw)
{
$source = $this->getWhereIteratorOrInnerIterator($func);
$count = 0;
$first = null;
foreach ($source as $stored) {
$count++;
$first = $stored;
break;
}
if ($count == 0 && $throw) {
throw new \RuntimeException("The input sequence contains no matching element.");
}
return $first;
}
private function getLast($func, $throw)
{
$source = $this->getWhereIteratorOrInnerIterator($func);
$count = 0;
$last = null;
foreach ($source as $stored) {
$count++;
$last = $stored;
}
if ($count == 0 && $throw) {
throw new \RuntimeException("The input sequence contains no matching element.");
}
return $last;
}
/**
* Creates an Array from this Linq object with key/value selector(s).
*
* @param callback $keySelector a func that returns the array-key for each element.
* @param callback $valueSelector a func that returns the array-value for each element.
*
* @return Array An array with all values.
*/
public function toArray($keySelector = null, $valueSelector = null)
{
if ($keySelector === null && $valueSelector === null) {
return iterator_to_array($this, false);
} elseif ($keySelector == null) {
return iterator_to_array(new SelectIterator($this->getIterator(), $valueSelector), false);
} else {
$array = array();
foreach ($this as $value) {
$key = $keySelector($value);
$array[$key] = $valueSelector == null ? $value : $valueSelector($value);
}
return $array;
}
}
/**
* Retrieves the iterator of this Linq class.
* @link http://php.net/manual/en/iteratoraggregate.getiterator.php
* @return Traversable An instance of an object implementing <b>Iterator</b> or
* <b>Traversable</b>
*/
public function getIterator()
{
return $this->iterator;
}
}
+20
View File
@@ -0,0 +1,20 @@
Copyright (c) 2014 Fusonic GmbH (http://www.fusonic.net)
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+156
View File
@@ -0,0 +1,156 @@
<?php
namespace Fusonic\OpenGraph;
use DOMElement;
use Fusonic\Linq\Linq;
use Fusonic\OpenGraph\Objects\ObjectBase;
use Fusonic\OpenGraph\Objects\Website;
use LogicException;
use Psr\Http\Client\ClientExceptionInterface;
use Psr\Http\Client\ClientInterface;
use Psr\Http\Message\RequestFactoryInterface;
use Symfony\Component\DomCrawler\Crawler;
/**
* Consumer that extracts Open Graph data from either a URL or a HTML string.
*/
class Consumer
{
private ?ClientInterface $client;
private ?RequestFactoryInterface $requestFactory;
/**
* When enabled, crawler will read content of title and meta description if no
* Open Graph data is provided by target page.
*/
public bool $useFallbackMode = false;
/**
* When enabled, crawler will throw exceptions for some crawling errors like unexpected
* Open Graph elements.
*/
public bool $debug = false;
/**
* @param ClientInterface|null $client A PSR-18 ClientInterface implementation.
* @param RequestFactoryInterface|null $requestFactory A PSR-17 RequestFactoryInterface implementation.
*/
public function __construct(?ClientInterface $client = null, ?RequestFactoryInterface $requestFactory = null)
{
$this->client = $client;
$this->requestFactory = $requestFactory;
}
/**
* Fetches HTML content from the given URL and then crawls it for Open Graph data.
*
* @param string $url URL to be crawled.
*
* @return ObjectBase
*
* @throws ClientExceptionInterface
*/
public function loadUrl(string $url): ObjectBase
{
if ($this->client === null) {
throw new LogicException(
"To use loadUrl() you must provide \$client and \$requestFactory when instantiating the consumer."
);
}
$request = $this->requestFactory->createRequest("GET", $url);
$response = $this->client->sendRequest($request);
return $this->loadHtml($response->getBody()->getContents(), $url);
}
/**
* Crawls the given HTML string for OpenGraph data.
*
* @param string $html HTML string, usually whole content of crawled web resource.
* @param string $fallbackUrl URL to use when fallback mode is enabled.
*
* @return ObjectBase
*/
public function loadHtml(string $html, string $fallbackUrl = null): ObjectBase
{
// Extract all data that can be found
$page = $this->extractOpenGraphData($html);
// Use the user's URL as fallback
if ($this->useFallbackMode && $page->url === null) {
$page->url = $fallbackUrl;
}
// Return result
return $page;
}
private function extractOpenGraphData(string $content): ObjectBase
{
$crawler = new Crawler;
$crawler->addHTMLContent($content, 'UTF-8');
$properties = [];
foreach(['name', 'property'] as $t)
{
// Get all meta-tags starting with "og:"
$ogMetaTags = $crawler->filter("meta[{$t}^='og:']");
// Create clean property array
$props = Linq::from($ogMetaTags)
->select(
function (DOMElement $tag) use ($t) {
$name = strtolower(trim($tag->getAttribute($t)));
$value = trim($tag->getAttribute("content"));
return new Property($name, $value);
}
)
->toArray();
$properties = array_merge($properties, $props);
}
// Create new object of the correct type
$typeProperty = Linq::from($properties)
->firstOrNull(
function (Property $property) {
return $property->key === Property::TYPE;
}
);
switch ($typeProperty !== null ? $typeProperty->value : null) {
default:
$object = new Website();
break;
}
// Assign all properties to the object
$object->assignProperties($properties, $this->debug);
// Fallback for url
if ($this->useFallbackMode && !$object->url) {
$urlElement = $crawler->filter("link[rel='canonical']")->first();
if ($urlElement->count() > 0) {
$object->url = trim($urlElement->attr("href"));
}
}
// Fallback for title
if ($this->useFallbackMode && !$object->title) {
$titleElement = $crawler->filter("title")->first();
if ($titleElement->count() > 0) {
$object->title = trim($titleElement->text());
}
}
// Fallback for description
if ($this->useFallbackMode && !$object->description) {
$descriptionElement = $crawler->filter("meta[property='description']")->first();
if ($descriptionElement->count() > 0) {
$object->description = trim($descriptionElement->attr("content"));
}
}
return $object;
}
}
@@ -0,0 +1,67 @@
<?php
namespace Fusonic\OpenGraph\Elements;
use Fusonic\OpenGraph\Property;
/**
* An Open Graph audio element.
*/
class Audio extends ElementBase
{
/**
* The URL of an audio resource associated with the object.
*
* @var string
*/
public $url;
/**
* An alternate URL to use if an audio resource requires HTTPS.
*
* @var string
*/
public $secureUrl;
/**
* The MIME type of an audio resource associated with the object.
*
* @var type
*/
public $type;
/**
* @param string $url URL to the audio file.
*/
public function __construct($url)
{
parent::__construct();
$this->url = $url;
}
/**
* Gets all properties set on this element.
*
* @return array|Property[]
*/
public function getProperties()
{
$properties = [];
// URL must precede all other properties
if ($this->url !== null) {
$properties[] = new Property(Property::AUDIO_URL, $this->url);
}
if ($this->secureUrl !== null) {
$properties[] = new Property(Property::AUDIO_SECURE_URL, $this->secureUrl);
}
if ($this->type !== null) {
$properties[] = new Property(Property::AUDIO_TYPE, $this->type);
}
return $properties;
}
}
@@ -0,0 +1,20 @@
<?php
namespace Fusonic\OpenGraph\Elements;
/**
* Abstract base class for all OpenGraph elements (e.g. images, videos etc.)
*/
abstract class ElementBase
{
protected function __construct()
{
}
/**
* Gets all properties set on this element.
*
* @return array|Property[]
*/
abstract public function getProperties();
}
@@ -0,0 +1,100 @@
<?php
namespace Fusonic\OpenGraph\Elements;
use Fusonic\OpenGraph\Property;
/**
* An Open Graph image element.
*/
class Image extends ElementBase
{
/**
* The URL of an image resource associated with the object.
*
* @var string
*/
public $url;
/**
* An alternate URL to use if an image resource requires HTTPS.
*
* @var string
*/
public $secureUrl;
/**
* The MIME type of an image resource.
*
* @var type
*/
public $type;
/**
* The width of an image resource in pixels.
*
* @var int
*/
public $width;
/**
* The height of an image resource in pixels.
*
* @var int
*/
public $height;
/**
* Whether the image is user-generated or not.
*
* @var bool
*/
public $userGenerated;
/**
* @param string $url URL to the image file.
*/
public function __construct($url)
{
parent::__construct();
$this->url = $url;
}
/**
* Gets all properties set on this element.
*
* @return array|Property[]
*/
public function getProperties()
{
$properties = [];
// URL must precede all other properties
if ($this->url !== null) {
$properties[] = new Property(Property::IMAGE_URL, $this->url);
}
if ($this->height !== null) {
$properties[] = new Property(Property::IMAGE_HEIGHT, $this->height);
}
if ($this->secureUrl !== null) {
$properties[] = new Property(Property::IMAGE_SECURE_URL, $this->secureUrl);
}
if ($this->type !== null) {
$properties[] = new Property(Property::IMAGE_TYPE, $this->type);
}
if ($this->width !== null) {
$properties[] = new Property(Property::IMAGE_WIDTH, $this->width);
}
if ($this->userGenerated !== null) {
$properties[] = new Property(Property::IMAGE_USER_GENERATED, $this->userGenerated);
}
return $properties;
}
}
@@ -0,0 +1,89 @@
<?php
namespace Fusonic\OpenGraph\Elements;
use Fusonic\OpenGraph\Property;
/**
* An OpenGraph video element.
*/
class Video extends ElementBase
{
/**
* The URL of a video resource associated with the object.
*
* @var string
*/
public $url;
/**
* An alternate URL to use if a video resource requires HTTPS.
*
* @var string
*/
public $secureUrl;
/**
* The MIME type of a video resource associated with the object.
*
* @var type
*/
public $type;
/**
* The width of a video resource associated with the object in pixels.
*
* @var int
*/
public $width;
/**
* The height of a video resource associated with the object in pixels.
*
* @var int
*/
public $height;
/**
* @param string $url URL to the video.
*/
public function __construct($url)
{
parent::__construct();
$this->url = $url;
}
/**
* Gets all properties set on this element.
*
* @return array|Property[]
*/
public function getProperties()
{
$properties = [];
// URL must precede all other properties
if ($this->url !== null) {
$properties[] = new Property(Property::VIDEO_URL, $this->url);
}
if ($this->height !== null) {
$properties[] = new Property(Property::VIDEO_HEIGHT, $this->height);
}
if ($this->secureUrl !== null) {
$properties[] = new Property(Property::VIDEO_SECURE_URL, $this->secureUrl);
}
if ($this->type !== null) {
$properties[] = new Property(Property::VIDEO_TYPE, $this->type);
}
if ($this->width !== null) {
$properties[] = new Property(Property::VIDEO_WIDTH, $this->width);
}
return $properties;
}
}
@@ -0,0 +1,358 @@
<?php
namespace Fusonic\OpenGraph\Objects;
use DateTimeImmutable;
use Fusonic\OpenGraph\Elements\Audio;
use Fusonic\OpenGraph\Elements\Image;
use Fusonic\OpenGraph\Elements\Video;
use Fusonic\OpenGraph\Property;
use UnexpectedValueException;
/**
* Abstract base class for all Open Graph objects (website, video, ...)
*/
abstract class ObjectBase
{
/**
* An array of audio resources attached to the object.
*
* @var Audio[]
*/
public array $audios = [];
/**
* A short description of the object.
*/
public ?string $description = null;
/**
* The word that appears before the object's title in a sentence. This is an list of words from 'a', 'an', 'the',
* ' "" ', or 'auto'. If 'auto' is chosen, the consumer of the object will chose between 'a' or 'an'. The default is
* the blank, "".
*/
public ?string $determiner = null;
/**
* An array of images attached to the object.
*
* @var Image[]
*/
public array $images = [];
/**
* The locale that the object's tags are marked up in, in the format language_TERRITORY.
*/
public ?string $locale = null;
/**
* An array of alternate locales in which the resource is available.
*
* @var string[]
*/
public array $localeAlternate = [];
public ?bool $richAttachment = null;
/**
* An array of URLs of related resources.
*
* @var string[]
*/
public array $seeAlso = [];
/**
* The name of the web site upon which the object resides.
*/
public ?string $siteName = null;
/**
* The title of the object as it should appear in the graph.
*/
public ?string $title = null;
/**
* The type of the object, such as 'article'.
*/
public ?string $type = null;
/**
* The time when the object was last updated.
*/
public ?DateTimeImmutable $updatedTime = null;
/**
* The canonical URL of the object, used as its ID in the graph.
*/
public ?string $url = null;
/**
* An array of videos attached to the object.
*
* @var Video[]
*/
public array $videos = [];
public function __construct()
{
}
/**
* Assigns all properties given to the this Object instance.
*
* @param array|Property[] $properties Array of all properties to assign.
* @param bool $debug Throw exceptions when parsing or not.
*
* @throws UnexpectedValueException
*/
public function assignProperties(array $properties, $debug = false): void
{
foreach ($properties as $property) {
$name = $property->key;
$value = $property->value;
switch($name) {
case Property::AUDIO:
case Property::AUDIO_URL:
$this->audios[] = new Audio($value);
break;
case Property::AUDIO_SECURE_URL:
case Property::AUDIO_TYPE:
if (count($this->audios) > 0) {
$this->handleAudioAttribute($this->audios[count($this->audios) - 1], $name, $value);
} elseif ($debug) {
throw new UnexpectedValueException(
sprintf(
"Found '%s' property but no audio was found before.",
$name
)
);
}
break;
case Property::DESCRIPTION:
if ($this->description === null) {
$this->description = $value;
}
break;
case Property::DETERMINER:
if ($this->determiner === null) {
$this->determiner = $value;
}
break;
case Property::IMAGE:
case Property::IMAGE_URL:
$this->images[] = new Image($value);
break;
case Property::IMAGE_HEIGHT:
case Property::IMAGE_SECURE_URL:
case Property::IMAGE_TYPE:
case Property::IMAGE_WIDTH:
case Property::IMAGE_USER_GENERATED:
if (count($this->images) > 0) {
$this->handleImageAttribute($this->images[count($this->images) - 1], $name, $value);
} elseif ($debug) {
throw new UnexpectedValueException(
sprintf(
"Found '%s' property but no image was found before.",
$name
)
);
}
break;
case Property::LOCALE:
if ($this->locale === null) {
$this->locale = $value;
}
break;
case Property::LOCALE_ALTERNATE:
$this->localeAlternate[] = $value;
break;
case Property::RICH_ATTACHMENT:
$this->richAttachment = $this->convertToBoolean($value);
break;
case Property::SEE_ALSO:
$this->seeAlso[] = $value;
break;
case Property::SITE_NAME:
if ($this->siteName === null) {
$this->siteName = $value;
}
break;
case Property::TITLE:
if ($this->title === null) {
$this->title = $value;
}
break;
case Property::UPDATED_TIME:
if ($this->updatedTime === null) {
$this->updatedTime = $this->convertToDateTime($value);
}
break;
case Property::URL:
if ($this->url === null) {
$this->url = $value;
}
break;
case Property::VIDEO:
case Property::VIDEO_URL:
$this->videos[] = new Video($value);
break;
case Property::VIDEO_HEIGHT:
case Property::VIDEO_SECURE_URL:
case Property::VIDEO_TYPE:
case Property::VIDEO_WIDTH:
if (count($this->videos) > 0) {
$this->handleVideoAttribute($this->videos[count($this->videos) - 1], $name, $value);
} elseif ($debug) {
throw new UnexpectedValueException(sprintf(
"Found '%s' property but no video was found before.",
$name
));
}
}
}
}
private function handleImageAttribute(Image $element, string $name, string $value): void
{
switch($name)
{
case Property::IMAGE_HEIGHT:
$element->height = (int)$value;
break;
case Property::IMAGE_WIDTH:
$element->width = (int)$value;
break;
case Property::IMAGE_TYPE:
$element->type = $value;
break;
case Property::IMAGE_SECURE_URL:
$element->secureUrl = $value;
break;
case Property::IMAGE_USER_GENERATED:
$element->userGenerated = $this->convertToBoolean($value);
break;
}
}
private function handleVideoAttribute(Video $element, string $name, string $value): void
{
switch($name)
{
case Property::VIDEO_HEIGHT:
$element->height = (int)$value;
break;
case Property::VIDEO_WIDTH:
$element->width = (int)$value;
break;
case Property::VIDEO_TYPE:
$element->type = $value;
break;
case Property::VIDEO_SECURE_URL:
$element->secureUrl = $value;
break;
}
}
private function handleAudioAttribute(Audio $element, string $name, string $value): void
{
switch($name)
{
case Property::AUDIO_TYPE:
$element->type = $value;
break;
case Property::AUDIO_SECURE_URL:
$element->secureUrl = $value;
break;
}
}
protected function convertToDateTime(string $value): ?DateTimeImmutable
{
try {
return new DateTimeImmutable($value);
} catch (\Exception $e) {
return null;
}
}
protected function convertToBoolean(string $value): bool
{
switch(strtolower($value))
{
case "1":
case "true":
return true;
default:
return false;
}
}
/**
* Gets all properties set on this object.
*
* @return Property[]
*/
public function getProperties(): array
{
$properties = [];
foreach ($this->audios as $audio) {
$properties = array_merge($properties, $audio->getProperties());
}
if ($this->title !== null) {
$properties[] = new Property(Property::TITLE, $this->title);
}
if ($this->description !== null) {
$properties[] = new Property(Property::DESCRIPTION, $this->description);
}
if ($this->determiner !== null) {
$properties[] = new Property(Property::DETERMINER, $this->determiner);
}
foreach ($this->images as $image) {
$properties = array_merge($properties, $image->getProperties());
}
if ($this->locale !== null) {
$properties[] = new Property(Property::LOCALE, $this->locale);
}
foreach ($this->localeAlternate as $locale) {
$properties[] = new Property(Property::LOCALE_ALTERNATE, $locale);
}
if ($this->richAttachment !== null) {
$properties[] = new Property(Property::RICH_ATTACHMENT, (int)$this->richAttachment);
}
foreach ($this->seeAlso as $seeAlso) {
$properties[] = new Property(Property::SEE_ALSO, $seeAlso);
}
if ($this->siteName !== null) {
$properties[] = new Property(Property::SITE_NAME, $this->siteName);
}
if ($this->type !== null) {
$properties[] = new Property(Property::TYPE, $this->type);
}
if ($this->updatedTime !== null) {
$properties[] = new Property(Property::UPDATED_TIME, $this->updatedTime->format("c"));
}
if ($this->url !== null) {
$properties[] = new Property(Property::URL, $this->url);
}
foreach ($this->videos as $video) {
$properties = array_merge($properties, $video->getProperties());
}
return $properties;
}
}
@@ -0,0 +1,21 @@
<?php
namespace Fusonic\OpenGraph\Objects;
/**
* This object type represents a website. It is a simple object type and uses only common Open Graph properties. For
* specific pages within a website, the article object type should be used.
*
* https://developers.facebook.com/docs/reference/opengraph/object-type/website/
*/
class Website extends ObjectBase
{
const TYPE = "website";
public function __construct()
{
parent::__construct();
$this->type = self::TYPE;
}
}
@@ -0,0 +1,54 @@
<?php
namespace Fusonic\OpenGraph;
/**
* Class holding data for a single Open Graph property on a web page.
*/
class Property
{
const AUDIO = "og:audio";
const AUDIO_SECURE_URL = "og:audio:secure_url";
const AUDIO_TYPE = "og:audio:type";
const AUDIO_URL = "og:audio:url";
const DESCRIPTION = "og:description";
const DETERMINER = "og:determiner";
const IMAGE = "og:image";
const IMAGE_HEIGHT = "og:image:height";
const IMAGE_SECURE_URL = "og:image:secure_url";
const IMAGE_TYPE = "og:image:type";
const IMAGE_URL = "og:image:url";
const IMAGE_WIDTH = "og:image:width";
const IMAGE_USER_GENERATED = "og:image:user_generated";
const LOCALE = "og:locale";
const LOCALE_ALTERNATE = "og:locale:alternate";
const RICH_ATTACHMENT = "og:rich_attachment";
const SEE_ALSO = "og:see_also";
const SITE_NAME = "og:site_name";
const TITLE = "og:title";
const TYPE = "og:type";
const UPDATED_TIME = "og:updated_time";
const URL = "og:url";
const VIDEO = "og:video";
const VIDEO_HEIGHT = "og:video:height";
const VIDEO_SECURE_URL = "og:video:secure_url";
const VIDEO_TYPE = "og:video:type";
const VIDEO_URL = "og:video:url";
const VIDEO_WIDTH = "og:video:width";
/**
* Key of the property without "og:" prefix.
*/
public string $key;
/**
* Value of the property.
*/
public $value;
public function __construct(string $key, $value)
{
$this->key = $key;
$this->value = $value;
}
}
@@ -0,0 +1,65 @@
<?php
namespace Fusonic\OpenGraph;
use DateTimeInterface;
use Fusonic\OpenGraph\Objects\ObjectBase;
use UnexpectedValueException;
/**
* Class for generating Open Graph tags from objects.
*/
class Publisher
{
const DOCTYPE_HTML5 = 1;
const DOCTYPE_XHTML = 2;
/**
* Defines the style in which HTML tags should be written. Use one of Publisher::DOCTYPE_HTML5 or
* Publisher::DOCTYPE_XHTML.
*/
public int $doctype = self::DOCTYPE_HTML5;
public function __construct()
{
}
/**
* Generated HTML tags from the given object.
*/
public function generateHtml(ObjectBase $object): string
{
$html = "";
$format = "<meta property=\"%s\" content=\"%s\"" . ($this->doctype == self::DOCTYPE_XHTML ? " />" : ">");
foreach ($object->getProperties() as $property) {
if ($html !== "") {
$html .= "\n";
}
if ($property->value === null) {
continue;
} elseif ($property->value instanceof DateTimeInterface) {
$value = $property->value->format("c");
} elseif (is_object($property->value)) {
throw new UnexpectedValueException(
sprintf(
"Cannot handle value of type '%s' for property '%s'.",
get_class($property->value),
$property->key
)
);
} elseif ($property->value === true) {
$value = "1";
} elseif ($property->value === false) {
$value = "0";
} else {
$value = (string)$property->value;
}
$html .= sprintf($format, $property->key, htmlspecialchars($value));
}
return $html;
}
}